Refactor and simplify meter readings (#12479)

This commit is contained in:
andig 2024-02-26 11:18:48 +01:00 • committed by GitHub
parent 4b8fa29963
commit 69f899e950
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 174 additions and 178 deletions

View file

@ -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
)

View file

@ -9,6 +9,7 @@ const (
GridConfigured = "gridConfigured"
GridCurrents = "gridCurrents"
GridEnergy = "gridEnergy"
GridPower = "gridPower"
GridPowers = "gridPowers"
HomePower = "homePower"
PrioritySoc = "prioritySoc"

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)
}

1
go.mod
View file

@ -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

2
go.sum
View file

@ -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=