Refactor and simplify meter readings (#12479)
This commit is contained in:
parent
4b8fa29963
commit
69f899e950
7 changed files with 174 additions and 178 deletions
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const (
|
|||
GridConfigured = "gridConfigured"
|
||||
GridCurrents = "gridCurrents"
|
||||
GridEnergy = "gridEnergy"
|
||||
GridPower = "gridPower"
|
||||
GridPowers = "gridPowers"
|
||||
HomePower = "homePower"
|
||||
PrioritySoc = "prioritySoc"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
331
core/site.go
331
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
|
||||
|
|
|
|||
|
|
@ -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
1
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
|
||||
|
|
|
|||
2
go.sum
2
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=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue