+
bat enabled
- {api.StatusB, false, api.BatteryNormal, api.ModeNow}, // mode now, not charging -> bat enabled
- {api.StatusC, false, api.BatteryHold, api.ModeNow}, // mode now, charging -> bat disabled
- {api.StatusB, false, api.BatteryNormal, api.ModeMinPV}, // mode minPV, not charging -> bat enabled
- {api.StatusC, false, api.BatteryNormal, api.ModeMinPV}, // mode minPV, charging -> bat enabled
- {api.StatusB, false, api.BatteryNormal, api.ModePV}, // mode PV, not charging -> bat enabled
- {api.StatusC, false, api.BatteryNormal, api.ModePV}, // mode PV, charging, no planner -> bat enabled
- {api.StatusC, true, api.BatteryHold, api.ModePV}, // mode PV, charging, planner active -> bat disabled
+ {api.StatusB, false, api.BatteryNormal, api.ModeOff}, // mode off -> bat normal
+ {api.StatusB, false, api.BatteryNormal, api.ModeNow}, // mode now, not charging -> bat normal
+ {api.StatusC, false, api.BatteryHold, api.ModeNow}, // mode now, charging -> bat hold
+ {api.StatusB, false, api.BatteryNormal, api.ModeMinPV}, // mode minPV, not charging -> bat normal
+ {api.StatusC, false, api.BatteryNormal, api.ModeMinPV}, // mode minPV, charging -> bat normal
+ {api.StatusB, false, api.BatteryNormal, api.ModePV}, // mode PV, not charging -> bat normal
+ {api.StatusC, false, api.BatteryNormal, api.ModePV}, // mode PV, charging, no planner -> bat normal
+ {api.StatusC, true, api.BatteryHold, api.ModePV}, // mode PV, charging, planner active -> bat hold
}
log := util.NewLogger("foo")
for _, tc := range tcs {
- batCtrl := struct {
- *api.MockBatteryController
- *api.MockMeter
- }{
- api.NewMockBatteryController(ctrl),
- api.NewMockMeter(ctrl),
- }
- batCtrl.MockBatteryController.EXPECT().SetBatteryMode(tc.expBatMode).Times(1)
-
s := &Site{
- log: log,
- BatteryDischargeControl: true,
- batteryMeters: []api.Meter{batCtrl},
+ log: log,
}
lp := loadpoint.NewMockAPI(ctrl)
@@ -53,13 +42,16 @@ func TestBatteryDischarge(t *testing.T) {
lp.EXPECT().GetPlanActive().Return(tc.planActive).AnyTimes()
loadpoints := []loadpoint.API{lp}
- s.updateBatteryMode(loadpoints)
- assert.Equal(t, tc.expBatMode, s.getBatteryMode(), tc)
+
+ mode := s.determineBatteryMode(loadpoints)
+ assert.Equal(t, tc.expBatMode, mode, tc)
}
}
-// test that BatteryControllers are only called if batterymode changes
-func TestBatteryModeNoUpdate(t *testing.T) {
+func TestUpdateBatteryMode(t *testing.T) {
+
+ expBatMode := api.BatteryHold
+
ctrl := gomock.NewController(t)
batCtrl := struct {
@@ -69,28 +61,15 @@ func TestBatteryModeNoUpdate(t *testing.T) {
api.NewMockBatteryController(ctrl),
api.NewMockMeter(ctrl),
}
- batCtrl.MockBatteryController.EXPECT().SetBatteryMode(api.BatteryHold).Times(1)
-
- lp := loadpoint.NewMockAPI(ctrl)
- lp.EXPECT().GetStatus().Return(api.StatusC).Times(2)
- lp.EXPECT().GetMode().Return(api.ModeNow).Times(2)
- lp.EXPECT().GetPlanActive().Times(0)
- loadpoints := []loadpoint.API{lp}
+ batCtrl.MockBatteryController.EXPECT().SetBatteryMode(expBatMode).Times(1)
s := &Site{
- batteryMode: api.BatteryNormal,
- batteryMeters: []api.Meter{batCtrl},
- BatteryDischargeControl: true,
- log: util.NewLogger("foo"),
+ log: util.NewLogger("foo"),
+ batteryMeters: []api.Meter{batCtrl},
+ batteryMode: api.BatteryNormal,
}
- s.updateBatteryMode(loadpoints) // first call should call BatteryController
- s.updateBatteryMode(loadpoints) // this one should not
-
- // adjust mocks to simulate charge stop, should cause batMode udpate
- lp.EXPECT().GetStatus().Return(api.StatusB).Times(1)
- lp.EXPECT().GetMode().Return(api.ModeNow).Times(0)
- batCtrl.MockBatteryController.EXPECT().SetBatteryMode(api.BatteryNormal).Times(1)
-
- s.updateBatteryMode(loadpoints) // this one should have updated again
+ err := s.updateBatteryMode(expBatMode)
+ assert.NoError(t, err)
+ assert.Equal(t, expBatMode, s.GetBatteryMode())
}
diff --git a/i18n/de.toml b/i18n/de.toml
index b945517ba..5abc56b0b 100644
--- a/i18n/de.toml
+++ b/i18n/de.toml
@@ -1,6 +1,9 @@
[batterySettings]
batteryLevel = "Ladestand"
capacity = "{energy} von {total}"
+control = "Batteriesteuerung"
+notControllable = "Batteriesteuerung ist für diesen Speicher nicht verfügbar."
+discharge = "Verhindere Entladung im Schnell-Modus und bei geplantem Laden."
legendBottomName = "Haus hat Priorität"
legendBottomSubline = "nicht zum Laden verwenden"
legendMiddleName = "Fahrzeug zuerst"
@@ -174,6 +177,7 @@ titleTargetCharge = "Abfahrt"
battery = "Batterie"
batteryCharge = "Batterie laden"
batteryDischarge = "Batterie entladen"
+batteryHold = "Battery (gesperrt)"
batteryTooltip = "{energy} von {total} ({soc})"
gridImport = "Netzbezug"
homePower = "Verbrauch"
diff --git a/i18n/en.toml b/i18n/en.toml
index 4c7c49d93..84174f591 100644
--- a/i18n/en.toml
+++ b/i18n/en.toml
@@ -1,6 +1,9 @@
[batterySettings]
batteryLevel = "Battery level"
capacity = "{energy} of {total}"
+control = "Battery control"
+notControllable = "Battery control is not available for this battery."
+discharge = "Prevent discharge in fast mode and planned charging."
legendBottomName = "home priority"
legendBottomSubline = "not used for charging"
legendMiddleName = "vehicle first"
@@ -171,6 +174,7 @@ titleTargetCharge = "Departure"
battery = "Battery"
batteryCharge = "Battery charging"
batteryDischarge = "Battery discharging"
+batteryHold = "Battery (locked)"
batteryTooltip = "{energy} of {total} ({soc})"
gridImport = "Grid use"
homePower = "Consumption"
diff --git a/server/http.go b/server/http.go
index 9193eb3f2..0f8bc5968 100644
--- a/server/http.go
+++ b/server/http.go
@@ -95,28 +95,29 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, cache *util.Cache) {
// site api
routes := map[string]route{
- "health": {[]string{"GET"}, "/health", healthHandler(site)},
- "state": {[]string{"GET"}, "/state", stateHandler(cache)},
- "config": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", templatesHandler},
- "products": {[]string{"GET"}, "/config/products/{class:[a-z]+}", productsHandler},
- "device": {[]string{"GET"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deviceHandler},
- "devices": {[]string{"GET"}, "/config/devices/{class:[a-z]+}", devicesHandler},
- "newdevice": {[]string{"POST", "OPTIONS"}, "/config/devices/{class:[a-z]+}", newDeviceHandler},
- "updatedevice": {[]string{"PUT", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", updateDeviceHandler},
- "deletedevice": {[]string{"DELETE", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deleteDeviceHandler},
- "testconfig": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}", testHandler},
- "testdevice": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}/{id:[0-9.]+}", testHandler},
- "buffersoc": {[]string{"POST", "OPTIONS"}, "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)},
- "bufferstartsoc": {[]string{"POST", "OPTIONS"}, "/bufferstartsoc/{value:[0-9.]+}", floatHandler(site.SetBufferStartSoc, site.GetBufferStartSoc)},
- "prioritysoc": {[]string{"POST", "OPTIONS"}, "/prioritysoc/{value:[0-9.]+}", floatHandler(site.SetPrioritySoc, site.GetPrioritySoc)},
- "residualpower": {[]string{"POST", "OPTIONS"}, "/residualpower/{value:[-0-9.]+}", floatHandler(site.SetResidualPower, site.GetResidualPower)},
- "smartcost": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[-0-9.]+}", floatHandler(site.SetSmartCostLimit, site.GetSmartCostLimit)},
- "tariff": {[]string{"GET"}, "/tariff/{tariff:[a-z]+}", tariffHandler(site)},
- "sessions": {[]string{"GET"}, "/sessions", sessionHandler},
- "session1": {[]string{"PUT", "OPTIONS"}, "/session/{id:[0-9]+}", updateSessionHandler},
- "session2": {[]string{"DELETE", "OPTIONS"}, "/session/{id:[0-9]+}", deleteSessionHandler},
- "telemetry": {[]string{"GET"}, "/settings/telemetry", boolGetHandler(telemetry.Enabled)},
- "telemetry2": {[]string{"POST", "OPTIONS"}, "/settings/telemetry/{value:[a-z]+}", boolHandler(telemetry.Enable, telemetry.Enabled)},
+ "health": {[]string{"GET"}, "/health", healthHandler(site)},
+ "state": {[]string{"GET"}, "/state", stateHandler(cache)},
+ "config": {[]string{"GET"}, "/config/templates/{class:[a-z]+}", templatesHandler},
+ "products": {[]string{"GET"}, "/config/products/{class:[a-z]+}", productsHandler},
+ "device": {[]string{"GET"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deviceHandler},
+ "devices": {[]string{"GET"}, "/config/devices/{class:[a-z]+}", devicesHandler},
+ "newdevice": {[]string{"POST", "OPTIONS"}, "/config/devices/{class:[a-z]+}", newDeviceHandler},
+ "updatedevice": {[]string{"PUT", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", updateDeviceHandler},
+ "deletedevice": {[]string{"DELETE", "OPTIONS"}, "/config/devices/{class:[a-z]+}/{id:[0-9.]+}", deleteDeviceHandler},
+ "testconfig": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}", testHandler},
+ "testdevice": {[]string{"POST", "OPTIONS"}, "/config/test/{class:[a-z]+}/{id:[0-9.]+}", testHandler},
+ "buffersoc": {[]string{"POST", "OPTIONS"}, "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)},
+ "bufferstartsoc": {[]string{"POST", "OPTIONS"}, "/bufferstartsoc/{value:[0-9.]+}", floatHandler(site.SetBufferStartSoc, site.GetBufferStartSoc)},
+ "batterydischargecontrol": {[]string{"POST", "OPTIONS"}, "/batterydischargecontrol/{value:[a-z]+}", boolHandler(site.SetBatteryDischargeControl, site.GetBatteryDischargeControl)},
+ "prioritysoc": {[]string{"POST", "OPTIONS"}, "/prioritysoc/{value:[0-9.]+}", floatHandler(site.SetPrioritySoc, site.GetPrioritySoc)},
+ "residualpower": {[]string{"POST", "OPTIONS"}, "/residualpower/{value:[-0-9.]+}", floatHandler(site.SetResidualPower, site.GetResidualPower)},
+ "smartcost": {[]string{"POST", "OPTIONS"}, "/smartcostlimit/{value:[-0-9.]+}", floatHandler(site.SetSmartCostLimit, site.GetSmartCostLimit)},
+ "tariff": {[]string{"GET"}, "/tariff/{tariff:[a-z]+}", tariffHandler(site)},
+ "sessions": {[]string{"GET"}, "/sessions", sessionHandler},
+ "session1": {[]string{"PUT", "OPTIONS"}, "/session/{id:[0-9]+}", updateSessionHandler},
+ "session2": {[]string{"DELETE", "OPTIONS"}, "/session/{id:[0-9]+}", deleteSessionHandler},
+ "telemetry": {[]string{"GET"}, "/settings/telemetry", boolGetHandler(telemetry.Enabled)},
+ "telemetry2": {[]string{"POST", "OPTIONS"}, "/settings/telemetry/{value:[a-z]+}", boolHandler(telemetry.Enable, telemetry.Enabled)},
}
for _, r := range routes {
diff --git a/server/http_handler.go b/server/http_handler.go
index 814b0f4c8..0cd2753fc 100644
--- a/server/http_handler.go
+++ b/server/http_handler.go
@@ -125,14 +125,12 @@ func boolHandler(set func(bool) error, get func() bool) http.HandlerFunc {
vars := mux.Vars(r)
val, err := strconv.ParseBool(vars["value"])
- if err != nil {
- jsonError(w, http.StatusBadRequest, err)
- return
+ if err == nil {
+ err = set(val)
}
- err = set(val)
if err != nil {
- jsonError(w, http.StatusNotAcceptable, err)
+ jsonError(w, http.StatusBadRequest, err)
return
}