Loadpoint: cleanup phase configuration and drop deprecations (#18638)
This commit is contained in:
parent
f93ceffa6d
commit
07fb01d28c
19 changed files with 183 additions and 188 deletions
|
|
@ -384,7 +384,7 @@
|
|||
>
|
||||
<SelectGroup
|
||||
id="loadpointParamPhases"
|
||||
v-model="values.phases"
|
||||
v-model="values.phasesConfigured"
|
||||
class="w-100"
|
||||
:options="[
|
||||
{ value: 1, name: $t('config.loadpoint.phases1p') },
|
||||
|
|
@ -559,7 +559,7 @@ const nsPerMin = 60 * 1e9;
|
|||
|
||||
const defaultValues = {
|
||||
title: "",
|
||||
phases: 3,
|
||||
phasesConfigured: 3,
|
||||
minCurrent: 6,
|
||||
maxCurrent: 16,
|
||||
priority: 0,
|
||||
|
|
@ -813,17 +813,17 @@ export default {
|
|||
}
|
||||
},
|
||||
updatePhases() {
|
||||
const { phases } = this.values;
|
||||
const { phasesConfigured } = this.values;
|
||||
if (this.chargerIsSinglePhase) {
|
||||
this.values.phases = 1;
|
||||
this.values.phasesConfigured = 1;
|
||||
return;
|
||||
}
|
||||
if (this.chargerSupports1p3p && this.isNew) {
|
||||
this.values.phases = 0; // automatic
|
||||
this.values.phasesConfigured = 0; // automatic
|
||||
return;
|
||||
}
|
||||
if (!this.chargerSupports1p3p && phases === 0) {
|
||||
this.values.phases = 3; // no automatic switching, default to 3-phase
|
||||
if (!this.chargerSupports1p3p && phasesConfigured === 0) {
|
||||
this.values.phasesConfigured = 3; // no automatic switching, default to 3-phase
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -180,7 +180,6 @@ export default {
|
|||
chargeRemainingDuration: Number,
|
||||
|
||||
// other information
|
||||
phases: Number,
|
||||
phasesConfigured: Number,
|
||||
phasesActive: Number,
|
||||
chargerPhases1p3p: Boolean,
|
||||
|
|
|
|||
|
|
@ -183,7 +183,6 @@ export default {
|
|||
props: {
|
||||
id: [String, Number],
|
||||
phasesConfigured: Number,
|
||||
phasesActive: Number,
|
||||
chargerPhases1p3p: Boolean,
|
||||
chargerSinglePhase: Boolean,
|
||||
batteryBoost: Boolean,
|
||||
|
|
@ -237,7 +236,7 @@ export default {
|
|||
return this.maxPowerPhases(this.phasesConfigured);
|
||||
}
|
||||
}
|
||||
return this.fmtW(this.maxCurrent * V * this.phasesActive);
|
||||
return this.fmtW(this.maxCurrent * V * this.phasesConfigured);
|
||||
},
|
||||
minPower() {
|
||||
if (this.chargerPhases1p3p) {
|
||||
|
|
@ -248,7 +247,7 @@ export default {
|
|||
return this.minPowerPhases(this.phasesConfigured);
|
||||
}
|
||||
}
|
||||
return this.fmtW(this.minCurrent * V * this.phasesActive);
|
||||
return this.fmtW(this.minCurrent * V * this.phasesConfigured);
|
||||
},
|
||||
minCurrentOptions() {
|
||||
const opt1 = [...range(Math.floor(this.maxCurrent), 1), 0.5, 0.25, 0.125];
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
:key="loadpoint.name"
|
||||
:name="loadpoint.title"
|
||||
:editable="!!loadpoint.id"
|
||||
:error="deviceError('loadpoint', loadpoint.name)"
|
||||
data-testid="loadpoint"
|
||||
@edit="editLoadpoint(loadpoint.id)"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const _ClassName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
|
||||
const _ClassName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorshiploadpoint"
|
||||
|
||||
var _ClassIndex = [...]uint8{0, 10, 15, 22, 29, 35, 42, 46, 50, 58, 69, 74, 84, 86, 90, 96, 105, 116}
|
||||
var _ClassIndex = [...]uint8{0, 10, 15, 22, 29, 35, 42, 46, 50, 58, 69, 74, 84, 86, 90, 96, 105, 116, 125}
|
||||
|
||||
const _ClassLowerName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorship"
|
||||
const _ClassLowerName = "configfilemeterchargervehicletariffcircuitsitemqttdatabasemodbusproxyeebusjavascriptgohemsinfluxmessengersponsorshiploadpoint"
|
||||
|
||||
func (i Class) String() string {
|
||||
i -= 1
|
||||
|
|
@ -42,9 +42,10 @@ func _ClassNoOp() {
|
|||
_ = x[ClassInflux-(15)]
|
||||
_ = x[ClassMessenger-(16)]
|
||||
_ = x[ClassSponsorship-(17)]
|
||||
_ = x[ClassLoadpoint-(18)]
|
||||
}
|
||||
|
||||
var _ClassValues = []Class{ClassConfigFile, ClassMeter, ClassCharger, ClassVehicle, ClassTariff, ClassCircuit, ClassSite, ClassMqtt, ClassDatabase, ClassModbusProxy, ClassEEBus, ClassJavascript, ClassGo, ClassHEMS, ClassInflux, ClassMessenger, ClassSponsorship}
|
||||
var _ClassValues = []Class{ClassConfigFile, ClassMeter, ClassCharger, ClassVehicle, ClassTariff, ClassCircuit, ClassSite, ClassMqtt, ClassDatabase, ClassModbusProxy, ClassEEBus, ClassJavascript, ClassGo, ClassHEMS, ClassInflux, ClassMessenger, ClassSponsorship, ClassLoadpoint}
|
||||
|
||||
var _ClassNameToValueMap = map[string]Class{
|
||||
_ClassName[0:10]: ClassConfigFile,
|
||||
|
|
@ -81,6 +82,8 @@ var _ClassNameToValueMap = map[string]Class{
|
|||
_ClassLowerName[96:105]: ClassMessenger,
|
||||
_ClassName[105:116]: ClassSponsorship,
|
||||
_ClassLowerName[105:116]: ClassSponsorship,
|
||||
_ClassName[116:125]: ClassLoadpoint,
|
||||
_ClassLowerName[116:125]: ClassLoadpoint,
|
||||
}
|
||||
|
||||
var _ClassNames = []string{
|
||||
|
|
@ -101,6 +104,7 @@ var _ClassNames = []string{
|
|||
_ClassName[90:96],
|
||||
_ClassName[96:105],
|
||||
_ClassName[105:116],
|
||||
_ClassName[116:125],
|
||||
}
|
||||
|
||||
// ClassString retrieves an enum value from the enum constants string name.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const (
|
|||
ClassInflux
|
||||
ClassMessenger
|
||||
ClassSponsorship
|
||||
ClassLoadpoint
|
||||
)
|
||||
|
||||
// FatalError is an error that can be marshaled
|
||||
|
|
@ -57,7 +58,7 @@ type DeviceError struct {
|
|||
}
|
||||
|
||||
func (e *DeviceError) Error() string {
|
||||
return e.err.Error()
|
||||
return "[" + e.Name + "] " + e.err.Error()
|
||||
}
|
||||
|
||||
// ClassError indicates the class of devices that failed
|
||||
|
|
@ -67,7 +68,7 @@ type ClassError struct {
|
|||
}
|
||||
|
||||
func (e *ClassError) Error() string {
|
||||
return e.err.Error()
|
||||
return e.Class.String() + " " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e ClassError) MarshalJSON() ([]byte, error) {
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ func TestError(t *testing.T) {
|
|||
|
||||
b, err := res.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `{"class":"meter","device":"0815","error":"foo"}`, string(b))
|
||||
require.Equal(t, `{"class":"meter","device":"0815","error":"[0815] foo"}`, string(b))
|
||||
}
|
||||
|
|
|
|||
18
cmd/setup.go
18
cmd/setup.go
|
|
@ -862,7 +862,7 @@ func configureSiteAndLoadpoints(conf *globalconfig.All) (*core.Site, error) {
|
|||
}
|
||||
|
||||
if err := configureLoadpoints(*conf); err != nil {
|
||||
return nil, fmt.Errorf("failed configuring loadpoints: %w", err)
|
||||
return nil, &ClassError{ClassLoadpoint, err}
|
||||
}
|
||||
|
||||
tariffs, err := configureTariffs(conf.Tariffs)
|
||||
|
|
@ -945,7 +945,7 @@ func configureLoadpoints(conf globalconfig.All) error {
|
|||
|
||||
instance, err := core.NewLoadpointFromConfig(log, settings, cc.Other)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed configuring loadpoint: %w", err)
|
||||
return &DeviceError{cc.Name, err}
|
||||
}
|
||||
|
||||
if err := config.Loadpoints().Add(config.NewStaticDevice(cc, loadpoint.API(instance))); err != nil {
|
||||
|
|
@ -970,21 +970,21 @@ func configureLoadpoints(conf globalconfig.All) error {
|
|||
|
||||
dynamic, static, err := loadpoint.SplitConfig(cc.Other)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed configuring loadpoint: %w", err)
|
||||
return &DeviceError{cc.Name, err}
|
||||
}
|
||||
|
||||
instance, err := core.NewLoadpointFromConfig(log, settings, static)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed configuring loadpoint: %w", err)
|
||||
}
|
||||
|
||||
if err := dynamic.Apply(instance); err != nil {
|
||||
return err
|
||||
return &DeviceError{cc.Name, err}
|
||||
}
|
||||
|
||||
dev := config.NewConfigurableDevice[loadpoint.API](&conf, instance)
|
||||
if err := config.Loadpoints().Add(dev); err != nil {
|
||||
return err
|
||||
return &DeviceError{cc.Name, err}
|
||||
}
|
||||
|
||||
if err := dynamic.Apply(instance); err != nil {
|
||||
return &DeviceError{cc.Name, err}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ const (
|
|||
DisableDelay = "disableDelay"
|
||||
BatteryBoost = "batteryBoost"
|
||||
|
||||
Phases = "phases" // configured phases (1/3, 0 for auto on 1p3p chargers, nil for plain chargers)
|
||||
PhasesConfigured = "phasesConfigured" // TODO mirrors "phases" for UI purposes
|
||||
PhasesEnabled = "phasesEnabled" // enabled phases (1/3)
|
||||
PhasesConfigured = "phasesConfigured" // desired phase mode (0/1/3, 0 = automatic), user selection
|
||||
PhasesActive = "phasesActive" // active phases as used by vehicle (1/2/3)
|
||||
|
||||
ChargerIcon = "chargerIcon" // charger icon for ui
|
||||
|
|
|
|||
|
|
@ -93,20 +93,22 @@ type Loadpoint struct {
|
|||
Soc loadpoint.SocConfig
|
||||
Enable, Disable loadpoint.ThresholdConfig
|
||||
|
||||
// TODO deprecated
|
||||
// from yaml
|
||||
DefaultMode api.ChargeMode `mapstructure:"mode"` // Default charge mode, used for disconnect
|
||||
Title_ string `mapstructure:"title"` // UI title
|
||||
Priority_ int `mapstructure:"priority"` // Priority
|
||||
GuardDuration_ time.Duration `mapstructure:"guardduration"` // charger enable/disable minimum holding time
|
||||
Phases_ int `mapstructure:"phases"`
|
||||
MinCurrent_ float64 `mapstructure:"minCurrent"`
|
||||
MaxCurrent_ float64 `mapstructure:"maxCurrent"`
|
||||
Title string `mapstructure:"title"` // UI title
|
||||
Priority int `mapstructure:"priority"` // Priority
|
||||
|
||||
// from yaml, deprecated
|
||||
GuardDuration_ time.Duration `mapstructure:"guardduration"` // ignored, present for compatibility
|
||||
Phases_ int `mapstructure:"phases"` // ignored, present for compatibility
|
||||
MinCurrent_ float64 `mapstructure:"minCurrent"` // ignored, present for compatibility
|
||||
MaxCurrent_ float64 `mapstructure:"maxCurrent"` // ignored, present for compatibility
|
||||
|
||||
title string // UI title
|
||||
priority int // Priority
|
||||
minCurrent float64 // PV mode: start current Min+PV mode: min current
|
||||
maxCurrent float64 // Max allowed current. Physically ensured by the charger
|
||||
configuredPhases int // Charger configured phase mode 0/1/3
|
||||
phasesConfigured int // Charger configured phase mode 0/1/3
|
||||
limitSoc int // Session limit for soc
|
||||
limitEnergy float64 // Session limit for energy
|
||||
smartCostLimit *float64 // always charge if cost is below this value
|
||||
|
|
@ -223,13 +225,10 @@ func NewLoadpointFromConfig(log *util.Logger, settings settings.Settings, other
|
|||
|
||||
// phase switching defaults based on charger capabilities
|
||||
if !lp.hasPhaseSwitching() {
|
||||
lp.configuredPhases = 3
|
||||
lp.phasesConfigured = 3
|
||||
lp.phases = 3
|
||||
}
|
||||
|
||||
// TODO deprecated
|
||||
lp.migrateSettings()
|
||||
|
||||
// validate thresholds
|
||||
if lp.Enable.Threshold > lp.Disable.Threshold {
|
||||
lp.log.WARN.Printf("PV mode enable threshold (%.0fW) is larger than disable threshold (%.0fW)", lp.Enable.Threshold, lp.Disable.Threshold)
|
||||
|
|
@ -275,73 +274,43 @@ func NewLoadpoint(log *util.Logger, settings settings.Settings) *Loadpoint {
|
|||
return lp
|
||||
}
|
||||
|
||||
// migrateSettings migrates loadpoint settings
|
||||
func (lp *Loadpoint) migrateSettings() {
|
||||
// One-time migrations MUST be mirrored in restoreSettings
|
||||
if lp.DefaultMode != "" {
|
||||
lp.log.WARN.Println("deprecated: mode setting is ignored, please remove")
|
||||
if _, err := lp.settings.String(keys.Mode); err != nil {
|
||||
lp.settings.SetString(keys.Mode, string(lp.DefaultMode))
|
||||
}
|
||||
}
|
||||
if lp.Title_ != "" {
|
||||
lp.log.WARN.Println("deprecated: title setting is ignored, please remove")
|
||||
if _, err := lp.settings.String(keys.Title); err != nil {
|
||||
lp.settings.SetString(keys.Title, lp.Title_)
|
||||
}
|
||||
}
|
||||
if lp.Priority_ > 0 {
|
||||
lp.log.WARN.Println("deprecated: priority setting is ignored, please remove")
|
||||
if _, err := lp.settings.String(keys.Priority); err != nil {
|
||||
lp.settings.SetInt(keys.Priority, int64(lp.Priority_))
|
||||
}
|
||||
}
|
||||
if lp.MinCurrent_ > 0 {
|
||||
lp.log.WARN.Println("deprecated: mincurrent setting is ignored, please remove")
|
||||
if _, err := lp.settings.Float(keys.MinCurrent); err != nil {
|
||||
lp.settings.SetFloat(keys.MinCurrent, lp.MinCurrent_)
|
||||
}
|
||||
}
|
||||
if lp.MaxCurrent_ > 0 {
|
||||
lp.log.WARN.Println("deprecated: maxcurrent setting is ignored, please remove")
|
||||
if _, err := lp.settings.Float(keys.MaxCurrent); err != nil {
|
||||
lp.settings.SetFloat(keys.MaxCurrent, lp.MaxCurrent_)
|
||||
}
|
||||
}
|
||||
if lp.Phases_ > 0 {
|
||||
lp.log.WARN.Println("deprecated: phases setting is ignored, please remove")
|
||||
if _, err := lp.settings.Int(keys.Phases); err != nil {
|
||||
lp.settings.SetInt(keys.Phases, int64(lp.Phases_))
|
||||
}
|
||||
}
|
||||
if lp.Soc.Estimate != nil || lp.Soc.Poll.Mode != loadpoint.PollCharging || lp.Soc.Poll.Interval != 0 {
|
||||
lp.log.WARN.Println("deprecated: soc setting is ignored, please remove")
|
||||
if _, err := lp.settings.String(keys.Soc); err != nil {
|
||||
lp.settings.SetJson(keys.Soc, lp.Soc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// restoreSettings restores loadpoint settings
|
||||
func (lp *Loadpoint) restoreSettings() {
|
||||
if testing.Testing() {
|
||||
return
|
||||
}
|
||||
if v, err := lp.settings.String(keys.Title); err == nil && v != "" {
|
||||
lp.setTitle(v)
|
||||
|
||||
// from yaml
|
||||
if lp.Title != "" {
|
||||
lp.setTitle(lp.Title)
|
||||
}
|
||||
if lp.Priority > 0 {
|
||||
lp.setPriority(lp.Priority)
|
||||
}
|
||||
|
||||
// deprecated yaml properties
|
||||
if lp.Phases_ > 0 {
|
||||
lp.log.WARN.Printf("ignoring deprecated phases: %d. please configure via UI", lp.Phases_)
|
||||
}
|
||||
if lp.MinCurrent_ > 0 {
|
||||
lp.log.WARN.Printf("ignoring deprecated minCurrent: %f. please configure via UI", lp.MinCurrent_)
|
||||
}
|
||||
if lp.MaxCurrent_ > 0 {
|
||||
lp.log.WARN.Printf("ignoring deprecated maxCurrent: %f. please configure via UI", lp.MaxCurrent_)
|
||||
}
|
||||
if lp.GuardDuration_ > 0 {
|
||||
lp.log.WARN.Printf("ignoring deprecated guardduration: %s. please configure via UI", lp.GuardDuration_)
|
||||
}
|
||||
|
||||
// restore runtime configuration (database & yaml LPs)
|
||||
if v, err := lp.settings.String(keys.Mode); err == nil && v != "" {
|
||||
lp.setMode(api.ChargeMode(v))
|
||||
}
|
||||
if v, err := lp.settings.Int(keys.Priority); err == nil && v > 0 {
|
||||
lp.setPriority(int(v))
|
||||
}
|
||||
if v, err := lp.settings.Int(keys.Phases); err == nil && (v > 0 || lp.hasPhaseSwitching()) {
|
||||
lp.setConfiguredPhases(int(v))
|
||||
// for 1p3p charger, we don't know the physical state yet (phases == 0), so don't touch it
|
||||
if !lp.hasPhaseSwitching() {
|
||||
lp.phases = lp.configuredPhases
|
||||
}
|
||||
if v, err := lp.settings.Int(keys.PhasesConfigured); err == nil && (v > 0 || lp.hasPhaseSwitching()) {
|
||||
lp.setPhasesConfigured(int(v))
|
||||
}
|
||||
if v, err := lp.settings.Float(keys.MinCurrent); err == nil && v > 0 {
|
||||
lp.setMinCurrent(v)
|
||||
|
|
@ -534,7 +503,7 @@ func (lp *Loadpoint) evVehicleDisconnectHandler() {
|
|||
lp.clearSession()
|
||||
|
||||
// phases are unknown when vehicle disconnects
|
||||
lp.resetMeasuredPhases()
|
||||
lp.ResetMeasuredPhases()
|
||||
|
||||
// energy and duration
|
||||
lp.energyMetrics.Publish("session", lp)
|
||||
|
|
@ -647,20 +616,16 @@ func (lp *Loadpoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even
|
|||
lp.publish(keys.DisableDelay, lp.Disable.Delay)
|
||||
|
||||
if phases := lp.getChargerPhysicalPhases(); phases != 0 {
|
||||
if lp.configuredPhases != phases && lp.configuredPhases != 0 {
|
||||
lp.log.WARN.Printf("configured phases %d do not match physical phases %d", lp.configuredPhases, phases)
|
||||
if lp.phasesConfigured != phases && lp.phasesConfigured != 0 {
|
||||
lp.log.WARN.Printf("configured phases %d do not match physical phases %d", lp.phasesConfigured, phases)
|
||||
}
|
||||
lp.phases = phases
|
||||
lp.configuredPhases = phases
|
||||
lp.publish(keys.Phases, phases)
|
||||
} else {
|
||||
lp.publish(keys.Phases, nil)
|
||||
lp.phasesConfigured = phases
|
||||
}
|
||||
|
||||
lp.publish(keys.PhasesConfigured, lp.configuredPhases)
|
||||
lp.publish(keys.PhasesConfigured, lp.phasesConfigured)
|
||||
lp.publish(keys.ChargerPhases1p3p, lp.hasPhaseSwitching())
|
||||
lp.publish(keys.ChargerSinglePhase, lp.getChargerPhysicalPhases() == 1)
|
||||
lp.publish(keys.PhasesEnabled, lp.phases)
|
||||
lp.publish(keys.PhasesActive, lp.ActivePhases())
|
||||
lp.publish(keys.SmartCostLimit, lp.smartCostLimit)
|
||||
lp.publishTimer(phaseTimer, 0, timerInactive)
|
||||
|
|
@ -815,7 +780,7 @@ func (lp *Loadpoint) syncCharger() error {
|
|||
if chargerPhases, err = pg.GetPhases(); err == nil {
|
||||
if chargerPhases > 0 && chargerPhases != phases {
|
||||
lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases)
|
||||
lp.setPhases(chargerPhases)
|
||||
lp.SetPhases(chargerPhases)
|
||||
}
|
||||
} else {
|
||||
if errors.Is(err, api.ErrNotAvailable) {
|
||||
|
|
@ -829,7 +794,7 @@ func (lp *Loadpoint) syncCharger() error {
|
|||
if !isPg || errors.Is(err, api.ErrNotAvailable) {
|
||||
if chargerPhases > phases {
|
||||
lp.log.WARN.Printf("charger logic error: phases mismatch (got %d measured, expected %d)", chargerPhases, phases)
|
||||
lp.setPhases(chargerPhases)
|
||||
lp.SetPhases(chargerPhases)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1188,13 +1153,13 @@ func (lp *Loadpoint) resetPhaseTimer() {
|
|||
|
||||
// scalePhasesRequired validates if fixed phase configuration matches enabled phases
|
||||
func (lp *Loadpoint) scalePhasesRequired() bool {
|
||||
return lp.hasPhaseSwitching() && lp.configuredPhases != 0 && lp.configuredPhases != lp.GetPhases()
|
||||
return lp.hasPhaseSwitching() && lp.phasesConfigured != 0 && lp.phasesConfigured != lp.GetPhases()
|
||||
}
|
||||
|
||||
// scalePhasesIfAvailable scales if api.PhaseSwitcher is available
|
||||
func (lp *Loadpoint) scalePhasesIfAvailable(phases int) error {
|
||||
if lp.configuredPhases != 0 {
|
||||
phases = lp.configuredPhases
|
||||
if lp.phasesConfigured != 0 {
|
||||
phases = lp.phasesConfigured
|
||||
}
|
||||
|
||||
if lp.hasPhaseSwitching() {
|
||||
|
|
@ -1224,7 +1189,7 @@ func (lp *Loadpoint) scalePhases(phases int) error {
|
|||
lp.phasesSwitched = lp.clock.Now()
|
||||
|
||||
// update setting and reset timer
|
||||
lp.setPhases(phases)
|
||||
lp.SetPhases(phases)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -1252,13 +1217,13 @@ func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) in
|
|||
if lp.chargerUpdateCompleted() && lp.phaseSwitchCompleted() {
|
||||
lp.log.WARN.Printf("ignoring inconsistent phases: %dp < %dp observed active", phases, measuredPhases)
|
||||
}
|
||||
lp.resetMeasuredPhases()
|
||||
lp.ResetMeasuredPhases()
|
||||
}
|
||||
|
||||
var waiting bool
|
||||
activePhases := lp.ActivePhases()
|
||||
availablePower := lp.chargePower - sitePower
|
||||
scalable := (sitePower > 0 || !lp.enabled) && activePhases > 1 && lp.configuredPhases < 3
|
||||
scalable := (sitePower > 0 || !lp.enabled) && activePhases > 1 && lp.phasesConfigured < 3
|
||||
|
||||
lp.log.DEBUG.Printf("!! pvScalePhases DOWN activePhases: %d, available power: %.0fW, scalable: %t", activePhases, availablePower, scalable)
|
||||
|
||||
|
|
@ -1604,7 +1569,7 @@ func (lp *Loadpoint) updateChargeVoltages() {
|
|||
|
||||
if phases >= 1 {
|
||||
lp.log.DEBUG.Printf("detected connected phases: %dp", phases)
|
||||
lp.setPhases(phases)
|
||||
lp.SetPhases(phases)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1852,7 +1817,7 @@ func (lp *Loadpoint) Update(sitePower, batteryBoostPower float64, rates api.Rate
|
|||
err = lp.setLimit(0)
|
||||
|
||||
case lp.scalePhasesRequired():
|
||||
err = lp.scalePhases(lp.configuredPhases)
|
||||
err = lp.scalePhases(lp.phasesConfigured)
|
||||
|
||||
case lp.remoteControlled(loadpoint.RemoteHardDisable):
|
||||
remoteDisabled = loadpoint.RemoteHardDisable
|
||||
|
|
|
|||
|
|
@ -68,8 +68,10 @@ type API interface {
|
|||
SetDefaultMode(api.ChargeMode)
|
||||
// GetPhases returns the enabled phases
|
||||
GetPhases() int
|
||||
// SetPhases sets the enabled phases
|
||||
SetPhases(int) error
|
||||
// GetPhasesConfigured returns the configured phases
|
||||
GetPhasesConfigured() int
|
||||
// SetPhasesConfigured sets the configured phases
|
||||
SetPhasesConfigured(int) error
|
||||
// ActivePhases returns the active phases for the current vehicle
|
||||
ActivePhases() int
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ type DynamicConfig struct {
|
|||
Title string `json:"title"`
|
||||
DefaultMode string `json:"defaultMode"`
|
||||
Priority int `json:"priority"`
|
||||
Phases int `json:"phases"`
|
||||
PhasesConfigured int `json:"phasesConfigured"`
|
||||
MinCurrent float64 `json:"minCurrent"`
|
||||
MaxCurrent float64 `json:"maxCurrent"`
|
||||
SmartCostLimit *float64 `json:"smartCostLimit"`
|
||||
|
|
@ -69,7 +69,7 @@ func (payload DynamicConfig) Apply(lp API) error {
|
|||
}
|
||||
|
||||
if err == nil {
|
||||
err = lp.SetPhases(payload.Phases)
|
||||
err = lp.SetPhasesConfigured(payload.PhasesConfigured)
|
||||
}
|
||||
|
||||
if err == nil && payload.MinCurrent != 0 {
|
||||
|
|
|
|||
|
|
@ -405,6 +405,20 @@ func (mr *MockAPIMockRecorder) GetPhases() *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPhases", reflect.TypeOf((*MockAPI)(nil).GetPhases))
|
||||
}
|
||||
|
||||
// GetPhasesConfigured mocks base method.
|
||||
func (m *MockAPI) GetPhasesConfigured() int {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPhasesConfigured")
|
||||
ret0, _ := ret[0].(int)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetPhasesConfigured indicates an expected call of GetPhasesConfigured.
|
||||
func (mr *MockAPIMockRecorder) GetPhasesConfigured() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPhasesConfigured", reflect.TypeOf((*MockAPI)(nil).GetPhasesConfigured))
|
||||
}
|
||||
|
||||
// GetPlan mocks base method.
|
||||
func (m *MockAPI) GetPlan(targetTime time.Time, requiredDuration time.Duration) (api.Rates, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
@ -780,18 +794,18 @@ func (mr *MockAPIMockRecorder) SetMode(arg0 any) *gomock.Call {
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMode", reflect.TypeOf((*MockAPI)(nil).SetMode), arg0)
|
||||
}
|
||||
|
||||
// SetPhases mocks base method.
|
||||
func (m *MockAPI) SetPhases(arg0 int) error {
|
||||
// SetPhasesConfigured mocks base method.
|
||||
func (m *MockAPI) SetPhasesConfigured(arg0 int) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetPhases", arg0)
|
||||
ret := m.ctrl.Call(m, "SetPhasesConfigured", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetPhases indicates an expected call of SetPhases.
|
||||
func (mr *MockAPIMockRecorder) SetPhases(arg0 any) *gomock.Call {
|
||||
// SetPhasesConfigured indicates an expected call of SetPhasesConfigured.
|
||||
func (mr *MockAPIMockRecorder) SetPhasesConfigured(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPhases", reflect.TypeOf((*MockAPI)(nil).SetPhases), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetPhasesConfigured", reflect.TypeOf((*MockAPI)(nil).SetPhasesConfigured), arg0)
|
||||
}
|
||||
|
||||
// SetPlanEnergy mocks base method.
|
||||
|
|
|
|||
|
|
@ -166,25 +166,27 @@ func (lp *Loadpoint) SetPriority(prio int) {
|
|||
|
||||
lp.log.DEBUG.Println("set priority:", prio)
|
||||
|
||||
if lp.Priority_ != prio {
|
||||
if lp.Priority != prio {
|
||||
lp.setPriority(prio)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPhases returns loadpoint enabled phases
|
||||
// GetPhases returns the enabled phases
|
||||
func (lp *Loadpoint) GetPhases() int {
|
||||
lp.RLock()
|
||||
defer lp.RUnlock()
|
||||
return lp.getPhases()
|
||||
}
|
||||
|
||||
// getPhases returns loadpoint enabled phases
|
||||
func (lp *Loadpoint) getPhases() int {
|
||||
return lp.phases
|
||||
}
|
||||
|
||||
// SetPhases sets loadpoint enabled phases
|
||||
func (lp *Loadpoint) SetPhases(phases int) error {
|
||||
// GetPhasesConfigured returns the configured phases
|
||||
func (lp *Loadpoint) GetPhasesConfigured() int {
|
||||
lp.RLock()
|
||||
defer lp.RUnlock()
|
||||
return lp.phasesConfigured
|
||||
}
|
||||
|
||||
// SetPhasesConfigured sets the configured phases
|
||||
func (lp *Loadpoint) SetPhasesConfigured(phases int) error {
|
||||
// limit auto mode (phases=0) to scalable charger
|
||||
if !lp.hasPhaseSwitching() && phases == 0 {
|
||||
return fmt.Errorf("charger does not support phase switching")
|
||||
|
|
@ -198,13 +200,13 @@ func (lp *Loadpoint) SetPhases(phases int) error {
|
|||
lp.log.DEBUG.Println("set phases:", phases)
|
||||
|
||||
lp.Lock()
|
||||
lp.setConfiguredPhases(phases)
|
||||
lp.Unlock()
|
||||
lp.setPhasesConfigured(phases)
|
||||
|
||||
// apply immediately if not 1p3p
|
||||
if !lp.hasPhaseSwitching() {
|
||||
lp.setPhases(phases)
|
||||
}
|
||||
lp.Unlock()
|
||||
|
||||
lp.requestUpdate()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,23 +5,30 @@ import (
|
|||
"github.com/evcc-io/evcc/core/keys"
|
||||
)
|
||||
|
||||
// setConfiguredPhases sets the default phase configuration
|
||||
func (lp *Loadpoint) setConfiguredPhases(phases int) {
|
||||
lp.configuredPhases = phases
|
||||
lp.publish(keys.Phases, lp.configuredPhases)
|
||||
lp.publish(keys.PhasesConfigured, lp.configuredPhases) // TODO remove
|
||||
lp.settings.SetInt(keys.Phases, int64(lp.configuredPhases))
|
||||
// setPhasesConfigured sets the default phase configuration
|
||||
func (lp *Loadpoint) setPhasesConfigured(phases int) {
|
||||
lp.phasesConfigured = phases
|
||||
lp.publish(keys.PhasesConfigured, lp.phasesConfigured)
|
||||
lp.settings.SetInt(keys.PhasesConfigured, int64(lp.phasesConfigured))
|
||||
|
||||
// configured phases are actual phases for non-1p3p charger
|
||||
// for 1p3p charger, configuration does not mean that the physical state has changed, so don't touch it
|
||||
if !lp.hasPhaseSwitching() {
|
||||
lp.setPhases(phases)
|
||||
}
|
||||
}
|
||||
|
||||
// SetPhases sets the number of enabled phases without modifying the charger
|
||||
func (lp *Loadpoint) SetPhases(phases int) {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
lp.setPhases(phases)
|
||||
}
|
||||
|
||||
// setPhases sets the number of enabled phases without modifying the charger
|
||||
func (lp *Loadpoint) setPhases(phases int) {
|
||||
if lp.GetPhases() != phases {
|
||||
lp.Lock()
|
||||
if lp.phases != phases {
|
||||
lp.phases = phases
|
||||
lp.Unlock()
|
||||
|
||||
// publish updated phase configuration
|
||||
lp.publish(keys.PhasesEnabled, lp.phases)
|
||||
|
||||
// reset timer to disabled state
|
||||
lp.resetPhaseTimer()
|
||||
|
|
@ -31,13 +38,17 @@ func (lp *Loadpoint) setPhases(phases int) {
|
|||
}
|
||||
}
|
||||
|
||||
// ResetMeasuredPhases resets measured phases to unknown on vehicle disconnect, phase switch or phase api call
|
||||
func (lp *Loadpoint) ResetMeasuredPhases() {
|
||||
lp.Lock()
|
||||
defer lp.Unlock()
|
||||
lp.resetMeasuredPhases()
|
||||
}
|
||||
|
||||
// resetMeasuredPhases resets measured phases to unknown on vehicle disconnect, phase switch or phase api call
|
||||
func (lp *Loadpoint) resetMeasuredPhases() {
|
||||
lp.Lock()
|
||||
lp.measuredPhases = 0
|
||||
lp.Unlock()
|
||||
|
||||
lp.publish(keys.PhasesActive, lp.ActivePhases())
|
||||
lp.publish(keys.PhasesActive, lp.activePhases())
|
||||
}
|
||||
|
||||
// GetMeasuredPhases provides synchronized access to measuredPhases
|
||||
|
|
@ -73,7 +84,7 @@ func (lp *Loadpoint) ActivePhases() int {
|
|||
// activePhases returns the number of expectedly active phases for the meter.
|
||||
// If unknown for 1p3p chargers during startup it will assume 3p.
|
||||
func (lp *Loadpoint) activePhases() int {
|
||||
physical := lp.getPhases()
|
||||
physical := lp.phases
|
||||
vehicle := lp.getVehiclePhases()
|
||||
measured := lp.getMeasuredPhases()
|
||||
charger := lp.getChargerPhysicalPhases()
|
||||
|
|
@ -97,8 +108,7 @@ func (lp *Loadpoint) MinActivePhases() int {
|
|||
|
||||
// minActivePhases returns the minimum number of active phases for the loadpoint.
|
||||
func (lp *Loadpoint) minActivePhases() int {
|
||||
// 1p3p supported or limit 1p
|
||||
if lp.hasPhaseSwitching() || lp.configuredPhases == 1 {
|
||||
if lp.hasPhaseSwitching() || lp.phasesConfigured == 1 {
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +124,7 @@ func (lp *Loadpoint) MaxActivePhases() int {
|
|||
|
||||
// maxActivePhases returns the maximum number of active phases for the loadpoint.
|
||||
func (lp *Loadpoint) maxActivePhases() int {
|
||||
physical := lp.getPhases()
|
||||
physical := lp.phases
|
||||
measured := lp.getMeasuredPhases()
|
||||
vehicle := lp.getVehiclePhases()
|
||||
charger := lp.getChargerPhysicalPhases()
|
||||
|
|
@ -126,7 +136,7 @@ func (lp *Loadpoint) maxActivePhases() int {
|
|||
|
||||
// if 1p3p supported then assume configured limit or 3p
|
||||
if lp.hasPhaseSwitching() {
|
||||
physical = lp.configuredPhases
|
||||
physical = lp.phasesConfigured
|
||||
}
|
||||
|
||||
return min(expect(vehicle), expect(physical), expect(measured), expect(charger))
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func TestMaxActivePhases(t *testing.T) {
|
|||
vehicle.EXPECT().Phases().Return(tc.vehicle).MinTimes(1)
|
||||
|
||||
lp := &Loadpoint{
|
||||
configuredPhases: configured, // fixed phases or default
|
||||
phasesConfigured: configured, // fixed phases or default
|
||||
vehicle: vehicle,
|
||||
phases: tc.physical,
|
||||
measuredPhases: tc.measuredPhases,
|
||||
|
|
@ -134,7 +134,7 @@ func TestMinActivePhases(t *testing.T) {
|
|||
vehicle.EXPECT().Phases().Return(tc.vehicle).AnyTimes()
|
||||
|
||||
lp := &Loadpoint{
|
||||
configuredPhases: configured, // fixed phases or default
|
||||
phasesConfigured: configured, // fixed phases or default
|
||||
vehicle: vehicle,
|
||||
phases: tc.physical,
|
||||
measuredPhases: tc.measuredPhases,
|
||||
|
|
@ -231,7 +231,7 @@ func TestPvScalePhases(t *testing.T) {
|
|||
minCurrent: minA,
|
||||
maxCurrent: maxA,
|
||||
vehicle: vehicle,
|
||||
configuredPhases: 0, // allow switching
|
||||
phasesConfigured: 0, // allow switching
|
||||
phases: tc.physical,
|
||||
status: api.StatusC,
|
||||
}
|
||||
|
|
@ -463,7 +463,7 @@ func TestScalePhasesIfAvailable(t *testing.T) {
|
|||
phaseCharger,
|
||||
},
|
||||
minCurrent: minA,
|
||||
configuredPhases: tc.dflt, // fixed phases or default
|
||||
phasesConfigured: tc.dflt, // fixed phases or default
|
||||
phases: tc.physical, // current phase status
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param)
|
|||
"limitenergy": {"POST", "/limitenergy/{value:[0-9.]+}", floatHandler(pass(lp.SetLimitEnergy), lp.GetLimitEnergy)},
|
||||
"mincurrent": {"POST", "/mincurrent/{value:[0-9.]+}", floatHandler(lp.SetMinCurrent, lp.GetMinCurrent)},
|
||||
"maxcurrent": {"POST", "/maxcurrent/{value:[0-9.]+}", floatHandler(lp.SetMaxCurrent, lp.GetMaxCurrent)},
|
||||
"phases": {"POST", "/phases/{value:[0-9]+}", intHandler(lp.SetPhases, lp.GetPhases)},
|
||||
"phases": {"POST", "/phases/{value:[0-9]+}", intHandler(lp.SetPhasesConfigured, lp.GetPhasesConfigured)},
|
||||
"plan": {"GET", "/plan", planHandler(lp)},
|
||||
"staticPlanPreview": {"GET", "/plan/static/preview/{type:(?:soc|energy)}/{value:[0-9.]+}/{time:[0-9TZ:.+-]+}", staticPlanPreviewHandler(lp)},
|
||||
"repeatingPlanPreview": {"GET", "/plan/repeating/preview/{soc:[0-9]+}/{weekdays:[0-6,]+}/{time:[0-2][0-9]:[0-5][0-9]}/{tz:[a-zA-Z0-9_./:-]+}", repeatingPlanPreviewHandler(lp)},
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func getLoadpointDynamicConfig(lp loadpoint.API) loadpoint.DynamicConfig {
|
|||
Title: lp.GetTitle(),
|
||||
DefaultMode: string(lp.GetDefaultMode()),
|
||||
Priority: lp.GetPriority(),
|
||||
Phases: lp.GetPhases(),
|
||||
PhasesConfigured: lp.GetPhasesConfigured(),
|
||||
MinCurrent: lp.GetMinCurrent(),
|
||||
MaxCurrent: lp.GetMaxCurrent(),
|
||||
SmartCostLimit: lp.GetSmartCostLimit(),
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error {
|
|||
func (m *MQTT) listenLoadpointSetters(topic string, site site.API, lp loadpoint.API) error {
|
||||
for _, s := range []setter{
|
||||
{"mode", setterFunc(api.ChargeModeString, pass(lp.SetMode))},
|
||||
{"phases", intSetter(lp.SetPhases)},
|
||||
{"phases", intSetter(lp.SetPhasesConfigured)},
|
||||
{"limitSoc", intSetter(pass(lp.SetLimitSoc))},
|
||||
{"priority", intSetter(pass(lp.SetPriority))},
|
||||
{"minCurrent", floatSetter(lp.SetMinCurrent)},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue