From 06c17c33aeedca847cf44b2abbe9476deb7d4720 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 3 Aug 2023 15:13:37 +0200 Subject: [PATCH] RCT: add retry (#9231) --- go.mod | 2 +- go.sum | 4 ++-- meter/rct.go | 60 ++++++++++++++++++++++++++++++++++++---------------- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 7451e5dc9c..eedf3c729e 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/mlnoga/rct v0.1.2-0.20230227143934-71af1fb7dfa1 + github.com/mlnoga/rct v0.1.2-0.20230731074838-03eacb926f99 github.com/muka/go-bluetooth v0.0.0-20221213043340-85dc80edc4e1 github.com/mxschmitt/golang-combinations v1.1.0 github.com/nicksnyder/go-i18n/v2 v2.2.1 diff --git a/go.sum b/go.sum index f6b28bcd29..417471db0f 100644 --- a/go.sum +++ b/go.sum @@ -822,8 +822,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mlnoga/rct v0.1.2-0.20230227143934-71af1fb7dfa1 h1:+OVWP3tJu+en06qieaXY/pcX+1qKp4X1oX9g3hMgOCk= -github.com/mlnoga/rct v0.1.2-0.20230227143934-71af1fb7dfa1/go.mod h1:0lfd2mmBnBzIvuzYtdhG+2371u+cUfIxsYErm4P9KRI= +github.com/mlnoga/rct v0.1.2-0.20230731074838-03eacb926f99 h1:sX4VwA7zgImnMqRgEkzP+3oKCgUHRF0OvHghFJ0pntY= +github.com/mlnoga/rct v0.1.2-0.20230731074838-03eacb926f99/go.mod h1:0lfd2mmBnBzIvuzYtdhG+2371u+cUfIxsYErm4P9KRI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/meter/rct.go b/meter/rct.go index 35901fc512..35bfaf6389 100644 --- a/meter/rct.go +++ b/meter/rct.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" @@ -42,6 +43,7 @@ meters: // RCT implements the api.Meter interface type RCT struct { + bo *backoff.ExponentialBackOff conn *rct.Connection // connection with the RCT device usage string // grid, pv, battery } @@ -80,9 +82,14 @@ func NewRCT(uri, usage string, cache time.Duration, capacity func() float64) (ap return nil, err } + bo := backoff.NewExponentialBackOff() + bo.MaxElapsedTime = time.Second + bo.InitialInterval = 10 * time.Millisecond + m := &RCT{ usage: strings.ToLower(usage), conn: conn, + bo: bo, } // decorate api.MeterEnergy @@ -104,24 +111,24 @@ func NewRCT(uri, usage string, cache time.Duration, capacity func() float64) (ap func (m *RCT) CurrentPower() (float64, error) { switch m.usage { case "grid": - res, err := m.conn.QueryFloat32(rct.TotalGridPowerW) - return float64(res), err + res, err := m.queryFloat(rct.TotalGridPowerW) + return res, err case "pv": - a, err := m.conn.QueryFloat32(rct.SolarGenAPowerW) + a, err := m.queryFloat(rct.SolarGenAPowerW) if err != nil { return 0, err } - b, err := m.conn.QueryFloat32(rct.SolarGenBPowerW) + b, err := m.queryFloat(rct.SolarGenBPowerW) if err != nil { return 0, err } - c, err := m.conn.QueryFloat32(rct.S0ExternalPowerW) - return float64(a + b + c), err + c, err := m.queryFloat(rct.S0ExternalPowerW) + return a + b + c, err case "battery": - res, err := m.conn.QueryFloat32(rct.BatteryPowerW) - return float64(res), err + res, err := m.queryFloat(rct.BatteryPowerW) + return res, err default: return 0, fmt.Errorf("invalid usage: %s", m.usage) @@ -132,24 +139,24 @@ func (m *RCT) CurrentPower() (float64, error) { func (m *RCT) totalEnergy() (float64, error) { switch m.usage { case "grid": - res, err := m.conn.QueryFloat32(rct.TotalEnergyGridWh) - return float64(res / 1000), err + res, err := m.queryFloat(rct.TotalEnergyGridWh) + return res / 1000, err case "pv": - a, err := m.conn.QueryFloat32(rct.TotalEnergySolarGenAWh) + a, err := m.queryFloat(rct.TotalEnergySolarGenAWh) if err != nil { return 0, err } - b, err := m.conn.QueryFloat32(rct.TotalEnergySolarGenBWh) - return float64((a + b) / 1000), err + b, err := m.queryFloat(rct.TotalEnergySolarGenBWh) + return (a + b) / 1000, err case "battery": - in, err := m.conn.QueryFloat32(rct.TotalEnergyBattInWh) + in, err := m.queryFloat(rct.TotalEnergyBattInWh) if err != nil { return 0, err } - out, err := m.conn.QueryFloat32(rct.TotalEnergyBattOutWh) - return float64((in - out) / 1000), err + out, err := m.queryFloat(rct.TotalEnergyBattOutWh) + return (in - out) / 1000, err default: return 0, fmt.Errorf("invalid usage: %s", m.usage) @@ -158,6 +165,23 @@ func (m *RCT) totalEnergy() (float64, error) { // batterySoc implements the api.Battery interface func (m *RCT) batterySoc() (float64, error) { - res, err := m.conn.QueryFloat32(rct.BatterySoC) - return float64(res * 100), err + res, err := m.queryFloat(rct.BatterySoC) + return res * 100, err +} + +// queryFloat adds retry logic of recoverable errors to QueryFloat32 +func (m *RCT) queryFloat(id rct.Identifier) (float64, error) { + started := time.Now() + m.bo.Reset() + + for { + next := m.bo.NextBackOff() + + res, err := m.conn.QueryFloat32(id) + if err == nil || !errors.Is(err, &rct.RecoverableError{}) || time.Since(started)+next > m.bo.MaxElapsedTime { + return float64(res), err + } + + time.Sleep(next) + } }