diff --git a/core/helper.go b/core/helper.go index a4ed2976f..e03a7f19f 100644 --- a/core/helper.go +++ b/core/helper.go @@ -1,7 +1,6 @@ package core import ( - "github.com/avast/retry-go/v4" "github.com/evcc-io/evcc/util" ) @@ -9,9 +8,6 @@ var ( status = map[bool]string{false: "disable", true: "enable"} presence = map[bool]string{false: "✗", true: "✓"} - // retryOptions ist the default options set for retryable operations - retryOptions = []retry.Option{retry.Attempts(3), retry.LastErrorOnly(true)} - // Voltage global value Voltage float64 ) diff --git a/core/keys/site.go b/core/keys/site.go index 40b87062b..e930f7ba9 100644 --- a/core/keys/site.go +++ b/core/keys/site.go @@ -9,6 +9,7 @@ const ( GridConfigured = "gridConfigured" GridCurrents = "gridCurrents" GridEnergy = "gridEnergy" + GridPower = "gridPower" GridPowers = "gridPowers" HomePower = "homePower" PrioritySoc = "prioritySoc" diff --git a/core/loadpoint.go b/core/loadpoint.go index 98f9b1335..3387dd0bc 100644 --- a/core/loadpoint.go +++ b/core/loadpoint.go @@ -11,8 +11,8 @@ import ( "time" evbus "github.com/asaskevich/EventBus" - "github.com/avast/retry-go/v4" "github.com/benbjohnson/clock" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/core/coordinator" "github.com/evcc-io/evcc/core/keys" @@ -1242,7 +1242,10 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter // UpdateChargePower updates charge meter power func (lp *Loadpoint) UpdateChargePower() { - err := retry.Do(func() error { + bo := backoff.NewExponentialBackOff() + bo.MaxElapsedTime = time.Second + + if err := backoff.Retry(func() error { value, err := lp.chargeMeter.CurrentPower() if err != nil { return err @@ -1262,8 +1265,7 @@ func (lp *Loadpoint) UpdateChargePower() { } return nil - }, retryOptions...) - if err != nil { + }, bo); err != nil { lp.log.ERROR.Printf("charge meter: %v", err) } } diff --git a/core/site.go b/core/site.go index 09b655298..12d8b1cfa 100644 --- a/core/site.go +++ b/core/site.go @@ -10,7 +10,7 @@ import ( "testing" "time" - "github.com/avast/retry-go/v4" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/cmd/shutdown" "github.com/evcc-io/evcc/core/coordinator" @@ -399,209 +399,196 @@ func (site *Site) publishDelta(key string, val interface{}) { site.publish(key, val) } -// updateMeter updates and publishes single meter -func (site *Site) updateMeter(meter api.Meter, power *float64) func() error { - return func() error { - value, err := meter.CurrentPower() +// updatePvMeters updates pv meters. All measurements are optional. +func (site *Site) updatePvMeters() { + if len(site.pvMeters) == 0 { + return + } + + var totalEnergy float64 + + site.pvPower = 0 + + mm := make([]meterMeasurement, len(site.pvMeters)) + + for i, meter := range site.pvMeters { + // pv power + power, err := meter.CurrentPower() + if err == nil { - *power = value // update value if no error + // ignore negative values which represent self-consumption + site.pvPower += max(0, power) + if power < -500 { + site.log.WARN.Printf("pv %d power: %.0fW is negative - check configuration if sign is correct", i+1, power) + } + } else { + err = fmt.Errorf("pv %d power: %v", i+1, err) + site.log.ERROR.Println(err) } - return err + // pv energy (production) + var energy float64 + if m, ok := meter.(api.MeterEnergy); err == nil && ok { + energy, err = m.TotalEnergy() + if err == nil { + totalEnergy += energy + } else { + site.log.ERROR.Printf("pv %d energy: %v", i+1, err) + } + } + + mm[i] = meterMeasurement{ + Power: power, + Energy: energy, + } } + + site.log.DEBUG.Printf("pv power: %.0fW", site.pvPower) + site.publish(keys.PvPower, site.pvPower) + site.publish(keys.PvEnergy, totalEnergy) + site.publish(keys.Pv, mm) } -// retryMeter retries meter update -func (site *Site) retryMeter(name string, meter api.Meter, power *float64) error { - if meter == nil { +// updateBatteryMeters updates battery meters. Power is retried, other measurements are optional. +func (site *Site) updateBatteryMeters() error { + if len(site.batteryMeters) == 0 { return nil } - err := retry.Do(site.updateMeter(meter, power), retryOptions...) + bo := backoff.NewExponentialBackOff() + bo.MaxElapsedTime = time.Second - if err == nil { - site.log.DEBUG.Printf("%s power: %.0fW", name, *power) - site.publish(name+"Power", *power) - } else { - err = fmt.Errorf("%s meter: %v", name, err) - site.log.ERROR.Println(err) + var totalCapacity, totalEnergy float64 + + site.batteryPower = 0 + site.batterySoc = 0 + + mm := make([]batteryMeasurement, len(site.batteryMeters)) + + for i, meter := range site.batteryMeters { + bo.Reset() + + power, err := backoff.RetryWithData(meter.CurrentPower, bo) + if err == nil { + site.batteryPower += power + if len(site.batteryMeters) > 1 { + site.log.DEBUG.Printf("battery %d power: %.0fW", i+1, power) + } + } else { + // power is required- return on error + return fmt.Errorf("battery %d power: %v", i+1, err) + } + + // battery energy (discharge) + var energy float64 + if m, ok := meter.(api.MeterEnergy); err == nil && ok { + energy, err = m.TotalEnergy() + if err == nil { + totalEnergy += energy + } else { + site.log.ERROR.Printf("battery %d energy: %v", i+1, err) + } + } + + // battery soc and capacity + var batSoc, capacity float64 + if meter, ok := meter.(api.Battery); ok { + batSoc, err = soc.Guard(meter.Soc()) + + if err == nil { + // weigh soc by capacity and accumulate total capacity + weighedSoc := batSoc + if m, ok := meter.(api.BatteryCapacity); ok { + capacity = m.Capacity() + totalCapacity += capacity + weighedSoc *= capacity + } + + site.batterySoc += weighedSoc + if len(site.batteryMeters) > 1 { + site.log.DEBUG.Printf("battery %d soc: %.0f%%", i+1, batSoc) + } + } else { + site.log.ERROR.Printf("battery %d soc: %v", i+1, err) + } + } + + _, controllable := meter.(api.BatteryController) + + mm[i] = batteryMeasurement{ + Power: power, + Energy: energy, + Soc: batSoc, + Capacity: capacity, + Controllable: controllable, + } } - return err + site.publish(keys.BatteryCapacity, totalCapacity) + + // convert weighed socs to total soc + if totalCapacity == 0 { + totalCapacity = float64(len(site.batteryMeters)) + } + site.batterySoc /= totalCapacity + + site.log.DEBUG.Printf("battery soc: %.0f%%", math.Round(site.batterySoc)) + site.publish(keys.BatterySoc, site.batterySoc) + + site.log.DEBUG.Printf("battery power: %.0fW", site.batteryPower) + site.publish(keys.BatteryPower, site.batteryPower) + site.publish(keys.BatteryEnergy, totalEnergy) + site.publish(keys.Battery, mm) + + return nil } -// updateMeter updates and publishes single meter -func (site *Site) updateMeters() error { - if len(site.pvMeters) > 0 { - var totalEnergy float64 - - site.pvPower = 0 - - mm := make([]meterMeasurement, len(site.pvMeters)) - - for i, meter := range site.pvMeters { - // pv power - var power float64 - err := retry.Do(site.updateMeter(meter, &power), retryOptions...) - - if err == nil { - // ignore negative values which represent self-consumption - site.pvPower += max(0, power) - if power < -500 { - site.log.WARN.Printf("pv %d power: %.0fW is negative - check configuration if sign is correct", i+1, power) - } - } else { - err = fmt.Errorf("pv %d power: %v", i+1, err) - site.log.ERROR.Println(err) - } - - // pv energy (production) - var energy float64 - if m, ok := meter.(api.MeterEnergy); err == nil && ok { - energy, err = m.TotalEnergy() - if err == nil { - totalEnergy += energy - } else { - site.log.ERROR.Printf("pv %d energy: %v", i+1, err) - } - } - - mm[i] = meterMeasurement{ - Power: power, - Energy: energy, - } - } - - site.log.DEBUG.Printf("pv power: %.0fW", site.pvPower) - site.publish(keys.PvPower, site.pvPower) - - site.publish(keys.PvEnergy, totalEnergy) - - site.publish(keys.Pv, mm) +// updateGridMeter updates grid meter. Power is retried, other measurements are optional. +func (site *Site) updateGridMeter() error { + if site.gridMeter == nil { + return nil } - if len(site.batteryMeters) > 0 { - var totalCapacity float64 - var totalEnergy float64 + bo := backoff.NewExponentialBackOff() + bo.MaxElapsedTime = time.Second - site.batteryPower = 0 - site.batterySoc = 0 - - mm := make([]batteryMeasurement, len(site.batteryMeters)) - - for i, meter := range site.batteryMeters { - // battery power - var power float64 - - // NOTE battery errors are logged but ignored as we don't consider them relevant - err := retry.Do(site.updateMeter(meter, &power), retryOptions...) - - if err == nil { - site.batteryPower += power - if len(site.batteryMeters) > 1 { - site.log.DEBUG.Printf("battery %d power: %.0fW", i+1, power) - } - } else { - site.log.ERROR.Printf("battery %d power: %v", i+1, err) - } - - // battery energy (discharge) - var energy float64 - if m, ok := meter.(api.MeterEnergy); err == nil && ok { - energy, err = m.TotalEnergy() - if err == nil { - totalEnergy += energy - } else { - site.log.ERROR.Printf("battery %d energy: %v", i+1, err) - } - } - - // battery soc and capacity - var batSoc, capacity float64 - if meter, ok := meter.(api.Battery); ok { - batSoc, err = soc.Guard(meter.Soc()) - - if err == nil { - // weigh soc by capacity and accumulate total capacity - weighedSoc := batSoc - if m, ok := meter.(api.BatteryCapacity); ok { - capacity = m.Capacity() - totalCapacity += capacity - weighedSoc *= capacity - } - - site.batterySoc += weighedSoc - if len(site.batteryMeters) > 1 { - site.log.DEBUG.Printf("battery %d soc: %.0f%%", i+1, batSoc) - } - } else { - site.log.ERROR.Printf("battery %d soc: %v", i+1, err) - } - } - - _, controllable := meter.(api.BatteryController) - - mm[i] = batteryMeasurement{ - Power: power, - Energy: energy, - Soc: batSoc, - Capacity: capacity, - Controllable: controllable, - } - } - - site.publish(keys.BatteryCapacity, totalCapacity) - - // convert weighed socs to total soc - if totalCapacity == 0 { - totalCapacity = float64(len(site.batteryMeters)) - } - site.batterySoc /= totalCapacity - - site.log.DEBUG.Printf("battery soc: %.0f%%", math.Round(site.batterySoc)) - site.publish(keys.BatterySoc, site.batterySoc) - - site.log.DEBUG.Printf("battery power: %.0fW", site.batteryPower) - site.publish(keys.BatteryPower, site.batteryPower) - - site.publish(keys.BatteryEnergy, totalEnergy) - - site.publish(keys.Battery, mm) + res, err := backoff.RetryWithData(site.gridMeter.CurrentPower, bo) + if err == nil { + site.gridPower = res + site.log.DEBUG.Printf("grid meter: %.0fW", res) + site.publish(keys.GridPower, res) + } else { + return fmt.Errorf("grid meter: %v", err) } - // grid power - err := site.retryMeter("grid", site.gridMeter, &site.gridPower) - // grid phase powers var p1, p2, p3 float64 - if phaseMeter, ok := site.gridMeter.(api.PhasePowers); err == nil && ok { + if phaseMeter, ok := site.gridMeter.(api.PhasePowers); ok { p1, p2, p3, err = phaseMeter.Powers() if err == nil { phases := []float64{p1, p2, p3} site.log.DEBUG.Printf("grid powers: %.0fW", phases) site.publish(keys.GridPowers, phases) } else { - err = fmt.Errorf("grid powers: %w", err) + site.log.ERROR.Printf("grid powers: %v", err) } } // grid phase currents (signed) - if phaseMeter, ok := site.gridMeter.(api.PhaseCurrents); err == nil && ok { - var i1, i2, i3 float64 - i1, i2, i3, err = phaseMeter.Currents() + if phaseMeter, ok := site.gridMeter.(api.PhaseCurrents); ok { + i1, i2, i3, err := phaseMeter.Currents() if err == nil { phases := []float64{util.SignFromPower(i1, p1), util.SignFromPower(i2, p2), util.SignFromPower(i3, p3)} site.log.DEBUG.Printf("grid currents: %.3gA", phases) site.publish(keys.GridCurrents, phases) } else { - err = fmt.Errorf("grid currents: %w", err) + site.log.ERROR.Printf("grid currents: %v", err) } } // grid energy (import) - if energyMeter, ok := site.gridMeter.(api.MeterEnergy); err == nil && ok { - var f float64 - f, err = energyMeter.TotalEnergy() + if energyMeter, ok := site.gridMeter.(api.MeterEnergy); ok { + f, err := energyMeter.TotalEnergy() if err == nil { site.publish(keys.GridEnergy, f) } else { @@ -609,7 +596,21 @@ func (site *Site) updateMeters() error { } } - return err + return nil +} + +// updateMeter updates and publishes single meter +func (site *Site) updateMeters() error { + // errors ignored + site.updatePvMeters() + + // power errors returned + if err := site.updateBatteryMeters(); err != nil { + return err + } + + // power errors returned + return site.updateGridMeter() } // sitePower returns diff --git a/core/site_api.go b/core/site_api.go index acb7614be..344dca199 100644 --- a/core/site_api.go +++ b/core/site_api.go @@ -53,7 +53,7 @@ func (site *Site) SetTitle(title string) { defer site.Unlock() site.Title = title - site.publish("siteTitle", title) + site.publish(keys.SiteTitle, title) settings.SetString(keys.Title, title) } @@ -70,7 +70,6 @@ func (site *Site) SetGridMeterRef(ref string) { defer site.Unlock() site.Meters.GridMeterRef = ref - // site.publish("siteGridMeterRef", meter) settings.SetString(keys.GridMeter, ref) } diff --git a/go.mod b/go.mod index 1cfc20a3d..b4860fcfe 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/andig/gosunspec v0.0.0-20231205122018-1daccfa17912 github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef - github.com/avast/retry-go/v4 v4.5.1 github.com/aws/aws-sdk-go v1.50.24 github.com/basgys/goxml2json v1.1.0 github.com/basvdlei/gotsmart v0.0.3 diff --git a/go.sum b/go.sum index 4fe345cc5..fb4af5bff 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM= github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII= -github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= -github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw=