From aefaeddecc2bf5e19a75779b3cce6b7540df5d51 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 8 Feb 2024 13:02:27 +0100 Subject: [PATCH] Goodwe Wifi: fix timeout not handled (#12050) --- meter/goodwe-wifi.go | 39 ++++++++++------ meter/goodwe/server.go | 100 +++++++++++++++++++++++------------------ meter/goodwe/types.go | 6 ++- 3 files changed, 86 insertions(+), 59 deletions(-) diff --git a/meter/goodwe-wifi.go b/meter/goodwe-wifi.go index 26652e21c..c8ea6abf6 100644 --- a/meter/goodwe-wifi.go +++ b/meter/goodwe-wifi.go @@ -1,15 +1,17 @@ package meter import ( + "time" + "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/meter/goodwe" "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/request" ) type goodWeWiFi struct { - *goodwe.Server - usage string - uri string + usage string + inverter *util.Monitor[goodwe.Inverter] } func init() { @@ -19,31 +21,36 @@ func init() { //go:generate go run ../cmd/tools/decorate.go -f decorateGoodWeWifi -b *goodWeWiFi -r api.Meter -t "api.Battery,Soc,func() (float64, error)" func NewGoodWeWifiFromConfig(other map[string]interface{}) (api.Meter, error) { - var cc struct { + cc := struct { capacity `mapstructure:",squash"` URI, Usage string + Timeout time.Duration + }{ + Timeout: request.Timeout, } if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - return NewGoodWeWiFi(cc.URI, cc.Usage) + return NewGoodWeWiFi(cc.URI, cc.Usage, cc.Timeout) } -func NewGoodWeWiFi(uri string, usage string) (api.Meter, error) { - instance, err := goodwe.Instance() +func NewGoodWeWiFi(uri, usage string, timeout time.Duration) (api.Meter, error) { + instance, err := goodwe.Instance(util.NewLogger("goodwe-wifi")) if err != nil { return nil, err } - res := &goodWeWiFi{ - Server: instance, - usage: usage, - uri: uri, + inverter := instance.GetInverter(uri) + if inverter == nil { + inverter = instance.AddInverter(uri, timeout) } - instance.AddInverter(uri) + res := &goodWeWiFi{ + usage: usage, + inverter: inverter, + } // decorate api.BatterySoc var batterySoc func() (float64, error) @@ -55,7 +62,10 @@ func NewGoodWeWiFi(uri string, usage string) (api.Meter, error) { } func (m *goodWeWiFi) CurrentPower() (float64, error) { - data := m.GetInverter(m.uri) + data, err := m.inverter.Get() + if err != nil { + return 0, err + } switch m.usage { case "grid": @@ -69,5 +79,6 @@ func (m *goodWeWiFi) CurrentPower() (float64, error) { } func (m *goodWeWiFi) batterySoc() (float64, error) { - return m.GetInverter(m.uri).Soc, nil + data, err := m.inverter.Get() + return data.Soc, err } diff --git a/meter/goodwe/server.go b/meter/goodwe/server.go index 9a847c3ac..ac40acfaa 100644 --- a/meter/goodwe/server.go +++ b/meter/goodwe/server.go @@ -5,6 +5,8 @@ import ( "net" "sync" "time" + + "github.com/evcc-io/evcc/util" ) var ( @@ -12,7 +14,7 @@ var ( mu sync.RWMutex ) -func Instance() (*Server, error) { +func Instance(log *util.Logger) (*Server, error) { mu.Lock() defer mu.Unlock() @@ -21,7 +23,8 @@ func Instance() (*Server, error) { } instance = &Server{ - inverters: make(map[string]Inverter), + log: log, + inverters: make(map[string]*util.Monitor[Inverter]), } addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:8899") @@ -40,70 +43,81 @@ func Instance() (*Server, error) { return instance, err } -func (m *Server) AddInverter(ip string) { +func (m *Server) AddInverter(ip string, timeout time.Duration) *util.Monitor[Inverter] { mu.Lock() defer mu.Unlock() - m.inverters[ip] = Inverter{IP: ip} + monitor := util.NewMonitor[Inverter](timeout) + m.inverters[ip] = monitor + return monitor } -func (m *Server) GetInverter(uri string) Inverter { +func (m *Server) GetInverter(ip string) *util.Monitor[Inverter] { mu.RLock() defer mu.RUnlock() - return m.inverters[uri] + return m.inverters[ip] } func (m *Server) readData() { - for _, inverter := range m.inverters { - addr, err := net.ResolveUDPAddr("udp", inverter.IP+":8899") - if err != nil { - return + for { + mu.RLock() + ips := make([]string, 0, len(m.inverters)) + for ip := range m.inverters { + ips = append(ips, ip) } + mu.RUnlock() - if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x89, 0x1C, 0x00, 0x7D, 0x7A, 0xE7}, addr); err != nil { - return - } - time.Sleep(5 * time.Second) - if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x90, 0x88, 0x00, 0x0D, 0x3D, 0xB3}, addr); err != nil { - return + for _, ip := range ips { + addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(ip, "8899")) + if err != nil { + m.log.ERROR.Println(err) + continue + } + if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x89, 0x1C, 0x00, 0x7D, 0x7A, 0xE7}, addr); err != nil { + m.log.ERROR.Println(err) + continue + } + time.Sleep(5 * time.Second) + if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x90, 0x88, 0x00, 0x0D, 0x3D, 0xB3}, addr); err != nil { + m.log.ERROR.Println(err) + continue + } } } - m.readData() } func (m *Server) listen() { for { buf := make([]byte, 1024) - _, addr, err := m.conn.ReadFromUDP(buf) + n, addr, err := m.conn.ReadFromUDP(buf) if err != nil { + m.log.ERROR.Println(err) + continue + } + m.log.TRACE.Printf("recv from %s: % X", addr, buf[:n]) + + ip := addr.IP.String() + monitor := m.GetInverter(ip) + if monitor == nil { + m.log.ERROR.Println("unknown inverter:", ip) continue } - ip := addr.IP.String() + monitor.SetFunc(func(inverter Inverter) Inverter { + if buf[4] == 250 { + ui := func(u, i int16) float64 { + return float64(int16(binary.BigEndian.Uint16(buf[u:]))) * + float64(int16(binary.BigEndian.Uint16(buf[i:]))) / 100 + } + inverter.PvPower = ui(11, 13) + ui(19, 21) + inverter.BatteryPower = ui(165, 167) + inverter.NetPower = -float64(int32(binary.BigEndian.Uint32(buf[83:]))) + } - mu.Lock() - if buf[4] == 250 { - vPv1 := float64(int16(binary.BigEndian.Uint16(buf[11:]))) * 0.1 - vPv2 := float64(int16(binary.BigEndian.Uint16(buf[19:]))) * 0.1 - iPv1 := float64(int16(binary.BigEndian.Uint16(buf[13:]))) * 0.1 - iPv2 := float64(int16(binary.BigEndian.Uint16(buf[21:]))) * 0.1 - iBatt := float64(int16(binary.BigEndian.Uint16(buf[167:]))) * 0.1 - vBatt := float64(int16(binary.BigEndian.Uint16(buf[165:]))) * 0.1 + if buf[4] == 26 { + inverter.Soc = float64(buf[20]) + } - pvPower := vPv1*iPv1 + vPv2*iPv2 - - inverter := m.inverters[ip] - inverter.PvPower = pvPower - inverter.BatteryPower = vBatt * iBatt - inverter.NetPower = -float64(int32(binary.BigEndian.Uint32(buf[83:]))) - - m.inverters[ip] = inverter - } - - if buf[4] == 26 { - inverter := m.inverters[ip] - inverter.Soc = float64(buf[20]) - m.inverters[ip] = inverter - } - mu.Unlock() + return inverter + }) } } diff --git a/meter/goodwe/types.go b/meter/goodwe/types.go index 06bb99862..02c372eb2 100644 --- a/meter/goodwe/types.go +++ b/meter/goodwe/types.go @@ -2,15 +2,17 @@ package goodwe import ( "net" + + "github.com/evcc-io/evcc/util" ) type Server struct { + log *util.Logger conn *net.UDPConn - inverters map[string]Inverter + inverters map[string]*util.Monitor[Inverter] } type Inverter struct { - IP string PvPower float64 NetPower float64 BatteryPower float64