Add GoodWe over Wifi (#11750)
This commit is contained in:
parent
f4473c17ed
commit
56a2b8db58
6 changed files with 254 additions and 0 deletions
|
|
@ -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
73
meter/goodwe-wifi.go
Normal 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
|
||||
}
|
||||
35
meter/goodwe-wifi_decorators.go
Normal file
35
meter/goodwe-wifi_decorators.go
Normal 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
109
meter/goodwe/server.go
Normal 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
18
meter/goodwe/types.go
Normal 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
|
||||
}
|
||||
17
templates/definition/meter/goodwe-wifi.yaml
Normal file
17
templates/definition/meter/goodwe-wifi.yaml
Normal 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 }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue