diff --git a/assets/index.html b/assets/index.html index 89cb9c11d..9ec85ead3 100644 --- a/assets/index.html +++ b/assets/index.html @@ -335,7 +335,8 @@ loadpoints:
Leistung - + +
@@ -413,6 +414,7 @@ loadpoints: 'progress-bar-animated': state.charging, 'bg-light': !state.connected, 'text-secondary': !state.connected, + 'bg-warning': state.connected && minSoCActive, }" v-bind:style="{width: socChargeDisplayWidth+'%'}">{{socChargeDisplayValue}}
diff --git a/assets/js/app.js b/assets/js/app.js index baf67c18f..b09a0b072 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -346,7 +346,12 @@ Vue.component("loadpoint", { Vue.component("loadpoint-details", { template: "#loadpoint-details-template", props: ["state"], - mixins: [formatter] + mixins: [formatter], + computed: { + minSoCActive: function () { + return this.state.minSoC > 0 && this.state.socCharge < this.state.minSoC; + } + } }); Vue.component("vehicle", { @@ -377,6 +382,9 @@ Vue.component("vehicle", { socCharge += "%"; } return socCharge; + }, + minSoCActive: function () { + return this.state.minSoC > 0 && this.state.socCharge < this.state.minSoC; } } }); diff --git a/cmd/setup.go b/cmd/setup.go index 8f66457ae..4831e6626 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -21,7 +21,7 @@ func init() { } // setup influx databases -func configureDatabase(conf server.InfluxConfig, loadPoints []*core.LoadPoint, in <-chan util.Param) { +func configureDatabase(conf server.InfluxConfig, loadPoints []core.LoadPointAPI, in <-chan util.Param) { influx := server.NewInfluxClient( conf.URL, conf.Token, diff --git a/core/loadpoint.go b/core/loadpoint.go index 9e593d94c..5d689b539 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -29,6 +29,15 @@ const ( minActiveCurrent = 1.0 // minimum current at which a phase is treated as active ) +// SoCConfig defines soc settings, estimation and update behaviour +type SoCConfig struct { + AlwaysUpdate bool `mapstructure:"alwaysUpdate"` + Levels []int `mapstructure:"levels"` + Estimate bool `mapstructure:"estimate"` + Min int `mapstructure:"min"` // Default minimum SoC, guarded by mutex + Target int `mapstructure:"target"` // Default target SoC, guarded by mutex +} + // ThresholdConfig defines enable/disable hysteresis parameters type ThresholdConfig struct { Delay time.Duration @@ -47,8 +56,7 @@ type LoadPoint struct { // exposed public configuration sync.Mutex // guard status - Mode api.ChargeMode `mapstructure:"mode"` // Charge mode, guarded by mutex - TargetSoC int `mapstructure:"targetSoC"` // Target SoC, guarded by mutex + Mode api.ChargeMode `mapstructure:"mode"` // Charge mode, guarded by mutex Title string `mapstructure:"title"` // UI title Phases int64 `mapstructure:"phases"` // Phases- required for converting power and current @@ -57,11 +65,7 @@ type LoadPoint struct { Meters struct { ChargeMeterRef string `mapstructure:"charge"` // Charge meter reference } - SoC struct { - AlwaysUpdate bool `mapstructure:"alwaysUpdate"` - Levels []int `mapstructure:"levels"` - Estimate bool `mapstructure:"estimate"` - } + SoC SoCConfig OnDisconnect struct { Mode api.ChargeMode `mapstructure:"mode"` // Charge mode to apply when car disconnected TargetSoC int `mapstructure:"targetSoC"` // Target SoC to apply when car disconnected @@ -102,11 +106,14 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin lp.OnDisconnect.Mode = api.ChargeModeString(string(lp.OnDisconnect.Mode)) sort.Ints(lp.SoC.Levels) - if lp.TargetSoC == 0 { - lp.TargetSoC = 100 + if lp.SoC.Target == 0 { + lp.SoC.Target = lp.OnDisconnect.TargetSoC // use disconnect value as default soc + if lp.SoC.Target == 0 { + lp.SoC.Target = 100 + } if len(lp.SoC.Levels) > 0 { - lp.TargetSoC = lp.SoC.Levels[len(lp.SoC.Levels)-1] + lp.SoC.Target = lp.SoC.Levels[len(lp.SoC.Levels)-1] } } @@ -162,50 +169,6 @@ func NewLoadPoint(log *util.Logger) *LoadPoint { return lp } -// GetMode returns loadpoint charge mode -func (lp *LoadPoint) GetMode() api.ChargeMode { - lp.Lock() - defer lp.Unlock() - return lp.Mode -} - -// SetMode sets loadpoint charge mode -func (lp *LoadPoint) SetMode(mode api.ChargeMode) { - lp.Lock() - defer lp.Unlock() - - lp.log.INFO.Printf("set charge mode: %s", string(mode)) - - // apply immediately - if lp.Mode != mode { - lp.Mode = mode - lp.publish("mode", mode) - lp.requestUpdate() - } -} - -// GetTargetSoC returns loadpoint charge targetSoC -func (lp *LoadPoint) GetTargetSoC() int { - lp.Lock() - defer lp.Unlock() - return lp.TargetSoC -} - -// SetTargetSoC sets loadpoint charge targetSoC -func (lp *LoadPoint) SetTargetSoC(targetSoC int) { - lp.Lock() - defer lp.Unlock() - - lp.log.INFO.Println("set target soc:", targetSoC) - - // apply immediately - if lp.TargetSoC != targetSoC { - lp.TargetSoC = targetSoC - lp.publish("targetSoC", targetSoC) - lp.requestUpdate() - } -} - // requestUpdate requests site to update this loadpoint func (lp *LoadPoint) requestUpdate() { select { @@ -309,7 +272,7 @@ func (lp *LoadPoint) evVehicleDisconnectHandler() { lp.SetMode(lp.OnDisconnect.Mode) } if lp.OnDisconnect.TargetSoC != 0 { - lp.SetTargetSoC(lp.OnDisconnect.TargetSoC) + _ = lp.SetTargetSoC(lp.OnDisconnect.TargetSoC) } } @@ -364,7 +327,8 @@ func (lp *LoadPoint) Prepare(uiChan chan<- util.Param, pushChan chan<- push.Even // publish initial values lp.Lock() lp.publish("mode", lp.Mode) - lp.publish("targetSoC", lp.TargetSoC) + lp.publish("targetSoC", lp.SoC.Target) + lp.publish("minSoC", lp.SoC.Min) lp.Unlock() // prepare charger status @@ -376,10 +340,20 @@ func (lp *LoadPoint) connected() bool { return lp.status == api.StatusB || lp.status == api.StatusC } -// targetSocReached checks if targetSoC configured and reached -func (lp *LoadPoint) targetSocReached(socCharge, targetSoC float64) bool { - // check for vehicle != nil is not necessary as socCharge would be zero then - return targetSoC > 0 && targetSoC < 100 && socCharge >= targetSoC +// targetSocReached checks if target is configured and reached. +// If vehicle is not configured this will always return false +func (lp *LoadPoint) targetSocReached() bool { + return lp.vehicle != nil && + lp.SoC.Target > 0 && + lp.socCharge >= float64(lp.SoC.Target) +} + +// minSocNotReached checks if minimum is configured and not reached. +// If vehicle is not configured this will always return true +func (lp *LoadPoint) minSocNotReached() bool { + return lp.vehicle != nil && + lp.SoC.Min > 0 && + lp.socCharge < float64(lp.SoC.Min) } // climateActive checks if vehicle has active climate request @@ -481,8 +455,13 @@ func (lp *LoadPoint) detectPhases() { } } -// maxCurrent calculates the maximum target current for PV mode -func (lp *LoadPoint) maxCurrent(mode api.ChargeMode, sitePower float64) int64 { +// pvDisableTimer puts the pv enable/disable timer into elapsed state +func (lp *LoadPoint) pvDisableTimer() { + lp.pvTimer = time.Now().Add(-lp.Disable.Delay) +} + +// pvMaxCurrent calculates the maximum target current for PV mode +func (lp *LoadPoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64) int64 { // calculate target charge current from delta power and actual current effectiveCurrent := lp.handler.TargetCurrent() if lp.status != api.StatusC { @@ -624,11 +603,11 @@ func (lp *LoadPoint) publishSoC() { chargeEstimate := time.Duration(-1) if lp.charging { - chargeEstimate = lp.socEstimator.RemainingChargeDuration(lp.chargePower, lp.TargetSoC) + chargeEstimate = lp.socEstimator.RemainingChargeDuration(lp.chargePower, lp.SoC.Target) } lp.publish("chargeEstimate", chargeEstimate) - chargeRemainingEnergy := 1e3 * lp.socEstimator.RemainingChargeEnergy(lp.TargetSoC) + chargeRemainingEnergy := 1e3 * lp.socEstimator.RemainingChargeEnergy(lp.SoC.Target) lp.publish("chargeRemainingEnergy", chargeRemainingEnergy) return @@ -684,8 +663,8 @@ func (lp *LoadPoint) Update(sitePower float64) { // https://github.com/andig/evcc/issues/105 err = lp.handler.Ramp(0) - case lp.targetSocReached(lp.socCharge, float64(lp.TargetSoC)): - var targetCurrent int64 + case lp.targetSocReached(): + var targetCurrent int64 // zero disables if lp.climateActive() { targetCurrent = lp.MinCurrent } @@ -694,11 +673,15 @@ func (lp *LoadPoint) Update(sitePower float64) { case mode == api.ModeOff: err = lp.handler.Ramp(0, true) + case lp.minSocNotReached(): + err = lp.handler.Ramp(lp.MaxCurrent, true) + lp.pvDisableTimer() // let PV mode disable immediately afterwards + case mode == api.ModeNow: err = lp.handler.Ramp(lp.MaxCurrent, true) case mode == api.ModeMinPV || mode == api.ModePV: - targetCurrent := lp.maxCurrent(mode, sitePower) + targetCurrent := lp.pvMaxCurrent(mode, sitePower) lp.log.DEBUG.Printf("target charge current: %dA", targetCurrent) var required bool // false diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go new file mode 100644 index 000000000..835f51858 --- /dev/null +++ b/core/loadpoint_api.go @@ -0,0 +1,136 @@ +package core + +import ( + "github.com/andig/evcc/api" + "github.com/andig/evcc/core/wrapper" +) + +// LoadPointAPI is the external loadpoint API +type LoadPointAPI interface { + Name() string + HasChargeMeter() bool + LoadPointSettingsAPI + LoadPointEnergyAPI +} + +// LoadPointSettingsAPI is the getter/setter part of the external loadpoint API +type LoadPointSettingsAPI interface { + GetMode() api.ChargeMode + SetMode(api.ChargeMode) + GetTargetSoC() int + SetTargetSoC(int) error + GetMinSoC() int + SetMinSoC(int) error +} + +// LoadPointEnergyAPI is the external loadpoint API +type LoadPointEnergyAPI interface { + GetMinCurrent() int64 + GetMaxCurrent() int64 + GetMinPower() int64 + GetMaxPower() int64 +} + +// GetMode returns loadpoint charge mode +func (lp *LoadPoint) GetMode() api.ChargeMode { + lp.Lock() + defer lp.Unlock() + return lp.Mode +} + +// SetMode sets loadpoint charge mode +func (lp *LoadPoint) SetMode(mode api.ChargeMode) { + lp.Lock() + defer lp.Unlock() + + lp.log.INFO.Printf("set charge mode: %s", string(mode)) + + // apply immediately + if lp.Mode != mode { + lp.Mode = mode + lp.publish("mode", mode) + lp.requestUpdate() + } +} + +// GetTargetSoC returns loadpoint charge target soc +func (lp *LoadPoint) GetTargetSoC() int { + lp.Lock() + defer lp.Unlock() + return lp.SoC.Target +} + +// SetTargetSoC sets loadpoint charge target soc +func (lp *LoadPoint) SetTargetSoC(soc int) error { + if lp.vehicle == nil { + return api.ErrNotAvailable + } + + lp.Lock() + defer lp.Unlock() + + lp.log.INFO.Println("set target soc:", soc) + + // apply immediately + if lp.SoC.Target != soc { + lp.SoC.Target = soc + lp.publish("targetSoC", soc) + lp.requestUpdate() + } + + return nil +} + +// GetMinSoC returns loadpoint charge minimum soc +func (lp *LoadPoint) GetMinSoC() int { + lp.Lock() + defer lp.Unlock() + return lp.SoC.Min +} + +// SetMinSoC sets loadpoint charge minimum soc +func (lp *LoadPoint) SetMinSoC(soc int) error { + if lp.vehicle == nil { + return api.ErrNotAvailable + } + + lp.Lock() + defer lp.Unlock() + + lp.log.INFO.Println("set min soc:", soc) + + // apply immediately + if lp.SoC.Min != soc { + lp.SoC.Min = soc + lp.publish("minSoC", soc) + lp.requestUpdate() + } + + return nil +} + +// HasChargeMeter determines if a physical charge meter is attached +func (lp *LoadPoint) HasChargeMeter() bool { + _, isWrapped := lp.chargeMeter.(*wrapper.ChargeMeter) + return lp.chargeMeter != nil && !isWrapped +} + +// GetMinCurrent returns the minimal loadpoint current +func (lp *LoadPoint) GetMinCurrent() int64 { + return lp.MinCurrent +} + +// GetMaxCurrent returns the minimal loadpoint current +func (lp *LoadPoint) GetMaxCurrent() int64 { + return lp.MaxCurrent +} + +// GetMinPower returns the minimal loadpoint power for a single phase +func (lp *LoadPoint) GetMinPower() int64 { + return int64(Voltage) * lp.MinCurrent +} + +// GetMaxPower returns the minimal loadpoint power taking active phases into account +func (lp *LoadPoint) GetMaxPower() int64 { + return int64(Voltage) * lp.Phases * lp.MaxCurrent +} diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go index 849581e9c..ec0dc1a37 100644 --- a/core/loadpoint_test.go +++ b/core/loadpoint_test.go @@ -152,9 +152,9 @@ func TestUpdate(t *testing.T) { log: util.NewLogger("foo"), bus: evbus.New(), clock: clck, - chargeMeter: &Null{}, //silence nil panics - chargeRater: &Null{}, //silence nil panics - chargeTimer: &Null{}, //silence nil panics + chargeMeter: &Null{}, // silence nil panics + chargeRater: &Null{}, // silence nil panics + chargeTimer: &Null{}, // silence nil panics HandlerConfig: HandlerConfig{ MinCurrent: lpMinCurrent, MaxCurrent: lpMaxCurrent, @@ -330,7 +330,7 @@ func TestPVHysteresisForStatusC(t *testing.T) { handler.EXPECT().TargetCurrent().Return(int64(0)) handler.EXPECT().Enabled().Return(tc.enabled) - current := lp.maxCurrent(api.ModePV, se.site) + current := lp.pvMaxCurrent(api.ModePV, se.site) if current != se.current { t.Errorf("step %d: wanted %d, got %d", step, se.current, current) @@ -366,7 +366,7 @@ func TestPVHysteresisForStatusOtherThanC(t *testing.T) { // maxCurrent will read enabled state in PV mode sitePower := -float64(minA*lp.Phases)*Voltage + 1 // 1W below min power - current := lp.maxCurrent(api.ModePV, sitePower) + current := lp.pvMaxCurrent(api.ModePV, sitePower) if current != 0 { t.Errorf("PV mode could not disable charger as expected. Expected 0, got %d", current) @@ -389,24 +389,27 @@ func TestDisableAndEnableAtTargetSoC(t *testing.T) { log: util.NewLogger("foo"), bus: evbus.New(), clock: clock, - chargeMeter: &Null{}, //silence nil panics - chargeRater: &Null{}, //silence nil panics - chargeTimer: &Null{}, //silence nil panics + chargeMeter: &Null{}, // silence nil panics + chargeRater: &Null{}, // silence nil panics + chargeTimer: &Null{}, // silence nil panics HandlerConfig: HandlerConfig{ MinCurrent: lpMinCurrent, MaxCurrent: lpMaxCurrent, }, handler: handler, + vehicle: vehicle, // needed for targetSoC check socEstimator: socEstimator, // instead of vehicle: vehicle, status: api.StatusC, - TargetSoC: 90, + Mode: api.ModeNow, + SoC: SoCConfig{ + Target: 90, + }, } handler.EXPECT().Prepare().Return() attachListeners(t, lp) // charging below target - lp.Mode = api.ModeNow handler.EXPECT().TargetCurrent().Return(int64(6)) handler.EXPECT().Status().Return(api.StatusC, nil) vehicle.EXPECT().ChargeState().Return(85.0, nil) @@ -454,9 +457,9 @@ func TestSetModeAndSocAtDisconnect(t *testing.T) { log: util.NewLogger("foo"), bus: evbus.New(), clock: clock, - chargeMeter: &Null{}, //silence nil panics - chargeRater: &Null{}, //silence nil panics - chargeTimer: &Null{}, //silence nil panics + chargeMeter: &Null{}, // silence nil panics + chargeRater: &Null{}, // silence nil panics + chargeTimer: &Null{}, // silence nil panics HandlerConfig: HandlerConfig{ MinCurrent: lpMinCurrent, MaxCurrent: lpMaxCurrent, @@ -526,9 +529,9 @@ func TestChargedEnergyAtDisconnect(t *testing.T) { log: util.NewLogger("foo"), bus: evbus.New(), clock: clock, - chargeMeter: &Null{}, //silence nil panics + chargeMeter: &Null{}, // silence nil panics chargeRater: rater, - chargeTimer: &Null{}, //silence nil panics + chargeTimer: &Null{}, // silence nil panics HandlerConfig: HandlerConfig{ MinCurrent: lpMinCurrent, MaxCurrent: lpMaxCurrent, @@ -609,3 +612,81 @@ func TestChargedEnergyAtDisconnect(t *testing.T) { ctrl.Finish() } + +func TestTargetSoC(t *testing.T) { + ctrl := gomock.NewController(t) + vhc := mock.NewMockVehicle(ctrl) + + tc := []struct { + vehicle api.Vehicle + target int + soc float64 + res bool + }{ + {nil, 0, 0, false}, // never reached without vehicle + {nil, 0, 10, false}, // never reached without vehicle + {nil, 80, 0, false}, // never reached without vehicle + {nil, 80, 80, false}, // never reached without vehicle + {nil, 80, 100, false}, // never reached without vehicle + {vhc, 0, 0, false}, // target disabled + {vhc, 0, 10, false}, // target disabled + {vhc, 80, 0, false}, // target not reached + {vhc, 80, 80, true}, // target reached + {vhc, 80, 100, true}, // target reached + } + + for _, tc := range tc { + t.Logf("%+v", tc) + + lp := &LoadPoint{ + vehicle: tc.vehicle, + SoC: SoCConfig{ + Target: tc.target, + }, + socCharge: tc.soc, + } + + if res := lp.targetSocReached(); tc.res != res { + t.Errorf("expected %v, got %v", tc.res, res) + } + } +} + +func TestMinSoC(t *testing.T) { + ctrl := gomock.NewController(t) + vhc := mock.NewMockVehicle(ctrl) + + tc := []struct { + vehicle api.Vehicle + min int + soc float64 + res bool + }{ + {nil, 0, 0, false}, // never reached without vehicle + {nil, 0, 10, false}, // never reached without vehicle + {nil, 80, 0, false}, // never reached without vehicle + {nil, 80, 80, false}, // never reached without vehicle + {nil, 80, 100, false}, // never reached without vehicle + {vhc, 0, 0, false}, // min disabled + {vhc, 0, 10, false}, // min disabled + {vhc, 80, 0, true}, // min not reached + {vhc, 80, 80, false}, // min reached + {vhc, 80, 100, false}, // min reached + } + + for _, tc := range tc { + t.Logf("%+v", tc) + + lp := &LoadPoint{ + vehicle: tc.vehicle, + SoC: SoCConfig{ + Min: tc.min, + }, + socCharge: tc.soc, + } + + if res := lp.minSocNotReached(); tc.res != res { + t.Errorf("expected %v, got %v", tc.res, res) + } + } +} diff --git a/core/site.go b/core/site.go index 465fbf50d..0d0cf653e 100644 --- a/core/site.go +++ b/core/site.go @@ -7,7 +7,6 @@ import ( "time" "github.com/andig/evcc/api" - "github.com/andig/evcc/core/wrapper" "github.com/andig/evcc/push" "github.com/andig/evcc/util" "github.com/avast/retry-go" @@ -126,43 +125,6 @@ type LoadpointConfiguration struct { TargetSoC int `json:"targetSoC"` } -// GetMode gets loadpoint charge mode -func (site *Site) GetMode() api.ChargeMode { - return site.loadpoints[0].GetMode() -} - -// GetTargetSoC gets loadpoint charge targetSoC -func (site *Site) GetTargetSoC() int { - return site.loadpoints[0].GetTargetSoC() -} - -// SetMode sets loadpoint charge mode -func (site *Site) SetMode(mode api.ChargeMode) { - site.log.INFO.Printf("set global charge mode: %s", string(mode)) - for _, lp := range site.loadpoints { - lp.SetMode(mode) - } -} - -// SetTargetSoC sets loadpoint charge targetSoC -func (site *Site) SetTargetSoC(targetSoC int) { - site.log.INFO.Println("set global target soc:", targetSoC) - for _, lp := range site.loadpoints { - lp.SetTargetSoC(targetSoC) - } -} - -// HasChargeMeter determines if a physical charge meter is attached -func (lp *LoadPoint) HasChargeMeter() bool { - _, isWrapped := lp.chargeMeter.(*wrapper.ChargeMeter) - return lp.chargeMeter != nil && !isWrapped -} - -// LoadPoints returns the array of associated loadpoints -func (site *Site) LoadPoints() []*LoadPoint { - return site.loadpoints -} - // Configuration returns meter configuration func (site *Site) Configuration() SiteConfiguration { c := SiteConfiguration{ @@ -187,7 +149,7 @@ func (site *Site) Configuration() SiteConfiguration { lpc.SoCCapacity = lp.vehicle.Capacity() lpc.SoCTitle = lp.vehicle.Title() lpc.SoCLevels = lp.SoC.Levels - lpc.TargetSoC = lp.TargetSoC + lpc.TargetSoC = lp.GetTargetSoC() } c.LoadPoints = append(c.LoadPoints, lpc) diff --git a/core/site_api.go b/core/site_api.go new file mode 100644 index 000000000..ef5ee6606 --- /dev/null +++ b/core/site_api.go @@ -0,0 +1,65 @@ +package core + +import "github.com/andig/evcc/api" + +// SiteAPI is the external site API +type SiteAPI interface { + Healthy() bool + Configuration() SiteConfiguration + LoadPoints() []LoadPointAPI + LoadPointSettingsAPI +} + +// LoadPoints returns the array of associated loadpoints +func (site *Site) LoadPoints() []LoadPointAPI { + res := make([]LoadPointAPI, len(site.loadpoints)) + for id, lp := range site.loadpoints { + res[id] = lp + } + return res +} + +// GetMode gets loadpoint charge mode +func (site *Site) GetMode() api.ChargeMode { + return site.loadpoints[0].GetMode() +} + +// SetMode sets loadpoint charge mode +func (site *Site) SetMode(mode api.ChargeMode) { + site.log.INFO.Printf("set global charge mode: %s", string(mode)) + for _, lp := range site.loadpoints { + lp.SetMode(mode) + } +} + +// GetTargetSoC gets loadpoint charge target soc +func (site *Site) GetTargetSoC() int { + return site.loadpoints[0].GetTargetSoC() +} + +// SetTargetSoC sets loadpoint charge target soc +func (site *Site) SetTargetSoC(soc int) error { + site.log.INFO.Println("set global target soc:", soc) + for _, lp := range site.loadpoints { + if err := lp.SetTargetSoC(soc); err != nil { + return err + } + } + return nil +} + +// GetMinSoC gets loadpoint charge minimum soc +func (site *Site) GetMinSoC() int { + return site.loadpoints[0].GetMinSoC() +} + +// SetMinSoC sets loadpoint charge minimum soc +func (site *Site) SetMinSoC(soc int) error { + site.log.INFO.Println("set global min soc:", soc) + for _, lp := range site.loadpoints { + if err := lp.SetMinSoC(soc); err != nil { + return err + } + } + return nil +} diff --git a/evcc.dist.yaml b/evcc.dist.yaml index 23dce7594..6174e7e83 100644 --- a/evcc.dist.yaml +++ b/evcc.dist.yaml @@ -67,8 +67,9 @@ loadpoints: charge: charge # charge meter vehicle: audi mode: pv - targetSoC: 100 # charge to 100% soc: + min: 0 # immediately charge to 0% regardless of mode unless "off" (disabled) + target: 100 # always charge to 100% alwaysUpdate: false # set true to update vehicle soc even when disconnected estimate: false # set true to interpolate between api updates levels: # target soc levels for UI diff --git a/go.sum b/go.sum index 85d5aceea..083128ee5 100644 --- a/go.sum +++ b/go.sum @@ -19,7 +19,6 @@ github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154Oa github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alvaroloes/enumer v1.1.2 h1:5khqHB33TZy1GWCO/lZwcroBFh7u+0j40T83VUbfAMY= github.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo= github.com/andig/evcc v0.0.0-20200727161511-d58eb15f2dc9/go.mod h1:8HONEC6cC2s4k0u3QL7GIjrYOZYTOKiiXybw0FIJL0A= github.com/andig/evcc-config v0.0.0-20201007090146-de86a8fc8508 h1:Utyo4NnJg7ERtoTxdbmwZkJJ7V97Uw+PdL3G7FgVSL4= @@ -53,13 +52,11 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/deepmap/oapi-codegen v1.3.6 h1:Wj44p9A0V0PJ+AUg0BWdyGcsS1LY18U+0rCuPQgK0+o= github.com/deepmap/oapi-codegen v1.3.6/go.mod h1:aBozjEveG+33xPiP55Iw/XbVkhtZHEGLq3nxlX0+hfU= github.com/deepmap/oapi-codegen v1.3.9 h1:8e3ZfNFzwMDJ5kojta1GGS2+RjzWIThwDWgwe9GqQrE= github.com/deepmap/oapi-codegen v1.3.9/go.mod h1:suMvK7+rKlx3+tpa8ByptmvoXbAV70wERKTOGH3hLp0= @@ -113,7 +110,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -134,11 +130,9 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= @@ -168,7 +162,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -192,7 +185,6 @@ github.com/influxdata/influxdb-client-go v1.4.0 h1:+KavOkwhLClHFfYcJMHHnTL5CZQhX github.com/influxdata/influxdb-client-go v1.4.0/go.mod h1:S+oZsPivqbcP1S9ur+T+QqXvrYS3NCZeMQtBoH4D1dw= github.com/influxdata/influxdb-client-go/v2 v2.1.0 h1:zP2I/Vt+zHpF/8vtdkxI/qr25OgPPec3T6SPy6GesYY= github.com/influxdata/influxdb-client-go/v2 v2.1.0/go.mod h1:4m7g+qe8nHTRh+ptGHaR8FGB9yV3pxU93CKDb5bbTfY= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= @@ -221,7 +213,6 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -229,7 +220,6 @@ github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jv github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -271,7 +261,6 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -295,13 +284,11 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1 h1:/I3lTljEEDNYLho3/FUB7iD/oc2cEFgVmbHzV+O0PtU= github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ= github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk= github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9 h1:4lfz0keanz7/gAlvJ7lAe9zmE08HXxifBZJC0AdeGKo= github.com/pbnjay/strptime v0.0.0-20140226051138-5c05b0d668c9/go.mod h1:6Hr+C/olSdkdL3z68MlyXWzwhvwmwN7KuUFXGb3PoOk= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= @@ -319,25 +306,20 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= @@ -346,7 +328,6 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU= github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= @@ -364,7 +345,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -373,9 +353,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.0 h1:DMOzIV76tmoDNE9pX6RSN0aDtCYeCg5VueieJaAo1uw= github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -395,14 +373,12 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/volkszaehler/mbmd v0.0.0-20200717102329-c4d965bd1eac h1:LnvR04M0HuzjTbRzMpE5jRHcNu46CvG1FOtMcZAX8C4= github.com/volkszaehler/mbmd v0.0.0-20200717102329-c4d965bd1eac/go.mod h1:sldLyJCKVO9GQkit55U5WPNr9U4KmUn1SCdqFN9Gjb4= github.com/volkszaehler/mbmd v0.0.0-20200831092453-b235d6a65b21 h1:KiwFeqxuXkdnv67iuR2qOiyevfPrO4Ma00Ch+J3Tr5I= github.com/volkszaehler/mbmd v0.0.0-20200831092453-b235d6a65b21/go.mod h1:PQGaIeLLHhwwqQ56gnHJn9xSlwPoI1OeIXyGc/eLP0Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32 h1:5tjfNdR2ki3yYQ842+eX2sQHeiwpKJ0RnHO4IYOc4V8= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -440,7 +416,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -470,7 +445,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -513,7 +487,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -555,7 +528,6 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -570,10 +542,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -581,7 +551,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -593,7 +562,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/hems/semp/semp.go b/hems/semp/semp.go index a50cccdbf..59e42ea2e 100644 --- a/hems/semp/semp.go +++ b/hems/semp/semp.go @@ -50,7 +50,7 @@ type SEMP struct { // site is the minimal interface for accessing site methods type site interface { Configuration() core.SiteConfiguration - LoadPoints() []*core.LoadPoint + LoadPoints() []core.LoadPointAPI } // New generates SEMP Gateway listening at /semp endpoint @@ -316,7 +316,7 @@ func (s *SEMP) cacheGet(id int, key string) (res util.Param, err error) { return res, err } -func (s *SEMP) deviceInfo(id int, lp *core.LoadPoint) DeviceInfo { +func (s *SEMP) deviceInfo(id int, lp core.LoadPointAPI) DeviceInfo { method := MethodEstimation if lp.HasChargeMeter() { method = MethodMeasurement @@ -336,8 +336,8 @@ func (s *SEMP) deviceInfo(id int, lp *core.LoadPoint) DeviceInfo { OptionalEnergy: true, }, Characteristics: Characteristics{ - MinPowerConsumption: 230 * int(lp.MinCurrent), - MaxPowerConsumption: 230 * int(lp.Phases*lp.MaxCurrent), + MinPowerConsumption: int(lp.GetMinPower()), + MaxPowerConsumption: int(lp.GetMaxPower()), }, } @@ -352,7 +352,7 @@ func (s *SEMP) allDeviceInfo() (res []DeviceInfo) { return res } -func (s *SEMP) deviceStatus(id int, lp *core.LoadPoint) DeviceStatus { +func (s *SEMP) deviceStatus(id int, lp core.LoadPointAPI) DeviceStatus { var chargePower float64 if chargePowerP, err := s.cacheGet(id, "chargePower"); err == nil { chargePower = chargePowerP.Val.(float64) @@ -386,7 +386,7 @@ func (s *SEMP) allDeviceStatus() (res []DeviceStatus) { return res } -func (s *SEMP) planningRequest(id int, lp *core.LoadPoint) (res PlanningRequest) { +func (s *SEMP) planningRequest(id int, lp core.LoadPointAPI) (res PlanningRequest) { mode := api.ModeOff if modeP, err := s.cacheGet(id, "mode"); err == nil { mode = api.ChargeMode(modeP.Val.(string)) diff --git a/server/assets.go b/server/assets.go index 5d83b9661..da7ea5b96 100644 --- a/server/assets.go +++ b/server/assets.go @@ -3561,7 +3561,7 @@ oJE6K0E084u3ea7ne0X73qsc2P2KOB+ngKecxITsMAgTVJfqMwAA//+21HeD5gEAAA== "/index.html": { name: "index.html", local: "assets/index.html", - size: 19874, + size: 20017, modtime: 1566640112, compressed: ` H4sIAAAAAAAC/8R83XLbOJbwvZ/ihKmOkulQsp3fT6H0VbeTnZmdTjbVTidVu7UXIHkkog0CHACU7GRc @@ -3623,85 +3623,86 @@ xsTGLHt0fy7O9qrdBIdHGOpMIoZ/LYk0Fdth8dLWdV/QtC12c2ts+eIuM3yzS8+3WorqpeyQWqynthle U3d6JkG7v1KbbV/F3b9qeY99Ppo2Vfe4en5yOprea45cbzzJGn6/qaf+zFX1ousw0+/oAQy7+41uDX5L Ex/CctfkB5nVR6Ph/rQTdfvM1xd/v2Xs7W7CDlP/7xZv+5V/qH4by8t2DP7Oq8wuX2o14tvLza7WfL/s TtTY/Zen4aZxi1aY0YTh0DLlh2+yWD4dWCwH9jo65VowfyvSUrWTbTMsYs3DpRRlEar894qOm0gGDx5A -fwRtSWq3+iiytrCNQOuV9TeMt6H03FP/9MbfP6UAuiOKd1UY39FGze5m5gekSpd8ua+jMQ2NYQQl0aXE -kJkKkYUn7R4qYfYItpzNRhkSTfly1DqmdDD6jC6zGj8yhf1EEiHYDYlkKHNhN8/DjLDFAUQEH31Fr+v2 -5m/R6rYBv7LT3e0Ef0TmTrXeRrT0DUe5vLqNcBvQT9ku+b5awNekRHmweOeZkPq1fwnUYrh6uBG2LuqH -hG4h29JAA+FXKqAVh0okezTyIyr9Gam+A6W8UdrGyp0ppYHwzvcdzEI9lFHrRRI2y6U72WgPAWdUI6iM -pGK9WTgiey6pgcHCilIzyrHOPVafkFJFYoYpVHdN7OWONZoqP4wFS+uDPW7cG8CWMW7/39A6mOpWS0cS -TVfoSpzZSCwWo8f2Ksv0nidYtXCRvR7i1WjvhwROhFkgFgtfCCSMJhfNOuihRfkomJ9rUdw9v1ysRwcw -yF1F38+gwWEYFAsh9d2zmFNerEb1rm6x2s+uBRlm2GF8VJ8Ia/QQaUi54Q3SkC29Q76lvNUttOf7wost -PaSdD9/C+48boDvWyE3VsUsXByiiLumaIg3pIU9rPbwrZb8WbveaTSS/f4q5ZXKpOodbmt6/CjUNQvUa -1LUP/yZf44KUTNcOYp/PAvvHHySmqXtH2nUe05jMHNJ2gnrwgKYz+5po2JEcNe9Pntq2S52LM8f2I7Mk -2U/X1998tel9l7tvU9t1bVXTr0Tywff9/+JPLwbNo6YNwOrCY30qPEN37vv0aXH5qnFb8OT4+JtX0DjB -f/K8uOypCZo3KAOQwp4098/so05410v7qHX3UmlJC0xHU2gs5JQvHw/Mr+5q7gKoNsBGU/BvzhLBOSYa -08YsW1soTARP7fuTwbnXm77Xqe6Lvbc/BSUSd9/+tTt+88k8/nb0jVltvnzpjn40jtV3EPgW3uIO8N7g -RPC9MIQoO6lTmWMpfAqF3Ze5tGfDm5vAW1fLshOPqn3wthf0te+CU5RQOWb4uDoRFdozj/54U+vclD23 -Y68aHA28WHjaPS5yyMWqmx2f6dtZ72UnezZIfuvQyY4y/NTuwrfugenP/vjVtHNcvac9HTxP8Y///e+t -WzC7kNgjDg/ylKhs6/D7reR4/7Ey8o3FqI8Z/NOFqN6X39Yg7Vfzv4k4Wz56+EGjeomtHrXPF9k7O5t9 -pdG39nrO8Gu/oejcE5/bEVrTHNjXLna/ExvgMNtxTaEvZG/iJ9A8NTnduqm2w082sromdthVbu0se195 -7pLrfUYU8sNEapmuMIDq+rq4Q2beUj6Gcy1FfguGcsrPSimR6+vr7+6SKXL5FUyRywOZ+q2d2+6yZ53A -3L7r8wSy08E4PyDWzb+qKOgcZ2+x1/dgl8z9Vtsylt8YQcam3ZP7hxmsUXT/47/+p/OabZD1g3n7CynI -Z6p//UXfnr8z4n6D5Pr6wu6P7mev6xq7Dxr0H6FeCKFRNu4dPXV3uwr/grOIm68C/cPNj6DsL2J7Xzr2 -KLZ1y29oY7nvavoN7mi3fsJg3vzWuk0NEEV0ARyhmgDjM5HnVM/nDw+km9j5E3tRz0FagtUXe38pipCn -86YB23cpdxc/Vi+ye1JnUHUPElFcvYLT49PjYTrbfY7zj0aDU/+CUFGMf1b/P4rGH8jSStfwqon7mZZo -4n6R7f8CAAD//y5pd6WiTQAA +fwRtSWq3+iiytrCNQOuV9TeMt6H03FP/9MbfP6UAuiOKd1UY39FGze5m5gekSpd82d/RdI4OVB2N72Ug +Z+FJ7bKUn4uz7xJNV9g6mjTYJBmMRjaURJcSQ2aKTo8RmcJGb5Ywe7RbzmajDImmfDm6HY2MLrMDiCRC +sBsSyVDmwm7KhxlhiwOICD76ih7a7fnfooVuA35lB73buf6IzJ2WvY1o6RuOcnl1G+E2oJ+yXfJ9tYCv +SYnyYPHOMyH1a/9yqcVw9XAjbN0sDAndQralgQbCr1RAa49EiWSPRn5EpT8j1XeglDdK21i5M6U0EN75 +foYpAIYydb34wmYZdicm7eHijGoElZFUrDcLUmTPOzUwWFhRakY51rnH6hNSqkjMMIXqDou9NLJG0z2E +sWBpfWDIjXsD2PLIvVcwtA6mutUqEpv0Xek0G4nFYvTYXpGZ3vMEq9YwstdOvBrtvZPAiTALxGLhC4yE +0eSiWV89tCgfBfNzLYq755eL9egABrnrFPoZNDgMg2IhpL57FnPKi9Wo3i0uVvvZtSDDDDuMj+qTZo3e +JA0pN7xBGrKld8i3lLe6kPZ8X9CxpYe08+FbeP9xA3THGrmpOnbp4gBF1KViU6QhPeRprYd3pezXwu1e +34nk908xt0wuVUdyS9P7V6ym8aher7q25N/ka1yQkunaQezzWWD/+APKNHXvXrvOYxqemUPaTlAPHtB0 +Zl8/DTuSo+b9yVPbdqlzcebYfmSWJPvp+vqbrza97573bZa7brDaTFAi+eD3E/7Fn4oMmkdYG4DVRcr6 +tHmG7jz56dPi8lXjFuLJ8fE3r6BxM+DkeXHZUxM0b2YGIIU9we6f2Ued8K6X9lHrTqfSkhaYjqbQWMgp +Xz4emF/dAd0FUG2sjabg38glgnNMNKaNWba2UJgIntr3Mrvmbq4IbehW00wX3+yNKrDrTRvuNP7F/ozA +FJRI3PX/1+400Cfz+NvRN2aR+vKlO/rR+GPfueRbOJk7T3yDA8r3whCi7KTOgI6l8CkUdpvo0h5Vb+5J +b910y048qvY54F7Q174pT1FC5c/h4+qAVmiPYPrTVq1jXPYYkb35cDTwnuNp9/TKIfe8bnaap2+jv5ed +7Nkg+a0zMDuq91P7UqB1LU1/9qfBpp3T8z1d7eDxjn/8739vXcrZhcSeuHiQp0RlW2fxbyXH+4+VkW8s +Rn3q4Z8uRPX6/rYGaZ8U+E3E2fLRw8891Stz9ah93MleIdpsc42+tbeFht9CDkXnnvjcjtCa5sA2e7H7 +Fd0Ah9mOWxN9IXsTP4HmIc7p1sW5HX6ykdX1vsOucmtn2fsGdpdc7zOikB8mUst0hQFU19fFHTLzlvIx +nGsp8lswlFN+VkqJXF9ff3eXTJHLr2CKXB7I1G/t3HbTP+sE5vbVoyeQnQ7G+QGxbv5VRUHndH2Lvb4H +u2Tut9qWsfx+CjI27V4kOMxgjVr9H//1P523foOsH8zbX0hBPlP96y/69vydEfeTKNfXF3ZbdT97XdfY +fe6h/0T3QgiNsnEN6qm7alb4961F3Hwz6R9ufpNlfxHb+w60R7GtS4dD+9F9N+VvcGW89YsK8+a31uVu +gCiiC+AI1QQYn4k8p3o+f3gg3cTOn9h7gw7SEqy+2OtUUYQ8nTcN2L7aubv4sXqR3YNDg6p7kIji6hWc +Hp8eD9PZ7nOcfzQanPoHjYpi/LP6/1E0/kCWVrqGV03cr8ZEE/cDcf8XAAD//2pP41YxTgAA `, }, "/js/app.js": { name: "app.js", local: "assets/js/app.js", - size: 9717, + size: 9973, modtime: 1566640112, compressed: ` -H4sIAAAAAAAC/7xa747bNhL/7qeYsG0sd7WSt0Xbg3e1QS/toT2kzSHbPx8WC5SWxha7EimQlJ1F1sA9 -xD3hPcmBpP5Ldja5w33J2iTn/8xvhnTCEOhbJhQo1GUBe6ZT0Kg0bGiWrWl8P4sFVxoyEUMEe8YTsQ8y -EVPNBA9SiZuA8QTfvt54JNW6WIWh2c1SoXSIuzgOqVKoVUgWEEURLOEFvJsBFFJoEYtsBY6M+DMAQ8Rp -jisgDRO7UQipV0C+WX6zJP7sAKuhJpezmTUjSHBDy0yrYE0V/vrmFURG9aAWB2dAwpDAmV2t5cEZePaU -kBpeAFnVB+zCCghZWEJaMHI5FJQiTVCqoBBK385fCq6R6/NfHgqc30EEc1oUGXNahn8qweeXs1kYzsIQ -fmJvGVfmyyxDDRshc6o1SoisixKq6Qo2JY8NLXgLuwogUZeSV18ANrl+xXKmV3CxXPrt4ndsy7RawYVb -O8wADuZjjjoViVrV3ETJk1aMx8vch0JizBQTfNGI2VEJxqcQwcUSPv+8PXNZnaj08n6iOg0sW8MMPrdk -Cwjd30CLv7G3mHitDMfg4PTc5Lpr9I5mrQ5sYxdsIpU8wQ3jmMDjI9SLvMw6xxuVlrWKh8aYDCKwitK1 -skIGVpgT1xHolKmg9jC8cN+dbeZECBf4pd+cci5fwGpwzoflyMhfOTtuaKVET8Epdcg9sek5ZP5dKW3C -dQUkfT8mcGWq8fEREjjuufm///mv+dh7EhTGgicKIiBLUy1eAp/B18vGjeZMznipsXPG2rPJhJBeAqE5 -PiZKRSktiaEYEHz59bI9bIywh4MM+VancAVfdC1oGFnZ9tvQkMpId/KsqvtK60CVa6Wld/7FotmqjO5s -DR1/kwr5f/B+69kP8ukpd5pzOh8512TdRVe1YVCr75fNAZ1D9F6XNkYBZgo77J+SW5WMWpP3RWcU79rM -I2EbVubHhW4ybk8JxHHXV6xJSo4YRvIuEhxmh7rT3GiqsWk0SguJVZNRZqfuBXRHWUbXGf6GUtkENtY5 -L2WCJoVg3LSU2zu/7iZlkVgGTf/I1bbW2cjSVG5RQwVdVppT0ZiZq23Q8IVnXVRv7d6nLEPwWvqWpC39 -CHq8ul6bJixKlXrvDqMUmdC3Q3bbk3JXOXtm/7xe/4mxDu7xQVknBBshv6dx6rWZdN/PJP1QoNiAFlRp -dXt/Z1sYqY+TnhH1Ge9djkrRLa6Mxbf3d4fFsVL6rcRAofacST7c+xXJwGjnBBtN1kv9ZuJwE88WtUdi -wTdsSxaBTpF7U1EHM8iAZ4LPouUlsCvjNDPMjAN3CezsrGunoTJHIar9SZViWytgyOOW3fnwrk3MFTA4 -dEHCRNDlp2dIO2ZXHxLMUCNM8K6PdnnUx2rQD9aMJzYvF4sgpjpOPRemAKUU0nl1dmjGvR+QSr1Gqm0h -VgOsQv0j1yh3NPMGfu94PUWa6ZTUYpqDElU7FaqgSg0DnDcodyih5E1NV9DQVdHSz1wGHHz4amlhqFL3 -26KA5/BGlJrxrVXZ3QRyyjhENrtikReCI9fe3KzOfauLxrzILCjMPzHL5/XC3B9NtceG2gqWLFK5KgQI -Q9hmYk0zt3tyqnVpWkpMjs5YYWgTNcEdZqLIkWsoSlkIhWo0bsL86ip42fC8vp5P4LKWJQ7hxLB4xtTP -9GevoFLhj1xbLRbw/Dn0VuAalk9kWm1taKbwsnXDzISxCpKzfxSmunyHgSKfuI0mVOS/CtVkpCqQ6Sgp -RTVJ3M4A3kFBdboCEhIfGo1XLt1siNsTjRWdc5XFB3921xdgrlMc98YTb+x3z2juZPtGVWWav82FP9zq -qtr9YwaQMX7//Vsa629jzXb4MqNKrYBQ+41Y+r8KoZWWtIArTnfXXQtpUbTSrVw0N95PaFFYDzsFK3my -9XmdxHlBYwPIJtJNy40lUt1P7EWb9kpDKbN/UElzVcn+9c2rG6QyTt2qN7zFK7tZwZpFvEoyRC2vGv7t -Bll0o+nw4mUdC9UBC4c2kz5wW2RoNNOYmzKuhrNYlCa6S//Y9ZUyhV1X9DqRu1aXPIGoWy79Zm0ttmLf -07Mdv7weDizJ7f1d23HsTBOYtm4ww/QL+/n5c8hbbHYb1dcuc2hU7RZ9W/YHd9HsDYrPLElvPlfbgCW1 -jtZ9Z2eX46GgscCvSOzf4WjggmCbxVEnN3ZGQOxJ0uueNkKWostyTyV/Ekdz8BjD3hgvMRe746lgDHf9 -fsL2RX9sNkk97G8uXuc2a6caXb3f7XWFFIUZl+dG2NwOzbkJyKnitbEjDT+WnJu7TaNywBKn66ceSxaB -Vcebq1Ts54vuhuDePGVJgjxYq6DWeiy0GQic+1rfTE05nZofuGfnLgwTjql2jjmGK02zDBPnnSc+eY1b -Tv3uxYVGdZOKPe+gZtt8Tvr/U2f8pxI3KrCM6tHaetP42PgyFllGC4VDd7oYtQrUdTxyYcMxZQl+IEcH -Yn2WjW17MxzWwEjc3WV4pSOrySSw1xEb+jog8CwCcnUVVHTX18TgmOnsoomvm2CSF/2Ru89iGSyDi3Oa -FSmtOeT0HqEcE05rbLj0+Xbhrhe0NZWLxpseMREjQ0Q7WuGKmeQc5a9ZPpK8Vt+qrO1T7gpum0dcu2zY -lzbdnMZ5mWk2MVK1496pW+41XMDjoxkq6gbdha3pKZhjrCcFOrRp3sUHz+TmIppqXagVgRdA9sp8WJkP -qwaM62GDQQT/m/f1vRow39ejw++4vhHxPWqvlGzhg8JsU/W4mmKvAsFt/zF1UhuMu95jwF4FcSYUem1a -9Ojt5in65tb2C8tRlNozmgSVo324WHZecvqs28vZMeZaPvTGAfvSp8wU//eb1z8HttgMTdC7zTpANEjY -3lMX4+kBwN4dwRvMHL37IPrQ8B/PH8NsGw2iTXpVs4d1ildfhI/U3HmCmrJMHau9avujanBKKGkKa+Ia -1Oz1b0K1OAeqxAfCEvNvsTP/2qomp3DgaTcpzeJ7lD9QnmTYfXpru9cQT1KqfrGvOzfi5TSwj3FFifiV -ufMqA6xGiEHlqf0WdpbDyE91mjg1mtSPqGQSdKryiTOksnn2sLK7tjfZG4YgePZQOcY0KRrrkmbZA1hp -jG+H/aujC+PbgWl9FeE66l+5R4q0v3ieeKaZaGEDOWcRXHQKstO7h4BxGs5pwbpBNp9GgW6zW4Xt4JhY -eDXfDVFvbHavgz+JpD86i6SDE+4lqhCqujfQgnnEHKlw277xG4r+qyB4ElUhuMIj77GGBiKoT7lHOLPY -OOQpb2wDUwaloET8HkMcmRJxxxpD9YHGNNJHFjU7H2OW/SdBpaV4mMTaD6ipw2k8rKH2JC4O4XgKHz8I -jneYsjjDCaHVzvtFDWFRifilK0Gmiow+/M4Snb5n8G3AbwyHjtcILiah1Z098mJ3sVz2Enao5280K3Fa -zzAELgwBCFl/2pnj3ceAgSGPj9OGXPXtyFA7OMUbTXVpf3fbopbIuSb9x40JiO2D4JBPRhPkHSbVjxRD -Xm5S6E/2Y2Y7lOuSDxgOw9ElGj34hyEUKGPk2jnPKIJclNsUVEFj7Dwbte6KTobYmNLLkYuec9utswjI -Z8d+uRuwPXpLcag7LhWzfKROKopqVolpYdvz3UR/eUorcLctzJn2iJs3zSBkz3Qf5KdUNwg71lyJ+FiB -2/ONxj6QzM4l0zXv9l7L79z/y5kcP5q0c4frn2+bQsl6c080Xe+3d5cmjxKmzCU18U1Blgrhy6UPXy19 -+MvSdvUjPz23Yi5P/oAx2cs+PhTugsA4s+P4fwIAAP//2bCXhvUlAAA= +H4sIAAAAAAAC/8xabW/cNvJ/v59iwrZZbb2W1i3a/rG2HPTv9NAe0uYQt+kLw0BpaXbFWiIFktqNES9w +H+I+4X2SA0k9P2yc3OFwb+KVODOcx98MqQQB0HdMKFCoixz2TCegUWnY0DS9o9H9LBJcaUhFBCHsGY/F +3k9FRDUT3E8kbnzGY3z3euORROt8HQRmNU2E0gHuoiigSqFWAVlAGIawghfwfgaQS6FFJNI1ODaynAEY +Jk4zXAOphdiFXEi9BvLd6rsVWc4OsO5rcj6bWTP8GDe0SLXy76jC3968gtCo7lfbwQmQICBwYt9W+8EJ +eJZKSA0vgKwrAvtiDYQsLCPNGTnvb5QgjVEqPxdK38yvBNfI9emvDznObyGEOc3zlDktgz+V4PPz2SwI +ZkEAP7N3jCvzMEtRw0bIjGqNEkLrophquoZNwSPDC97CvgWQqAvJyweATaZfsYzpNZytVsvm5Uu2ZVqt +4cy9O8wADuZnhjoRsVpX0kTB42YbjxfZEnKJEVNM8EW9zY5KMD6FEM5W8OWXDc15SVHq5f1MdeJbsUYY +fGnZFhC4v74Wf2HvMPaaPZyAg9Nzk+m20TuaNjqwjX1hE6ngMW4YxxgeH6F6yYu0RV6rtKpUPNTGpBCC +VZTeKbtJzwpDcRmCTpjyKw/DC/fsbDMUAZzh18uayrl8Aese3RJWAyN/42za0FKJjoJj6pB7YtOzL/xl +IW3CtTeIu36M4cJU4+MjxDDtufk///6P+dB7EhRGgscKQiArUy1eDF/At6vajYYmY7zQ2KKx9mxSIaQX +Q2DIh0yJKKRlMRw9hq+/XTXExghL7KfItzqBC/iqbUEtyO5tn/qGlEY6ypOy7kutfVXcKS29068W9VJp +dGup7/jrRMj/gvcbz36UT4+509DpbOBck3VnbdX6QS2fz2sCnUH4QZfWRgGmClvin5Jb5R6VJh+KziDe +lZkTYetX5qeFbjRuTwnEtOtL0SQhE4aRrI0Eh9mh6jTXmmqsG43SQmLZZJRZqXoB3VGW0rsU36JUNoGN +dc5LqaBxLhg3LeXmdll1kyKPrYC6f2RqW+ls9tJUblFDCV12N6eiMTNTW7+WC8/aqN7YvU9YiuA1/A1L +U/ohdGS1vTbOmBcq8d4fBikyom+L7aazy23p7Jn98/ruT4y0f48PyjrB3wj5A40Sr8mk+24m6YccxQa0 +oEqrm/tb28JIRU46RlQ03vsMlaJbXBuLb+5vD4upUnpboK9Qe86kJdwvS5ae0c4JNpqsk/r1xOEmni1q +j0SCb9iWLHydIPfGog5mkAHPBJ+Fq3NgF8ZpZpgZBu4c2MlJ207DZUghrPxJlWJbu0Ffxg27XcL7JjHX +wODQBgkTQZefnmFtmV3+iDFFjTAiuyJty6jIKtD37xiPbV4uFn5EdZR4Lkw+Simk8+rsUI97PyKV+g6p +toVYDrAK9U9co9zR1Ov5veX1BGmqE1JtUxNKVM1UqPwyNQxwXqPcoYSC1zVdQkNbRcs/cxlwWMI3KwtD +pbrf5zk8hzei0IxvrcruJJBRxiG02RWJLBccufbm5u18aXXRmOWpBYX5Z+b1afVivhxMtVNDbQlLFqlc +FQIEAWxTcUdTt3p0qnVpWkiMJ2esILCJGuMOU5FnyDXkhcyFQjUYN2F+ceFf1TIvL+cjuKxlgX04MSKe +MfUL/cXLqVT4E9dWiwU8fw6dN3AJqycKLZc2NFV43rhhZsJYBsnZPwhTVb79QJHP3EIdKvJvhWo0UiXI +tJSUopwkbmYA7yGnOlkDCcgSao3XLt1siBuK2ooWXWnxYTm77W5gjlMc98YTb+yzZzR3ey+Nqso0f5sL +f7i363L1jxlAyvj9D+9opL+PNNvhVUqVWgOh9olY/v8XQistaQ4XnO4u2xbSPG92t/uiOfF+RvPcetgp +WO4nG59XSZzlNDKAbCJdt9xIItXdxF40aa80FDL9G5U0U+Xev715dY1URol76/VP8coulrBmEa/cGcJG +VgX/doEs2tF0eHFVxUK1wMKhzagP3BLpG800ZqaMy+EsEoWJ7mo5dXylTGHbFZ1O5I7VBY8hbJdLt1lb +i+22H+jZTl5WDQeW5eb+tuk4dqbxTVs3mGH6hf39/DlkDTa7hfKxLRxqVdtF35T9wR00O4PiM8vSmc/V +1mdxpaN138nJ+XAoqC1Yliz2b380cEGwzWLSybWdIRBLSTrd00bIcrRF7qnkT5JoCKcEdsZ4iZnYTaeC +Mdz1+xHbF92x2SR1v7+5eJ3arB1rdNV6u9flUuRmXJ6bzeZ2aM5MQI4Vr40dqeWx+NScbWqVfRY7XT/3 +WLzwrTreXCViP1+0FwT35gmLY+T+nfIrrYeb1gOBc1/jm7Epp1XzPffs3IFhxDHlypRjuNI0TTF23nni +ldew5VT3XlxoVNeJ2PMWajbN56j/P3fGfy5xo3wrqBqtrTeNj40vI5GmNFfYd6eLUaNAVccDF9YSExbj +R0p0INYVWdu2N8NhBYzEnV36RzqyHk0Cexyxoa8CAs9CIBcXfsl3eUkMjpnOLur4ugkmftEdubsiVv7K +PzulaZ7QSkJG7xGKIeO4xkZKV24b7jpBu6NyUXvTIyZipI9okxWumEnOQf6a1xPJa/Uty9pe5a7hpr7E +ta+N+MKmm9M4K1LNRkaqZtw7dsq9hDN4fDRDRdWg27A1PgVzjPTohg5t6nvx3jW5OYgmWudqTeAFkL0y +P9bmx7oG42rYYBDCf+Z+fa96wvfV6PA73l2L6B61V0i2WILCdFP2uIpjr3zBbf8xdVIZjLvOZcBe+VEq +FHpNWnT47eIx/vrU9ivLUBTaM5r4paOXcLZq3eR0RTeHsynhWj50xgF706fMFP/X69e/+LbYDI/fOc06 +QDRI2JxTF8PpAcCeHcHrzRyd8yAuoZY/nD/62TYYROv0KmcP6xSvOghP1NxpjJqyVE3VXrn8STU4timp +C2vkGFSvdU9C1XYOVMkSCIvNv/nO/GurmhzDgaedpDSL7lH+SHmcYvvqrelefTxJqPrV3u5ci6txYB/i +ihLRK3PmVQZYzSYGlcfWG9hZ9SM/1mmixGhSXaKSUdApyydKkcr62sPu3ba9zt4gAMHTh9IxpknRSBc0 +TR/A7sb4tt+/Wrowvu2Z1lURLsPukXugSPPF88g1zUgL6+1zEsJZqyBbvbsPGMfhnOasHWTzaxDoJrtV +0AyOsYVX82yYOmOzux38WcTd0VnELZxwN1G5UOW5gebMI4akxG17x284ureC4ElUueAKJ+5jDQ+EUFG5 +SzjzsnbIU+7Yeqb0SkGJ6AOGODYlopY1husjjal3H1hUr3yKWfafGJWW4mEUaz+ipg7H8bCC2qO42Ifj +MXz8qJGI8Wtx5W5XngphjscA0xC7rmztwcWQ/vz4AEh2mLAoxRHry5UP29w3rtbnJVN5Sh9+Z7FOPjCB +15ZM2tbHrVGMd7QTV4dnq1Wncvp6vqVpMRGNIAAuDAMIWf3aGfL2rUTPkMfHqSCt+rcrDjqvNdWF/QC4 +RS2Rc026tywjWN9F476clMbIW0LKryV9WW5k6R4xhsJ2KO8K3hPYD0ebafDlIQggRxkh1855RhHkotgm +oHIaYev+qnFXeDTExpROjpx1nNssnYRAvpj6hNgTW+bH/06R2q4zrFDzeqI8S45yVotobseT25H++pRW +6E6bmDHtETdvm0HQ0rQ/SIypbjrMUHMloilcsfS1xksgqZ3LxqHGrb2WL93/Sxodv+psd8TV5+u6PtPO +3BeOw8zN7blJ35gpc0iPlwYHCoXw9WoJ36yW8H8rO9VMfHpvtjk/+gFntJd/eijcAYlxZo8j/woAAP// +P36oC/UmAAA= `, }, diff --git a/server/http.go b/server/http.go index 1699f5f23..485140628 100644 --- a/server/http.go +++ b/server/http.go @@ -24,28 +24,16 @@ type targetSoCJSON struct { TargetSoC int `json:"targetSoC"` } +type minSoCJSON struct { + MinSoC int `json:"minSoC"` +} + type route struct { Methods []string Pattern string HandlerFunc http.HandlerFunc } -// site is the minimal interface for accessing site methods -type site interface { - Healthy() bool - Configuration() core.SiteConfiguration - LoadPoints() []*core.LoadPoint - loadpoint -} - -// loadpoint is the minimal interface for accessing loadpoint methods -type loadpoint interface { - GetMode() api.ChargeMode - SetMode(api.ChargeMode) - GetTargetSoC() int - SetTargetSoC(targetSoC int) -} - // routeLogger traces matched routes including their executing time func routeLogger(inner http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -60,18 +48,19 @@ func routeLogger(inner http.Handler) http.HandlerFunc { } } -func indexHandler(site site, useLocal bool) http.HandlerFunc { +func indexHandler(site core.SiteAPI, useLocal bool) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=UTF-8") indexTemplate, err := FSString(useLocal, "/index.html") if err != nil { - log.FATAL.Fatal("httpd: failed to load embedded template: " + err.Error()) + log.FATAL.Print("httpd: failed to load embedded template:", err.Error()) + log.FATAL.Fatal("Make sure templates are included using the `release` build tag or use `make build`") } t, err := template.New("evcc").Delims("<<", ">>").Parse(indexTemplate) if err != nil { - log.FATAL.Fatal("httpd: failed to create main page template: ", err.Error()) + log.FATAL.Fatal("httpd: failed to create main page template:", err.Error()) } if err := t.Execute(w, map[string]interface{}{ @@ -80,7 +69,7 @@ func indexHandler(site site, useLocal bool) http.HandlerFunc { "Configured": len(site.LoadPoints()), "Tag": time.Now().Unix(), }); err != nil { - log.ERROR.Println("httpd: failed to render main page: ", err.Error()) + log.ERROR.Println("httpd: failed to render main page:", err.Error()) } }) } @@ -101,7 +90,7 @@ func jsonResponse(w http.ResponseWriter, r *http.Request, content interface{}) { } // HealthHandler returns current charge mode -func HealthHandler(site site) http.HandlerFunc { +func HealthHandler(site core.SiteAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !site.Healthy() { w.WriteHeader(http.StatusInternalServerError) @@ -114,7 +103,7 @@ func HealthHandler(site site) http.HandlerFunc { } // ConfigHandler returns current charge mode -func ConfigHandler(site site) http.HandlerFunc { +func ConfigHandler(site core.SiteAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { res := site.Configuration() jsonResponse(w, r, res) @@ -162,7 +151,7 @@ func StateHandler(cache *util.Cache) http.HandlerFunc { } // CurrentChargeModeHandler returns current charge mode -func CurrentChargeModeHandler(loadpoint loadpoint) http.HandlerFunc { +func CurrentChargeModeHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { res := chargeModeJSON{Mode: loadpoint.GetMode()} jsonResponse(w, r, res) @@ -170,7 +159,7 @@ func CurrentChargeModeHandler(loadpoint loadpoint) http.HandlerFunc { } // ChargeModeHandler updates charge mode -func ChargeModeHandler(loadpoint loadpoint) http.HandlerFunc { +func ChargeModeHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -189,7 +178,7 @@ func ChargeModeHandler(loadpoint loadpoint) http.HandlerFunc { } // CurrentTargetSoCHandler returns current target soc -func CurrentTargetSoCHandler(loadpoint loadpoint) http.HandlerFunc { +func CurrentTargetSoCHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { res := targetSoCJSON{TargetSoC: loadpoint.GetTargetSoC()} jsonResponse(w, r, res) @@ -197,25 +186,57 @@ func CurrentTargetSoCHandler(loadpoint loadpoint) http.HandlerFunc { } // TargetSoCHandler updates target soc -func TargetSoCHandler(loadpoint loadpoint) http.HandlerFunc { +func TargetSoCHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) socS, ok := vars["soc"] soc, err := strconv.ParseInt(socS, 10, 32) + if ok && err == nil { + err = loadpoint.SetTargetSoC(int(soc)) + } + if !ok || err != nil { w.WriteHeader(http.StatusBadRequest) return } - loadpoint.SetTargetSoC(int(soc)) - res := targetSoCJSON{TargetSoC: loadpoint.GetTargetSoC()} jsonResponse(w, r, res) } } +// CurrentMinSoCHandler returns current minimum soc +func CurrentMinSoCHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + res := minSoCJSON{MinSoC: loadpoint.GetMinSoC()} + jsonResponse(w, r, res) + } +} + +// MinSoCHandler updates minimum soc +func MinSoCHandler(loadpoint core.LoadPointSettingsAPI) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + socS, ok := vars["soc"] + soc, err := strconv.ParseInt(socS, 10, 32) + + if ok && err == nil { + err = loadpoint.SetMinSoC(int(soc)) + } + + if !ok || err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + res := minSoCJSON{MinSoC: loadpoint.GetMinSoC()} + jsonResponse(w, r, res) + } +} + // SocketHandler attaches websocket handler to uri func SocketHandler(hub *SocketHub) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -235,7 +256,7 @@ type HTTPd struct { } // NewHTTPd creates HTTP server with configured routes for loadpoint -func NewHTTPd(url string, site site, hub *SocketHub, cache *util.Cache) *HTTPd { +func NewHTTPd(url string, site core.SiteAPI, hub *SocketHub, cache *util.Cache) *HTTPd { var routes = map[string]route{ "health": {[]string{"GET"}, "/health", HealthHandler(site)}, "config": {[]string{"GET"}, "/config", ConfigHandler(site)}, @@ -245,6 +266,8 @@ func NewHTTPd(url string, site site, hub *SocketHub, cache *util.Cache) *HTTPd { "setmode": {[]string{"POST", "OPTIONS"}, "/mode/{mode:[a-z]+}", ChargeModeHandler(site)}, "gettargetsoc": {[]string{"GET"}, "/targetsoc", CurrentTargetSoCHandler(site)}, "settargetsoc": {[]string{"POST", "OPTIONS"}, "/targetsoc/{soc:[0-9]+}", TargetSoCHandler(site)}, + "getminsoc": {[]string{"GET"}, "/minsoc", CurrentMinSoCHandler(site)}, + "setminsoc": {[]string{"POST", "OPTIONS"}, "/minsoc/{soc:[0-9]+}", MinSoCHandler(site)}, } router := mux.NewRouter().StrictSlash(true) @@ -284,6 +307,8 @@ func NewHTTPd(url string, site site, hub *SocketHub, cache *util.Cache) *HTTPd { applyRouteHandler(subAPI, routes["setmode"], ChargeModeHandler(lp)) applyRouteHandler(subAPI, routes["gettargetsoc"], CurrentTargetSoCHandler(lp)) applyRouteHandler(subAPI, routes["settargetsoc"], TargetSoCHandler(lp)) + applyRouteHandler(subAPI, routes["getminsoc"], CurrentMinSoCHandler(lp)) + applyRouteHandler(subAPI, routes["setminsoc"], MinSoCHandler(lp)) } srv := &HTTPd{ diff --git a/server/influxdb.go b/server/influxdb.go index ef0c5ec7b..eb683e2f8 100644 --- a/server/influxdb.go +++ b/server/influxdb.go @@ -73,7 +73,7 @@ func (m *Influx) supportedType(p util.Param) bool { } // Run Influx publisher -func (m *Influx) Run(loadPoints []*core.LoadPoint, in <-chan util.Param) { +func (m *Influx) Run(loadPoints []core.LoadPointAPI, in <-chan util.Param) { writer := m.client.WriteAPI(m.org, m.database) // log errors diff --git a/server/mqtt.go b/server/mqtt.go index 6e875fda9..2088d76b6 100644 --- a/server/mqtt.go +++ b/server/mqtt.go @@ -65,25 +65,20 @@ func (m *MQTT) publish(topic string, retained bool, payload interface{}) { m.publishSingleValue(topic, retained, payload) } -type apiHandler interface { - SetMode(api.ChargeMode) - SetTargetSoC(int) -} - -func (m *MQTT) listenSetters(topic string, apiHandler apiHandler) { +func (m *MQTT) listenSetters(topic string, apiHandler core.LoadPointSettingsAPI) { m.Handler.Listen(topic+"/mode/set", func(payload string) { apiHandler.SetMode(api.ChargeMode(payload)) }) m.Handler.Listen(topic+"/targetsoc/set", func(payload string) { soc, err := strconv.Atoi(payload) if err == nil { - apiHandler.SetTargetSoC(soc) + _ = apiHandler.SetTargetSoC(soc) } }) } // Run starts the MQTT publisher for the MQTT API -func (m *MQTT) Run(site *core.Site, in <-chan util.Param) { +func (m *MQTT) Run(site core.SiteAPI, in <-chan util.Param) { topic := fmt.Sprintf("%s/site", m.root) m.listenSetters(topic, site) diff --git a/server/uds.go b/server/uds.go index 0efe0d42f..93512347e 100644 --- a/server/uds.go +++ b/server/uds.go @@ -6,6 +6,8 @@ import ( "net" "net/http" "os" + + "github.com/andig/evcc/core" ) // SocketPath is the unix domain socket path @@ -24,7 +26,7 @@ func remoteIfExists(file string) { } // HealthListener attaches listener to unix domain socket and runs listener -func HealthListener(site site) { +func HealthListener(site core.SiteAPI) { remoteIfExists(SocketPath) l, err := net.Listen("unix", SocketPath)