From 42eb940fe8e29f67a0d9f1fd2fd33f3075478123 Mon Sep 17 00:00:00 2001 From: andig Date: Sat, 5 Oct 2024 11:51:13 +0200 Subject: [PATCH] Loadpoint: add solar share setting (experimental) --- core/keys/loadpoint.go | 1 + core/loadpoint.go | 17 ++++++++++++----- core/loadpoint/api.go | 3 +++ core/loadpoint_api.go | 22 ++++++++++++++++++++++ server/http.go | 2 ++ server/http_site_handler.go | 28 ++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 5 deletions(-) diff --git a/core/keys/loadpoint.go b/core/keys/loadpoint.go index 6f58fd953..5ddbe0311 100644 --- a/core/keys/loadpoint.go +++ b/core/keys/loadpoint.go @@ -34,6 +34,7 @@ const ( SmartCostActive = "smartCostActive" // smart cost active SmartCostLimit = "smartCostLimit" // smart cost limit SmartCostNextStart = "smartCostNextStart" // smart cost next start + SolarShare = "solarShare" // solar share // effective values EffectivePriority = "effectivePriority" // effective priority diff --git a/core/loadpoint.go b/core/loadpoint.go index 6d09eb824..70c387326 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -127,6 +127,7 @@ type Loadpoint struct { limitSoc int // Session limit for soc limitEnergy float64 // Session limit for energy smartCostLimit *float64 // always charge if cost is below this value + solarShare float64 // solar share for pv mode mode api.ChargeMode enabled bool // Charger enabled state @@ -1308,9 +1309,13 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter // notes: activePhases can be 1, 2 or 3 and phaseTimer can only be active if lp current is already at minCurrent projectedSitePower -= Voltage * minCurrent * float64(activePhases-1) } + + // lp.Disable.Threshold + disableThreshold := (lp.GetSolarShare() - 1) * lp.EffectiveMinPower() + // kick off disable sequence - if projectedSitePower >= lp.Disable.Threshold { - lp.log.DEBUG.Printf("projected site power %.0fW >= %.0fW disable threshold", projectedSitePower, lp.Disable.Threshold) + if projectedSitePower >= disableThreshold { + lp.log.DEBUG.Printf("projected site power %.0fW >= %.0fW disable threshold", projectedSitePower, disableThreshold) if lp.pvTimer.IsZero() { lp.log.DEBUG.Printf("pv disable timer start: %v", lp.GetDisableDelay()) @@ -1339,10 +1344,12 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter } if mode == api.ModePV && !lp.enabled { + // lp.Enable.Threshold + enableThreshold := -lp.GetSolarShare() * lp.EffectiveMinPower() + // kick off enable sequence - if (lp.Enable.Threshold == 0 && targetCurrent >= minCurrent) || - (lp.Enable.Threshold != 0 && sitePower <= lp.Enable.Threshold) { - lp.log.DEBUG.Printf("site power %.0fW <= %.0fW enable threshold", sitePower, lp.Enable.Threshold) + if sitePower <= enableThreshold { + lp.log.DEBUG.Printf("site power %.0fW <= %.0fW enable threshold", sitePower, enableThreshold) if lp.pvTimer.IsZero() { lp.log.DEBUG.Printf("pv enable timer start: %v", lp.GetEnableDelay()) diff --git a/core/loadpoint/api.go b/core/loadpoint/api.go index 57f9498da..992ca9b11 100644 --- a/core/loadpoint/api.go +++ b/core/loadpoint/api.go @@ -123,6 +123,9 @@ type API interface { // SetSmartCostLimit sets the smart cost limit SetSmartCostLimit(limit *float64) + SetSolarShare(float64) + GetSolarShare() float64 + // // power and energy // diff --git a/core/loadpoint_api.go b/core/loadpoint_api.go index bc4082764..d076dd643 100644 --- a/core/loadpoint_api.go +++ b/core/loadpoint_api.go @@ -555,6 +555,28 @@ func (lp *Loadpoint) SetSmartCostLimit(val *float64) { } } +// GetSolarShare gets the solar share +func (lp *Loadpoint) GetSolarShare() float64 { + lp.RLock() + defer lp.RUnlock() + return lp.solarShare +} + +// SetSolarShare sets the solar share +func (lp *Loadpoint) SetSolarShare(val float64) { + lp.Lock() + defer lp.Unlock() + + lp.log.DEBUG.Println("set solar share:", "%.0f", 100*val) + + if lp.solarShare != val { + lp.solarShare = val + + lp.settings.SetFloat(keys.SolarShare, val) + lp.publish(keys.SolarShare, val) + } +} + // GetCircuit returns the assigned circuit func (lp *Loadpoint) GetCircuit() api.Circuit { lp.RLock() diff --git a/server/http.go b/server/http.go index 7d433c3cc..ffb37320c 100644 --- a/server/http.go +++ b/server/http.go @@ -112,6 +112,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param) "residualpower": {"POST", "/residualpower/{value:-?[0-9.]+}", floatHandler(site.SetResidualPower, site.GetResidualPower)}, "smartcost": {"POST", "/smartcostlimit/{value:-?[0-9.]+}", updateSmartCostLimit(site)}, "smartcostdelete": {"DELETE", "/smartcostlimit", updateSmartCostLimit(site)}, + "solar": {"POST", "/solarshare/{value:[0-9]+}", updateSolarShare(site)}, "tariff": {"GET", "/tariff/{tariff:[a-z]+}", tariffHandler(site)}, "sessions": {"GET", "/sessions", sessionHandler}, "updatesession": {"PUT", "/session/{id:[0-9]+}", updateSessionHandler}, @@ -181,6 +182,7 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param) "smartCost": {"POST", "/smartcostlimit/{value:-?[0-9.]+}", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)}, "smartCostDelete": {"DELETE", "/smartcostlimit", floatPtrHandler(pass(lp.SetSmartCostLimit), lp.GetSmartCostLimit)}, // "priority": {"POST", "/priority/{value:[0-9.]+}", floatHandler(pass(lp.SetPriority), lp.GetPriority)}, + "solar": {"POST", "/solarshare/{value:[0-9]+}", scaledIntHandler(pass(lp.SetSolarShare), lp.GetSolarShare, 100)}, } for _, r := range routes { diff --git a/server/http_site_handler.go b/server/http_site_handler.go index c04ce38fc..784280d3e 100644 --- a/server/http_site_handler.go +++ b/server/http_site_handler.go @@ -150,6 +150,15 @@ func intHandler(set func(int) error, get func() int) http.HandlerFunc { return handler(strconv.Atoi, set, get) } +// scaledIntHandler updates int-param api +func scaledIntHandler(set func(float64) error, get func() float64, scale float64) http.HandlerFunc { + return intHandler(func(v int) error { + return set(float64(v) / scale) + }, func() int { + return int(get() * scale) + }) +} + // boolHandler updates bool-param api func boolHandler(set func(bool) error, get func() bool) http.HandlerFunc { return handler(strconv.ParseBool, set, get) @@ -191,6 +200,25 @@ func updateSmartCostLimit(site site.API) http.HandlerFunc { } } +// updateSolarShare sets the solar share limit globally +func updateSolarShare(site site.API) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + val, err := strconv.Atoi(vars["value"]) + if err != nil { + jsonError(w, http.StatusBadRequest, err) + return + } + + for _, lp := range site.Loadpoints() { + lp.SetSolarShare(float64(val) / 100) + } + + jsonResult(w, val) + } +} + // stateHandler returns the combined state func stateHandler(cache *util.Cache) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) {