Goodwe Wifi: fix timeout not handled (#12050)

This commit is contained in:
andig 2024-02-08 13:02:27 +01:00 • committed by GitHub
parent 0ec4afc3dc
commit aefaeddecc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 59 deletions

View file

@ -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
}

View file

@ -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
})
}
}

View file

@ -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