From f3727c66391d75269f340d24c6f0cf8d495716f9 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 19 Oct 2025 11:18:08 +0200 Subject: [PATCH] Battery: prevent api misuse (#24559) --- core/site.go | 4 +++- core/site/api.go | 4 ++-- core/site_api.go | 29 ++++++++++++++++++++++++----- core/site_battery.go | 12 ++++++++++++ server/http.go | 4 ++-- server/mqtt.go | 8 ++++---- 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/core/site.go b/core/site.go index 362ee2646..9a46cdd71 100644 --- a/core/site.go +++ b/core/site.go @@ -324,7 +324,9 @@ func (site *Site) restoreSettings() error { } } if v, err := settings.Float(keys.BatteryGridChargeLimit); err == nil { - site.SetBatteryGridChargeLimit(&v) + if err := site.SetBatteryGridChargeLimit(&v); err != nil { + return err + } } // restore accumulated energy diff --git a/core/site/api.go b/core/site/api.go index a514a092d..3656896fe 100644 --- a/core/site/api.go +++ b/core/site/api.go @@ -45,7 +45,7 @@ type API interface { // GetBatteryGridChargeLimit get the grid charge limit GetBatteryGridChargeLimit() *float64 // SetBatteryGridChargeLimit sets the grid charge limit - SetBatteryGridChargeLimit(limit *float64) + SetBatteryGridChargeLimit(limit *float64) error // // power and energy @@ -75,5 +75,5 @@ type API interface { // GetBatteryModeExternal returns the external battery mode GetBatteryModeExternal() api.BatteryMode // SetBatteryModeExternal sets the external battery mode - SetBatteryModeExternal(api.BatteryMode) + SetBatteryModeExternal(api.BatteryMode) error } diff --git a/core/site_api.go b/core/site_api.go index 15c42683d..e593a4c3a 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -16,7 +16,10 @@ import ( var _ site.API = (*Site)(nil) -var ErrBatteryNotConfigured = errors.New("battery not configured") +var ( + ErrBatteryNotConfigured = errors.New("battery not configured") + ErrBatteryControlNotAvailable = errors.New("battery control not available") +) // isConfigurable checks if the meter is configurable func isConfigurable(ref string) bool { @@ -296,6 +299,10 @@ func (site *Site) GetBatteryDischargeControl() bool { func (site *Site) SetBatteryDischargeControl(val bool) error { site.log.DEBUG.Println("set battery discharge control:", val) + if !site.hasBatteryControl() { + return ErrBatteryControlNotAvailable + } + site.Lock() defer site.Unlock() @@ -314,9 +321,13 @@ func (site *Site) GetBatteryGridChargeLimit() *float64 { return site.batteryGridChargeLimit } -func (site *Site) SetBatteryGridChargeLimit(val *float64) { +func (site *Site) SetBatteryGridChargeLimit(val *float64) error { site.log.DEBUG.Println("set grid charge limit:", printPtr("%.1f", val)) + if !site.hasBatteryControl() { + return ErrBatteryControlNotAvailable + } + site.Lock() defer site.Unlock() @@ -331,6 +342,8 @@ func (site *Site) SetBatteryGridChargeLimit(val *float64) { site.publish(keys.BatteryGridChargeLimit, *val) } } + + return nil } // GetBatteryMode returns the battery mode @@ -348,12 +361,16 @@ func (site *Site) GetBatteryModeExternal() api.BatteryMode { } // SetBatteryModeExternal sets the external battery mode -func (site *Site) SetBatteryModeExternal(mode api.BatteryMode) { +func (site *Site) SetBatteryModeExternal(mode api.BatteryMode) error { + site.log.DEBUG.Printf("set external battery mode: %s", mode.String()) + + if !site.hasBatteryControl() { + return ErrBatteryControlNotAvailable + } + site.Lock() defer site.Unlock() - site.log.DEBUG.Printf("set external battery mode: %s", mode.String()) - disable := mode == api.BatteryUnknown if mode != site.batteryModeExternal { @@ -376,6 +393,8 @@ func (site *Site) SetBatteryModeExternal(mode api.BatteryMode) { if !disable { site.batteryModeExternalTimer = time.Now() } + + return nil } func (site *Site) batteryModeWatchdogExpired() bool { diff --git a/core/site_battery.go b/core/site_battery.go index aa2b3b21b..e48e80a99 100644 --- a/core/site_battery.go +++ b/core/site_battery.go @@ -18,6 +18,18 @@ func (site *Site) batteryConfigured() bool { return len(site.batteryMeters) > 0 } +func (site *Site) hasBatteryControl() bool { + for _, dev := range site.batteryMeters { + meter := dev.Instance() + + if _, ok := meter.(api.BatteryController); ok { + return true + } + } + + return false +} + // setBatteryMode sets the battery mode func (site *Site) setBatteryMode(batMode api.BatteryMode) { site.batteryMode = batMode diff --git a/server/http.go b/server/http.go index 016a9f6be..f8503ff93 100644 --- a/server/http.go +++ b/server/http.go @@ -141,8 +141,8 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param) "buffersoc": {"POST", "/buffersoc/{value:[0-9.]+}", floatHandler(site.SetBufferSoc, site.GetBufferSoc)}, "bufferstartsoc": {"POST", "/bufferstartsoc/{value:[0-9.]+}", floatHandler(site.SetBufferStartSoc, site.GetBufferStartSoc)}, "batterydischargecontrol": {"POST", "/batterydischargecontrol/{value:[01truefalse]+}", boolHandler(site.SetBatteryDischargeControl, site.GetBatteryDischargeControl)}, - "batterygridcharge": {"POST", "/batterygridchargelimit/{value:-?[0-9.]+}", floatPtrHandler(pass(site.SetBatteryGridChargeLimit), site.GetBatteryGridChargeLimit)}, - "batterygridchargedelete": {"DELETE", "/batterygridchargelimit", floatPtrHandler(pass(site.SetBatteryGridChargeLimit), site.GetBatteryGridChargeLimit)}, + "batterygridcharge": {"POST", "/batterygridchargelimit/{value:-?[0-9.]+}", floatPtrHandler(site.SetBatteryGridChargeLimit, site.GetBatteryGridChargeLimit)}, + "batterygridchargedelete": {"DELETE", "/batterygridchargelimit", floatPtrHandler(site.SetBatteryGridChargeLimit, site.GetBatteryGridChargeLimit)}, "batterymode": {"POST", "/batterymode/{value:[a-z]+}", updateBatteryMode(site)}, "batterymodedelete": {"DELETE", "/batterymode", updateBatteryMode(site)}, "prioritysoc": {"POST", "/prioritysoc/{value:[0-9.]+}", floatHandler(site.SetPrioritySoc, site.GetPrioritySoc)}, diff --git a/server/mqtt.go b/server/mqtt.go index e9113fd50..c1b60c7b1 100644 --- a/server/mqtt.go +++ b/server/mqtt.go @@ -220,13 +220,13 @@ func (m *MQTT) listenSiteSetters(topic string, site site.API) error { lp.SetSmartFeedInPriorityLimit(limit) } }))}, - {"batteryGridChargeLimit", floatPtrSetter(pass(site.SetBatteryGridChargeLimit))}, - {"batteryMode", ptrSetter(api.BatteryModeString, pass(func(m *api.BatteryMode) { + {"batteryGridChargeLimit", floatPtrSetter(site.SetBatteryGridChargeLimit)}, + {"batteryMode", ptrSetter(api.BatteryModeString, func(m *api.BatteryMode) error { if m == nil { m = lo.ToPtr(api.BatteryUnknown) } - site.SetBatteryModeExternal(*m) - }))}, + return site.SetBatteryModeExternal(*m) + })}, } { if err := m.Handler.ListenSetter(topic+"/"+s.topic, s.fun); err != nil { return err