diff --git a/go.mod b/go.mod index 580751f57..c96ebb908 100644 --- a/go.mod +++ b/go.mod @@ -117,6 +117,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect github.com/breml/rootcerts v0.2.10 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cronokirby/saferith v0.33.0 // indirect @@ -220,3 +221,5 @@ replace gopkg.in/yaml.v3 => github.com/andig/yaml v0.0.0-20240531135838-1ff5761a replace github.com/grid-x/modbus => github.com/evcc-io/modbus v0.0.0-20241027151224-722a7a5ae529 replace github.com/lorenzodonini/ocpp-go => github.com/evcc-io/ocpp-go v0.0.0-20241230132027-815870498cc3 + +replace github.com/mlnoga/rct => github.com/andig/rct v0.1.2-0.20250301173047-991884936f23 diff --git a/go.sum b/go.sum index ce1df833e..34612c047 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/andig/gosunspec v0.0.0-20240918203654-860ce51d602b h1:81UMfM949I7StrR github.com/andig/gosunspec v0.0.0-20240918203654-860ce51d602b/go.mod h1:c6P6szcR+ROkqZruOR4f6qbDKFjZX6OitPpj+yJ/r8k= github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e h1:m/NTP3JWpR7M0ljLxiQU4fzR25jjhe1LDtxLMNcoNJQ= github.com/andig/mbserver v0.0.0-20230310211055-1d29cbb5820e/go.mod h1:4VtYzTm//oUipwvO3yh0g/udTE7pYJM+U/kyAuFDsgM= +github.com/andig/rct v0.1.2-0.20250301173047-991884936f23 h1:2kEZKOc1EzKCgzSXuG/iEkEf9yc/U4ArGGGmfFcqU3M= +github.com/andig/rct v0.1.2-0.20250301173047-991884936f23/go.mod h1:KRjBDV8kuvGd2kJfN8/F0kjH8JjkFVpBpn7MhW+ACL8= github.com/andig/yaml v0.0.0-20240531135838-1ff5761ab467 h1:JqIoHxsQSV39xaemvVRu3HOkSf5AbhQ1YEENJa2cL3Q= github.com/andig/yaml v0.0.0-20240531135838-1ff5761ab467/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= @@ -82,6 +84,8 @@ github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -458,8 +462,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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.20250202175314-91abcbf32e53 h1:wav6CUbSFXojyUwSRmMhgzIQHUuVCoS3V7BZ3nRi4vE= -github.com/mlnoga/rct v0.1.2-0.20250202175314-91abcbf32e53/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 a42d5234c..10beeaf1b 100644 --- a/meter/rct.go +++ b/meter/rct.go @@ -1,63 +1,33 @@ package meter import ( + "context" "encoding/binary" "errors" "fmt" "math" "strings" - "sync" "time" - "github.com/cenkalti/backoff/v4" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" "github.com/mlnoga/rct" ) -/* -This meter supports devices implementing the RCT communication protocol, e.g. the RCT PS 6.0 with / without battery. - -** Usages ** -The following usages are supported: -- grid ... for reading the power imported or exported to the grid -- pv ... for reading the power produced by the pv -- battery ... for reading the power imported or exported to the battery - -** Example configuration ** -meters: -- name: GridMeter - type: rct - uri: 192.168.1.23 - cache: 2s - usage: grid -- name: PvMeter - type: rct - uri: 192.168.1.23 - cache: 2s - usage: pv -- name: BatteryMeter - type: rct - uri: 192.168.1.23 - cache: 2s - usage: battery -*/ - // 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 } func init() { - registry.Add("rct", NewRCTFromConfig) + registry.AddCtx("rct", NewRCTFromConfig) } //go:generate go tool decorate -f decorateRCT -b *RCT -r api.Meter -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.Battery,Soc,func() (float64, error)" -t "api.BatteryController,SetBatteryMode,func(api.BatteryMode) error" -t "api.BatteryCapacity,Capacity,func() float64" // NewRCTFromConfig creates an RCT from generic config -func NewRCTFromConfig(other map[string]interface{}) (api.Meter, error) { +func NewRCTFromConfig(ctx context.Context, other map[string]interface{}) (api.Meter, error) { cc := struct { capacity `mapstructure:",squash"` Uri, Usage string @@ -75,29 +45,25 @@ func NewRCTFromConfig(other map[string]interface{}) (api.Meter, error) { return nil, errors.New("missing usage") } - return NewRCT(cc.Uri, cc.Usage, cc.MinSoc, cc.MaxSoc, cc.Cache, cc.capacity.Decorator()) + return NewRCT(ctx, cc.Uri, cc.Usage, cc.MinSoc, cc.MaxSoc, cc.Cache, cc.capacity.Decorator()) } -var rctMu sync.Mutex - // NewRCT creates an RCT meter -func NewRCT(uri, usage string, minSoc, maxSoc int, cache time.Duration, capacity func() float64) (api.Meter, error) { - rctMu.Lock() - defer rctMu.Unlock() +func NewRCT(ctx context.Context, uri, usage string, minSoc, maxSoc int, cache time.Duration, capacity func() float64) (api.Meter, error) { + log := util.NewLogger("rct") - conn, err := rct.NewConnection(uri, cache) + conn, err := rct.NewConnection(ctx, uri, rct.WithErrorCallback(func(err error) { + if err != nil { + log.ERROR.Println(err) + } + }), rct.WithTimeout(cache)) if err != nil { return nil, err } - bo := backoff.NewExponentialBackOff( - backoff.WithInitialInterval(10*time.Millisecond), - backoff.WithMaxElapsedTime(time.Second)) - m := &RCT{ usage: strings.ToLower(usage), conn: conn, - bo: bo, } // decorate api.MeterEnergy @@ -232,32 +198,11 @@ func (m *RCT) batterySoc() (float64, error) { // queryFloat adds retry logic of recoverable errors to QueryFloat32 func (m *RCT) queryFloat(id rct.Identifier) (float64, error) { - m.bo.Reset() - - res, err := backoff.RetryWithData(func() (float32, error) { - res, err := m.conn.QueryFloat32(id) - if err != nil && !errors.As(err, new(rct.RecoverableError)) { - err = backoff.Permanent(err) - } - - return res, err - }, m.bo) - + res, err := m.conn.QueryFloat32(id) return float64(res), err } // queryInt32 adds retry logic of recoverable errors to QueryInt32 func (m *RCT) queryInt32(id rct.Identifier) (int32, error) { - m.bo.Reset() - - res, err := backoff.RetryWithData(func() (int32, error) { - res, err := m.conn.QueryInt32(id) - if err != nil && !errors.As(err, new(rct.RecoverableError)) { - err = backoff.Permanent(err) - } - - return res, err - }, m.bo) - - return res, err + return m.conn.QueryInt32(id) }