SMA: speed up device discovery (#1147)

This commit is contained in:
Benjamin Böhmke 2021-06-12 20:34:15 +02:00 • committed by GitHub
parent bbc79cb992
commit f9d7a15adb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 30 deletions

View file

@ -68,11 +68,13 @@ func (h *SMAHandler) Test(log *util.Logger, in ResultDetails) (res []ResultDetai
return nil
}
devices, err := sunny.DiscoverDevices(h.Password)
connection, err := sunny.NewConnection("")
if err != nil {
log.ERROR.Println("sma:", err)
return nil
}
devices := connection.SimpleDiscoverDevices(h.Password)
h.handled = true
h.mux.Unlock()

2
go.mod
View file

@ -70,7 +70,7 @@ require (
github.com/thoas/go-funk v0.8.0
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
github.com/volkszaehler/mbmd v0.0.0-20210117183837-59dcc46d62d4
gitlab.com/bboehmke/sunny v0.11.1
gitlab.com/bboehmke/sunny v0.12.1
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20210508051633-16afe75a6701

4
go.sum
View file

@ -721,8 +721,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
gitlab.com/bboehmke/sunny v0.11.1 h1:AmGqCXAgDbQFwGjNQ/+K1jGDwAPDL4q6zGtW+ux7zlI=
gitlab.com/bboehmke/sunny v0.11.1/go.mod h1:o0e0jA5xTQ7JpQ2FO/C8N21gSV47g64EC+dRUQui+CM=
gitlab.com/bboehmke/sunny v0.12.1 h1:SGFssDJNenC5csLHjtYsuFpOO+2Wd0woJyLBwfygr9g=
gitlab.com/bboehmke/sunny v0.12.1/go.mod h1:o0e0jA5xTQ7JpQ2FO/C8N21gSV47g64EC+dRUQui+CM=
go.coder.com/go-tools v0.0.0-20190317003359-0c6a35b74a16/go.mod h1:iKV5yK9t+J5nG9O3uF6KYdPEz3dyfMyB15MN1rbQ8Qw=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=

View file

@ -1,12 +1,15 @@
package meter
import (
"context"
"errors"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/andig/evcc/api"
@ -16,6 +19,76 @@ import (
const udpTimeout = 10 * time.Second
// SMADiscoverHelper discovers SMA devices in background while providing already found devices
type SMADiscoverHelper struct {
conn *sunny.Connection
devices map[string]*sunny.Device
devicesMutex sync.RWMutex
done uint32
}
// NewSMADiscoverHelper creates discovery helper for given connection (normally one per interface)
func NewSMADiscoverHelper(conn *sunny.Connection) *SMADiscoverHelper {
discover := SMADiscoverHelper{
conn: conn,
devices: make(map[string]*sunny.Device),
}
go discover.run()
return &discover
}
// run discover and store found devices
func (d *SMADiscoverHelper) run() {
devices := make(chan *sunny.Device, 10)
var wg sync.WaitGroup
wg.Add(1)
go func() {
for device := range devices {
d.devicesMutex.Lock()
d.devices[strconv.FormatInt(int64(device.SerialNumber()), 10)] = device
d.devicesMutex.Unlock()
}
wg.Done()
}()
// discover devices and wait for results
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
d.conn.DiscoverDevices(ctx, devices, "")
cancel()
close(devices)
wg.Wait()
// mark discover as done
atomic.AddUint32(&d.done, 1)
}
func (d *SMADiscoverHelper) get(serial string) *sunny.Device {
d.devicesMutex.RLock()
defer d.devicesMutex.RUnlock()
return d.devices[serial]
}
// GetDevice with the given serial number
func (d *SMADiscoverHelper) GetDevice(serial string) *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
} else if device := d.get(serial); device != nil {
return device
}
time.Sleep(time.Millisecond * 10)
}
return d.get(serial)
}
// values bundles SMA readings
type values struct {
power float64
@ -63,6 +136,9 @@ func NewSMAFromConfig(other map[string]interface{}) (api.Meter, error) {
return NewSMA(cc.URI, cc.Password, cc.Serial, cc.Interface, cc.Power, cc.Energy, cc.Scale)
}
// map of created discover instances
var discovers = make(map[string]*SMADiscoverHelper)
// NewSMA creates a SMA Meter
func NewSMA(uri, password, serial, iface, power, energy string, scale float64) (api.Meter, error) {
log := util.NewLogger("sma")
@ -76,12 +152,6 @@ func NewSMA(uri, password, serial, iface, power, energy string, scale float64) (
log.WARN.Println("SMA energy not supported -> ignoring")
}
if iface != "" {
if err := sunny.SetMulticastInterface(iface); err != nil {
return nil, err
}
}
sm := &SMA{
mux: util.NewWaiter(udpTimeout, func() { log.TRACE.Println("wait for initial value") }),
log: log,
@ -92,42 +162,41 @@ func NewSMA(uri, password, serial, iface, power, energy string, scale float64) (
scale: scale,
}
var err error
conn, err := sunny.NewConnection(iface)
if err != nil {
return nil, fmt.Errorf("failed to get SMA connection: %w", err)
}
if uri != "" {
sm.device, err = sunny.NewDevice(uri, password)
sm.device, err = conn.NewDevice(uri, password)
if err != nil {
return nil, err
}
} else if serial != "" {
// list all devices
devices, err := sunny.DiscoverDevices(password)
if err != nil {
return nil, err
}
// check if device with serial number is present
for _, device := range devices {
if serial == strconv.FormatInt(int64(device.SerialNumber()), 10) {
sm.device = device
}
if _, ok := discovers[iface]; !ok {
discovers[iface] = NewSMADiscoverHelper(conn)
}
sm.device = discovers[iface].GetDevice(serial)
if sm.device == nil {
return nil, fmt.Errorf("failed to find device with serial: %s", serial)
}
sm.device.SetPassword(password)
} else {
return nil, errors.New("missing uri or serial")
}
vals, err := sm.device.GetValues()
if err != nil {
return nil, err
}
// decorate api.Battery
var soc func() (float64, error)
if _, ok := vals["battery_charge"]; ok {
soc = sm.soc
if !sm.device.IsEnergyMeter() { // only for inverters possible
vals, err := sm.device.GetValues()
if err != nil {
return nil, err
}
if _, ok := vals["battery_charge"]; ok {
soc = sm.soc
}
}
go func() {