evcc-io/meter/goodwe/server.go
andig 8b52365011
Some checks failed
Release / Hassio Addon (push) Has been cancelled
Release / call-build-workflow (push) Has been cancelled
Release / Publish Docker :release (push) Has been cancelled
Release / Github & APT (push) Has been cancelled
Release / Demo (push) Has been cancelled
chore: align logging
2026-06-19 17:49:21 +02:00

126 lines
2.6 KiB
Go

package goodwe
import (
"encoding/binary"
"maps"
"net"
"slices"
"sync"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/evcc-io/evcc/util"
)
var (
instance *Server
mu sync.RWMutex
)
func Instance(log *util.Logger) (*Server, error) {
mu.Lock()
defer mu.Unlock()
if instance != nil {
return instance, nil
}
instance = &Server{
log: log,
inverters: make(map[string]*util.Monitor[Inverter]),
}
addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:8899")
if err != nil {
return nil, err
}
instance.conn, err = net.ListenUDP("udp", addr)
if err != nil {
return nil, err
}
go instance.listen()
go instance.readData()
return instance, nil
}
func (m *Server) AddInverter(ip string, timeout time.Duration) *util.Monitor[Inverter] {
mu.Lock()
defer mu.Unlock()
monitor := util.NewMonitor[Inverter](timeout)
m.inverters[ip] = monitor
return monitor
}
func (m *Server) GetInverter(ip string) *util.Monitor[Inverter] {
mu.RLock()
defer mu.RUnlock()
return m.inverters[ip]
}
func (m *Server) readData() {
bo := backoff.NewExponentialBackOff(
backoff.WithMaxInterval(time.Second),
backoff.WithMaxElapsedTime(10*time.Second))
for {
mu.RLock()
ips := slices.Collect(maps.Keys(m.inverters))
mu.RUnlock()
for _, ip := range ips {
if err := backoff.Retry(func() error {
addr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(ip, "8899"))
if err == nil {
_, err = m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x89, 0x1C, 0x00, 0x7D, 0x7A, 0xE7}, addr)
}
if err == nil {
time.Sleep(5 * time.Second)
_, err = m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x90, 0x88, 0x00, 0x0D, 0x3D, 0xB3}, addr)
}
return err
}, bo); err != nil {
m.log.ERROR.Println(err)
}
}
}
}
func (m *Server) listen() {
for {
buf := make([]byte, 1024)
n, addr, err := m.conn.ReadFromUDP(buf)
if err != nil {
m.log.ERROR.Println(err)
continue
}
m.log.TRACE.Printf("recv %s: % X", addr, buf[:n])
ip := addr.IP.String()
monitor := m.GetInverter(ip)
if monitor == nil {
m.log.ERROR.Println("unknown inverter:", ip)
continue
}
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) + ui(27, 29) + ui(35, 37)
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
})
}
}