From c3db3032ce7d70bc5e41e108ba117711e8492745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Thu, 24 Jun 2021 19:42:11 +0200 Subject: [PATCH 1/6] reworked SMA and added SMA provider --- meter/sma.go | 178 ++++----------------------------------- provider/sma.go | 98 +++++++++++++++++++++ provider/sma/device.go | 62 ++++++++++++++ provider/sma/discover.go | 163 +++++++++++++++++++++++++++++++++++ 4 files changed, 341 insertions(+), 160 deletions(-) create mode 100644 provider/sma.go create mode 100644 provider/sma/device.go create mode 100644 provider/sma/discover.go diff --git a/meter/sma.go b/meter/sma.go index 7b41518118..c243f6c08d 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -1,88 +1,25 @@ package meter import ( - "context" "errors" "fmt" "os" "sort" - "sync" - "sync/atomic" "text/tabwriter" - "time" "github.com/andig/evcc/api" + "github.com/andig/evcc/provider/sma" "github.com/andig/evcc/util" - "github.com/imdario/mergo" "gitlab.com/bboehmke/sunny" ) -const udpTimeout = 10 * time.Second - -// smaDiscoverer discovers SMA devices in background while providing already found devices -type smaDiscoverer struct { - conn *sunny.Connection - devices map[uint32]*sunny.Device - mux sync.RWMutex - done uint32 -} - -// run discover and store found devices -func (d *smaDiscoverer) run() { - devices := make(chan *sunny.Device) - - go func() { - for device := range devices { - d.mux.Lock() - d.devices[device.SerialNumber()] = device - d.mux.Unlock() - } - }() - - // discover devices and wait for results - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - d.conn.DiscoverDevices(ctx, devices, "") - cancel() - close(devices) - - // mark discover as done - atomic.AddUint32(&d.done, 1) -} - -func (d *smaDiscoverer) get(serial uint32) *sunny.Device { - d.mux.RLock() - defer d.mux.RUnlock() - return d.devices[serial] -} - -// deviceBySerial with the given serial number -func (d *smaDiscoverer) deviceBySerial(serial uint32) *sunny.Device { - start := time.Now() - for time.Since(start) < time.Second*3 { - // discover done -> return immediately regardless of result - if atomic.LoadUint32(&d.done) != 0 { - return d.get(serial) - } - - // device with serial found -> return - if device := d.get(serial); device != nil { - return device - } - - time.Sleep(time.Millisecond * 10) - } - return d.get(serial) -} - // SMA supporting SMA Home Manager 2.0 and SMA Energy Meter 30 type SMA struct { log *util.Logger - mux *util.Waiter uri string iface string - values map[sunny.ValueID]interface{} scale float64 - device *sunny.Device + device *sma.Device } func init() { @@ -114,58 +51,32 @@ func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) { return NewSMA(cc.URI, cc.Password, cc.Interface, cc.Serial, cc.Scale) } -// map of created discover instances -var discoverers = make(map[string]*smaDiscoverer) - -// initialize sunny logger only once -var once sync.Once - // NewSMA creates a SMA Meter func NewSMA(uri, password, iface string, serial uint32, scale float64) (api.Meter, error) { - log := util.NewLogger("sma") - once.Do(func() { - sunny.Log = log.TRACE - }) - sm := &SMA{ - mux: util.NewWaiter(udpTimeout, func() { log.TRACE.Println("wait for initial value") }), - log: log, - uri: uri, - iface: iface, - values: make(map[sunny.ValueID]interface{}), - scale: scale, + log: util.NewLogger("sma"), + uri: uri, + iface: iface, + scale: scale, } - conn, err := sunny.NewConnection(iface) + discoverer, err := sma.GetDiscoverer(iface) if err != nil { - return nil, fmt.Errorf("connection failed: %w", err) + return nil, fmt.Errorf("failed to get discoverer failed: %w", err) } switch { case uri != "": - sm.device, err = conn.NewDevice(uri, password) + sm.device, err = discoverer.DeviceByIP(uri, password) if err != nil { return nil, err } case serial > 0: - discoverer, ok := discoverers[iface] - if !ok { - discoverer = &smaDiscoverer{ - conn: conn, - devices: make(map[uint32]*sunny.Device), - } - - go discoverer.run() - - discoverers[iface] = discoverer - } - - sm.device = discoverer.deviceBySerial(serial) + sm.device = discoverer.DeviceBySerial(serial, password) if sm.device == nil { return nil, fmt.Errorf("device not found: %d", serial) } - sm.device.SetPassword(password) default: return nil, errors.New("missing uri or serial") @@ -184,62 +95,29 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64) (api.Mete } } - go func() { - for range time.NewTicker(time.Second).C { - sm.updateValues() - } - }() - return decorateSMA(sm, soc), nil } -func (sm *SMA) updateValues() { - sm.mux.Lock() - defer sm.mux.Unlock() - - values, err := sm.device.GetValues() - if err == nil { - err = mergo.Merge(&sm.values, values, mergo.WithOverride) - } - - if err == nil { - sm.mux.Update() - } else { - sm.log.ERROR.Println(err) - } -} - -func (sm *SMA) hasValue() (map[sunny.ValueID]interface{}, error) { - elapsed := sm.mux.LockWithTimeout() - defer sm.mux.Unlock() - - if elapsed > 0 { - return nil, fmt.Errorf("update timeout: %v", elapsed.Truncate(time.Second)) - } - - return sm.values, nil -} - // CurrentPower implements the api.Meter interface func (sm *SMA) CurrentPower() (float64, error) { - values, err := sm.hasValue() - return sm.scale * (sm.asFloat(values[sunny.ActivePowerPlus]) - sm.asFloat(values[sunny.ActivePowerMinus])), err + values, err := sm.device.GetValues() + return sm.scale * (sma.AsFloat(values[sunny.ActivePowerPlus]) - sma.AsFloat(values[sunny.ActivePowerMinus])), err } // TotalEnergy implements the api.MeterEnergy interface func (sm *SMA) TotalEnergy() (float64, error) { - values, err := sm.hasValue() - return sm.asFloat(values[sunny.ActiveEnergyPlus]) / 3600000, err + values, err := sm.device.GetValues() + return sma.AsFloat(values[sunny.ActiveEnergyPlus]) / 3600000, err } // Currents implements the api.MeterCurrent interface func (sm *SMA) Currents() (float64, float64, float64, error) { - values, err := sm.hasValue() + values, err := sm.device.GetValues() measurements := []sunny.ValueID{sunny.CurrentL1, sunny.CurrentL2, sunny.CurrentL3} var vals [3]float64 for i := 0; i < 3; i++ { - vals[i] = sm.asFloat(values[measurements[i]]) + vals[i] = sma.AsFloat(values[measurements[i]]) } return vals[0], vals[1], vals[2], err @@ -247,8 +125,8 @@ func (sm *SMA) Currents() (float64, float64, float64, error) { // soc implements the api.Battery interface func (sm *SMA) soc() (float64, error) { - values, err := sm.hasValue() - return sm.asFloat(values[sunny.BatteryCharge]), err + values, err := sm.device.GetValues() + return sma.AsFloat(values[sunny.BatteryCharge]), err } // Diagnose implements the api.Diagnosis interface @@ -290,23 +168,3 @@ func (sm *SMA) Diagnose() { } w.Flush() } - -func (sm *SMA) asFloat(value interface{}) float64 { - switch v := value.(type) { - case float64: - return v - case int32: - return float64(v) - case int64: - return float64(v) - case uint32: - return float64(v) - case uint64: - return float64(v) - case nil: - return 0 - default: - sm.log.WARN.Printf("unknown value type: %T", value) - return 0 - } -} diff --git a/provider/sma.go b/provider/sma.go new file mode 100644 index 0000000000..a8a9d01468 --- /dev/null +++ b/provider/sma.go @@ -0,0 +1,98 @@ +package provider + +import ( + "errors" + "fmt" + + "github.com/andig/evcc/provider/sma" + "github.com/andig/evcc/util" + "gitlab.com/bboehmke/sunny" +) + +// SMA provider +type SMA struct { + device *sma.Device + value sunny.ValueID + scale float64 +} + +func init() { + registry.Add("sma", NewSMAFromConfig) +} + +// NewSMAFromConfig creates SMA provider +func NewSMAFromConfig(other map[string]interface{}) (IntProvider, error) { + cc := struct { + URI, Password, Interface string + Serial uint32 + Value string + Scale float64 + }{ + Password: "0000", + Scale: 1, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + discoverer, err := sma.GetDiscoverer(cc.Interface) + if err != nil { + return nil, fmt.Errorf("failed to get discoverer failed: %w", err) + } + + var provider = &SMA{ + scale: cc.Scale, + } + switch { + case cc.URI != "": + provider.device, err = discoverer.DeviceByIP(cc.URI, cc.Password) + if err != nil { + return nil, err + } + + case cc.Serial > 0: + provider.device = discoverer.DeviceBySerial(cc.Serial, cc.Password) + if provider.device == nil { + return nil, fmt.Errorf("device not found: %d", cc.Serial) + } + + default: + return nil, errors.New("missing uri or serial") + } + + provider.value, err = sunny.ValueIDString(cc.Value) + if err != nil { + return nil, err + } + + return provider, err +} + +var _ FloatProvider = (*Mqtt)(nil) + +// FloatGetter creates handler for float64 +func (p *SMA) FloatGetter() func() (float64, error) { + return func() (float64, error) { + values, err := p.device.GetValues() + if err != nil { + return 0, err + } + + return sma.AsFloat(values[p.value]) * p.scale, nil + } +} + +// IntGetter creates handler for int64 +func (p *SMA) IntGetter() func() (int64, error) { + fl := p.FloatGetter() + + return func() (int64, error) { + f, err := fl() + if err != nil { + return 0, err + } + + return int64(f), nil + } +} diff --git a/provider/sma/device.go b/provider/sma/device.go new file mode 100644 index 0000000000..54b56d8e79 --- /dev/null +++ b/provider/sma/device.go @@ -0,0 +1,62 @@ +package sma + +import ( + "fmt" + "time" + + "github.com/andig/evcc/util" + "github.com/imdario/mergo" + "gitlab.com/bboehmke/sunny" +) + +// Device holds information for a Device and provides interface to get values +type Device struct { + *sunny.Device + + log *util.Logger + mux *util.Waiter + values map[sunny.ValueID]interface{} +} + +func (d *Device) updateValues() error { + d.mux.Lock() + defer d.mux.Unlock() + + values, err := d.Device.GetValues() + if err == nil { + err = mergo.Merge(&d.values, values, mergo.WithOverride) + d.mux.Update() + } + return err +} + +func (d *Device) GetValues() (map[sunny.ValueID]interface{}, error) { + elapsed := d.mux.LockWithTimeout() + defer d.mux.Unlock() + + if elapsed > 0 { + return nil, fmt.Errorf("update timeout: %v", elapsed.Truncate(time.Second)) + } + + return d.values, nil +} + +func AsFloat(value interface{}) float64 { + switch v := value.(type) { + case float64: + return v + case int32: + return float64(v) + case int64: + return float64(v) + case uint32: + return float64(v) + case uint64: + return float64(v) + case nil: + return 0 + default: + util.NewLogger("sma").WARN.Printf("unknown value type: %T", value) + return 0 + } +} diff --git a/provider/sma/discover.go b/provider/sma/discover.go new file mode 100644 index 0000000000..d9c05a2304 --- /dev/null +++ b/provider/sma/discover.go @@ -0,0 +1,163 @@ +package sma + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/andig/evcc/util" + "gitlab.com/bboehmke/sunny" +) + +const udpTimeout = 10 * time.Second + +// map of created discover instances +var discoverers = make(map[string]*Discoverer) +var discoverersMutex sync.Mutex + +// initialize sunny logger only once +var once sync.Once + +// GetDiscoverer fo the given interface +func GetDiscoverer(iface string) (*Discoverer, error) { + // on time initialization of sunny logger + log := util.NewLogger("sma") + once.Do(func() { + sunny.Log = log.TRACE + }) + + discoverersMutex.Lock() + defer discoverersMutex.Unlock() + + // get or create discoverer + discoverer, ok := discoverers[iface] + if !ok { + conn, err := sunny.NewConnection(iface) + if err != nil { + return nil, fmt.Errorf("connection failed: %w", err) + } + + discoverer = &Discoverer{ + log: log, + conn: conn, + devices: make(map[uint32]*Device), + } + + go discoverer.run() + + discoverers[iface] = discoverer + } + return discoverer, nil +} + +// Discoverer discovers SMA devicesBySerial in background while providing already found devicesBySerial +type Discoverer struct { + log *util.Logger + conn *sunny.Connection + devices map[uint32]*Device + mux sync.RWMutex + done uint32 +} + +func (d *Discoverer) createDevice(device *sunny.Device) *Device { + dev := &Device{ + Device: device, + log: d.log, + mux: util.NewWaiter(udpTimeout, func() { d.log.TRACE.Println("wait for initial value") }), + values: make(map[sunny.ValueID]interface{}), + } + + go func() { + for range time.NewTicker(time.Second).C { + err := dev.updateValues() + if err != nil { + d.log.ERROR.Println(err) + } + } + }() + + return dev +} + +func (d *Discoverer) addDevice(device *sunny.Device) { + d.mux.Lock() + defer d.mux.Unlock() + + if _, ok := d.devices[device.SerialNumber()]; !ok { + d.devices[device.SerialNumber()] = d.createDevice(device) + } +} + +// run discover and store found devicesBySerial +func (d *Discoverer) run() { + devices := make(chan *sunny.Device) + + go func() { + for device := range devices { + d.addDevice(device) + } + }() + + // discover devicesBySerial and wait for results + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + d.conn.DiscoverDevices(ctx, devices, "") + cancel() + close(devices) + + // mark discover as done + atomic.AddUint32(&d.done, 1) +} + +func (d *Discoverer) get(serial uint32, password string) *Device { + d.mux.RLock() + defer d.mux.RUnlock() + + device := d.devices[serial] + if device != nil { + device.SetPassword(password) + } + return device +} + +// DeviceBySerial with the given serial number +func (d *Discoverer) DeviceBySerial(serial uint32, password string) *Device { + start := time.Now() + for time.Since(start) < time.Second*3 { + // discover done -> return immediately regardless of result + if atomic.LoadUint32(&d.done) != 0 { + return d.get(serial, password) + } + + // Device with serial found -> return + if device := d.get(serial, password); device != nil { + return device + } + + time.Sleep(time.Millisecond * 10) + } + return d.get(serial, password) +} + +// DeviceByIP with the given serial number +func (d *Discoverer) DeviceByIP(ip, password string) (*Device, error) { + d.mux.Lock() + defer d.mux.Unlock() + + for _, device := range d.devices { + if device.Address().IP.String() == ip { + device.SetPassword(password) + return device, nil + } + } + + device, err := d.conn.NewDevice(ip, password) + if err != nil { + return nil, fmt.Errorf("failed to get device: %w", err) + } + + dev := d.createDevice(device) + d.devices[device.SerialNumber()] = dev + return dev, err +} From 074d97e1d24f2b2a570e309ebfc4c4309cca1505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Thu, 24 Jun 2021 21:13:50 +0200 Subject: [PATCH 2/6] updated readme for SMA plugin --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a323effa3..0f0af17c2b 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ Available meter implementations are: - `modbus`: ModBus meters as supported by [MBMD](https://github.com/volkszaehler/mbmd#supported-devices). Configuration is similar to the [ModBus plugin](#modbus-readwrite) where `power` and `energy` specify the MBMD measurement value to use. Additionally, `soc` can specify an MBMD measurement value for home battery soc. Typical values are `power: Power`, `energy: Sum` and `soc: ChargeState` where only `power` applied per default. - `openwb`: OpenWB meters. Use `usage` to choose meter type: `grid`/`pv`/`battery`. -- `sma`: SMA Home Manager 2.0 and SMA Energy Meter. Power reading is configured out of the box but can be customized if necessary. To obtain specific energy readings define the desired Obis code (Import Energy: "1:1.8.0", Export Energy: "1:2.8.0"). +- `sma`: SMA Home Manager 2.0, SMA Energy Meter and Inverters via SMA Speedwire. - `tesla`: Tesla PowerWall meter. Use `usage` to choose meter type: `grid`/`pv`/`battery`. - `custom`: default meter implementation where meter readings- `power`, `energy`, per-phase `currents` and battery `soc` are configured using [plugins](#plugins) @@ -448,6 +448,29 @@ scale: 0.001 # floating point factor applied to result, e.g. for Wh to kWh conve timeout: 30s # error if no update received in 30 seconds ``` +### SMA/Speedwire (read only) + +The `sma` plugin provides an interface to SMA devices via the Speedwire protocol. + +Sample configuration (read only): + +```yaml +source: sma +uri: 192.168.4.51 # alternative to serial +serial: 123456 # alternative to uri +value: ActivePowerPlus # ID of value to read +password: "0000" # optional (default: 0000) +interface: "eth0" # optional +scale: 1 # optional scale factor for value +``` + +Supported values for `value` can be found in the diagnostic dump of the command +`evcc meter` (with a configured SMA meter). + +All possible values can be found as const [here](https://gitlab.com/bboehmke/sunny/-/blob/master/values.go#L24) +(use the names of the const for `value`). + + ### Javascript (read/write) EVCC includes a bundled Javascript interpreter with Underscore.js library installed. The `js` plugin is able to execute Javascript code from the `script` tag. Useful for quick prototyping: From af7684b9019e0458d5905c1fc1b1d9cbe422469e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Fri, 25 Jun 2021 14:59:10 +0200 Subject: [PATCH 3/6] changes based on review --- README.md | 3 ++- provider/sma.go | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f0af17c2b..f1f0b3dfde 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G - [MQTT (read/write)](#mqtt-readwrite) - [HTTP (read/write)](#http-readwrite) - [Websocket (read only)](#websocket-read-only) + - [SMA/Speedwire (read only)](#smaspeedwire-read-only) - [Javascript (read/write)](#javascript-readwrite) - [Shell Script (read/write)](#shell-script-readwrite) - [Calc (read only)](#calc-read-only) @@ -460,7 +461,7 @@ uri: 192.168.4.51 # alternative to serial serial: 123456 # alternative to uri value: ActivePowerPlus # ID of value to read password: "0000" # optional (default: 0000) -interface: "eth0" # optional +interface: eth0 # optional scale: 1 # optional scale factor for value ``` diff --git a/provider/sma.go b/provider/sma.go index a8a9d01468..906d3c5ac3 100644 --- a/provider/sma.go +++ b/provider/sma.go @@ -69,8 +69,6 @@ func NewSMAFromConfig(other map[string]interface{}) (IntProvider, error) { return provider, err } -var _ FloatProvider = (*Mqtt)(nil) - // FloatGetter creates handler for float64 func (p *SMA) FloatGetter() func() (float64, error) { return func() (float64, error) { From c5298e52845a0f795fe7a6347d96f110d6f7b7f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Fri, 25 Jun 2021 17:05:18 +0200 Subject: [PATCH 4/6] improved comment of SMA meter struct --- meter/sma.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meter/sma.go b/meter/sma.go index c243f6c08d..d1c340b50f 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -13,7 +13,7 @@ import ( "gitlab.com/bboehmke/sunny" ) -// SMA supporting SMA Home Manager 2.0 and SMA Energy Meter 30 +// SMA supporting SMA Home Manager 2.0, SMA Energy Meter 30 and SMA inverter type SMA struct { log *util.Logger uri string From 5dafdac3eff59051604466cadda98c2ade797f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Sun, 4 Jul 2021 12:23:40 +0200 Subject: [PATCH 5/6] renamed GetValues() to Values() --- meter/sma.go | 12 ++++++------ provider/sma.go | 2 +- provider/sma/device.go | 2 +- provider/sma/discover.go | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/meter/sma.go b/meter/sma.go index d1c340b50f..a72023ba14 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -85,7 +85,7 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64) (api.Mete // decorate api.Battery in case of inverter var soc func() (float64, error) if !sm.device.IsEnergyMeter() { - vals, err := sm.device.GetValues() + vals, err := sm.device.Values() if err != nil { return nil, err } @@ -100,19 +100,19 @@ func NewSMA(uri, password, iface string, serial uint32, scale float64) (api.Mete // CurrentPower implements the api.Meter interface func (sm *SMA) CurrentPower() (float64, error) { - values, err := sm.device.GetValues() + values, err := sm.device.Values() return sm.scale * (sma.AsFloat(values[sunny.ActivePowerPlus]) - sma.AsFloat(values[sunny.ActivePowerMinus])), err } // TotalEnergy implements the api.MeterEnergy interface func (sm *SMA) TotalEnergy() (float64, error) { - values, err := sm.device.GetValues() + values, err := sm.device.Values() return sma.AsFloat(values[sunny.ActiveEnergyPlus]) / 3600000, err } // Currents implements the api.MeterCurrent interface func (sm *SMA) Currents() (float64, float64, float64, error) { - values, err := sm.device.GetValues() + values, err := sm.device.Values() measurements := []sunny.ValueID{sunny.CurrentL1, sunny.CurrentL2, sunny.CurrentL3} var vals [3]float64 @@ -125,7 +125,7 @@ func (sm *SMA) Currents() (float64, float64, float64, error) { // soc implements the api.Battery interface func (sm *SMA) soc() (float64, error) { - values, err := sm.device.GetValues() + values, err := sm.device.Values() return sma.AsFloat(values[sunny.BatteryCharge]), err } @@ -147,7 +147,7 @@ func (sm *SMA) Diagnose() { } fmt.Fprintln(w) - if values, err := sm.device.GetValues(); err == nil { + if values, err := sm.device.Values(); err == nil { ids := make([]sunny.ValueID, 0, len(values)) for k := range values { ids = append(ids, k) diff --git a/provider/sma.go b/provider/sma.go index 906d3c5ac3..27a76d0a0f 100644 --- a/provider/sma.go +++ b/provider/sma.go @@ -72,7 +72,7 @@ func NewSMAFromConfig(other map[string]interface{}) (IntProvider, error) { // FloatGetter creates handler for float64 func (p *SMA) FloatGetter() func() (float64, error) { return func() (float64, error) { - values, err := p.device.GetValues() + values, err := p.device.Values() if err != nil { return 0, err } diff --git a/provider/sma/device.go b/provider/sma/device.go index 54b56d8e79..61dfeddce5 100644 --- a/provider/sma/device.go +++ b/provider/sma/device.go @@ -30,7 +30,7 @@ func (d *Device) updateValues() error { return err } -func (d *Device) GetValues() (map[sunny.ValueID]interface{}, error) { +func (d *Device) Values() (map[sunny.ValueID]interface{}, error) { elapsed := d.mux.LockWithTimeout() defer d.mux.Unlock() diff --git a/provider/sma/discover.go b/provider/sma/discover.go index d9c05a2304..82fee84188 100644 --- a/provider/sma/discover.go +++ b/provider/sma/discover.go @@ -130,7 +130,7 @@ func (d *Discoverer) DeviceBySerial(serial uint32, password string) *Device { return d.get(serial, password) } - // Device with serial found -> return + // device with serial found -> return if device := d.get(serial, password); device != nil { return device } From 6518326e88691e27d3426eb75081a5eb20b521e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20B=C3=B6hmke?= Date: Sun, 4 Jul 2021 12:36:03 +0200 Subject: [PATCH 6/6] reworked Currents() for SMA meter --- meter/sma.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/meter/sma.go b/meter/sma.go index a72023ba14..89c86692b4 100644 --- a/meter/sma.go +++ b/meter/sma.go @@ -114,13 +114,12 @@ func (sm *SMA) TotalEnergy() (float64, error) { func (sm *SMA) Currents() (float64, float64, float64, error) { values, err := sm.device.Values() - measurements := []sunny.ValueID{sunny.CurrentL1, sunny.CurrentL2, sunny.CurrentL3} - var vals [3]float64 - for i := 0; i < 3; i++ { - vals[i] = sma.AsFloat(values[measurements[i]]) + var currents [3]float64 + for i, id := range []sunny.ValueID{sunny.CurrentL1, sunny.CurrentL2, sunny.CurrentL3} { + currents[i] = sma.AsFloat(values[id]) } - return vals[0], vals[1], vals[2], err + return currents[0], currents[1], currents[2], err } // soc implements the api.Battery interface