Add GoodWe over Wifi (#11750)

This commit is contained in:
motze92 2024-01-26 15:58:05 +01:00 • committed by GitHub
parent f4473c17ed
commit 56a2b8db58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 254 additions and 0 deletions

View file

@ -90,6 +90,8 @@ EXPOSE 7070/tcp
EXPOSE 7090/udp
# OCPP charger
EXPOSE 8887/tcp
# GoodWe Wifi Inverter
EXPOSE 8899/udp
# SMA Energy Manager
EXPOSE 9522/udp

73
meter/goodwe-wifi.go Normal file
View file

@ -0,0 +1,73 @@
package meter
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/meter/goodwe"
"github.com/evcc-io/evcc/util"
)
type goodWeWiFi struct {
*goodwe.Server
usage string
uri string
}
func init() {
registry.Add("goodwe-wifi", NewGoodWeWifiFromConfig)
}
//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 {
capacity `mapstructure:",squash"`
URI, Usage string
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewGoodWeWiFi(cc.URI, cc.Usage)
}
func NewGoodWeWiFi(uri string, usage string) (api.Meter, error) {
instance, err := goodwe.Instance()
if err != nil {
return nil, err
}
res := &goodWeWiFi{
Server: instance,
usage: usage,
uri: uri,
}
instance.AddInverter(uri)
// decorate api.BatterySoc
var batterySoc func() (float64, error)
if usage == "battery" {
batterySoc = res.batterySoc
}
return decorateGoodWeWifi(res, batterySoc), nil
}
func (m *goodWeWiFi) CurrentPower() (float64, error) {
data := m.GetInverter(m.uri)
switch m.usage {
case "grid":
return data.NetPower, nil
case "pv":
return data.PvPower, nil
case "battery":
return data.BatteryPower, nil
}
return 0, api.ErrNotAvailable
}
func (m *goodWeWiFi) batterySoc() (float64, error) {
return m.GetInverter(m.uri).Soc, nil
}

View file

@ -0,0 +1,35 @@
package meter
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateGoodWeWifi(base *goodWeWiFi, battery func() (float64, error)) api.Meter {
switch {
case battery == nil:
return base
case battery != nil:
return &struct {
*goodWeWiFi
api.Battery
}{
goodWeWiFi: base,
Battery: &decorateGoodWeWifiBatteryImpl{
battery: battery,
},
}
}
return nil
}
type decorateGoodWeWifiBatteryImpl struct {
battery func() (float64, error)
}
func (impl *decorateGoodWeWifiBatteryImpl) Soc() (float64, error) {
return impl.battery()
}

109
meter/goodwe/server.go Normal file
View file

@ -0,0 +1,109 @@
package goodwe
import (
"encoding/binary"
"net"
"sync"
"time"
)
var (
instance *Server
mu sync.RWMutex
)
func Instance() (*Server, error) {
mu.Lock()
defer mu.Unlock()
if instance != nil {
return instance, nil
}
instance = &Server{
inverters: make(map[string]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, err
}
func (m *Server) AddInverter(ip string) {
mu.Lock()
defer mu.Unlock()
m.inverters[ip] = Inverter{IP: ip}
}
func (m *Server) GetInverter(uri string) Inverter {
mu.RLock()
defer mu.RUnlock()
return m.inverters[uri]
}
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
}
time.Sleep(5 * time.Second)
if _, err := m.conn.WriteToUDP([]byte{0xF7, 0x03, 0x90, 0x88, 0x00, 0x0D, 0x3D, 0xB3}, addr); err != nil {
return
}
}
m.readData()
}
func (m *Server) listen() {
for {
buf := make([]byte, 1024)
_, addr, err := m.conn.ReadFromUDP(buf)
if err != nil {
continue
}
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
}
if buf[4] == 26 {
inverter := m.inverters[ip]
inverter.Soc = float64(buf[20])
m.inverters[ip] = inverter
}
mu.Unlock()
}
}

18
meter/goodwe/types.go Normal file
View file

@ -0,0 +1,18 @@
package goodwe
import (
"net"
)
type Server struct {
conn *net.UDPConn
inverters map[string]Inverter
}
type Inverter struct {
IP string
PvPower float64
NetPower float64
BatteryPower float64
Soc float64
}

View file

@ -0,0 +1,17 @@
template: goodwe-wifi
products:
- brand: GoodWe
description:
generic: GoodWe over Wifi
params:
- name: usage
choice: ["grid", "pv", "battery"]
allinone: true
- name: uri
description:
en: IP address of the GoodWe inverter.
de: IP-Adresse des GoodWe Wechselrichters.
render: |
type: goodwe-wifi
usage: {{ .usage }}
uri: {{ .uri }}