Skip to content

Commit

Permalink
Goodwe Wifi: fix timeout not handled (evcc-io#12050)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Feb 8, 2024
1 parent 0ec4afc commit aefaedd
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 59 deletions.
39 changes: 25 additions & 14 deletions meter/goodwe-wifi.go
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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)
Expand All @@ -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":
Expand All @@ -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
}
100 changes: 57 additions & 43 deletions meter/goodwe/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import (
"net"
"sync"
"time"

"github.com/evcc-io/evcc/util"
)

var (
instance *Server
mu sync.RWMutex
)

func Instance() (*Server, error) {
func Instance(log *util.Logger) (*Server, error) {
mu.Lock()
defer mu.Unlock()

Expand All @@ -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")
Expand All @@ -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
}

if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x89, 0x1C, 0x00, 0x7D, 0x7A, 0xE7}, addr); err != nil {
return
for {
mu.RLock()
ips := make([]string, 0, len(m.inverters))
for ip := range m.inverters {
ips = append(ips, ip)
}
time.Sleep(5 * time.Second)
if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x90, 0x88, 0x00, 0x0D, 0x3D, 0xB3}, addr); err != nil {
return
mu.RUnlock()

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()

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

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
monitor := m.GetInverter(ip)
if monitor == nil {
m.log.ERROR.Println("unknown inverter:", ip)
continue
}

if buf[4] == 26 {
inverter := m.inverters[ip]
inverter.Soc = float64(buf[20])
m.inverters[ip] = inverter
}
mu.Unlock()
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:])))
}

if buf[4] == 26 {
inverter.Soc = float64(buf[20])
}

return inverter
})
}
}
6 changes: 4 additions & 2 deletions meter/goodwe/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit aefaedd

Please sign in to comment.