SMA: speed up device discovery (#1147)
This commit is contained in:
parent
bbc79cb992
commit
f9d7a15adb
4 changed files with 101 additions and 30 deletions
|
|
@ -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
2
go.mod
|
|
@ -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
4
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
121
meter/sma.go
121
meter/sma.go
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue