Add Ecoflow PowerOcean (#24012)
This commit is contained in:
parent
525480e416
commit
eda16fe102
5 changed files with 222 additions and 0 deletions
1
go.mod
1
go.mod
|
|
@ -98,6 +98,7 @@ require (
|
||||||
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
github.com/stianeikeland/go-rpio/v4 v4.6.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/teslamotors/vehicle-command v0.4.0
|
github.com/teslamotors/vehicle-command v0.4.0
|
||||||
|
github.com/tess1o/go-ecoflow v1.1.1-0.20251003083510-2ccc15a17e29
|
||||||
github.com/traefik/yaegi v0.16.1
|
github.com/traefik/yaegi v0.16.1
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
||||||
github.com/volkszaehler/mbmd v0.0.0-20250808161051-499ae856f44e
|
github.com/volkszaehler/mbmd v0.0.0-20250808161051-499ae856f44e
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -751,6 +751,8 @@ github.com/teivah/onecontext v1.3.0 h1:tbikMhAlo6VhAuEGCvhc8HlTnpX4xTNPTOseWuhO1
|
||||||
github.com/teivah/onecontext v1.3.0/go.mod h1:hoW1nmdPVK/0jrvGtcx8sCKYs2PiS4z0zzfdeuEVyb0=
|
github.com/teivah/onecontext v1.3.0/go.mod h1:hoW1nmdPVK/0jrvGtcx8sCKYs2PiS4z0zzfdeuEVyb0=
|
||||||
github.com/teslamotors/vehicle-command v0.4.0 h1:39JJ7wGdS3dmW3tlsshg2C9/YWx+fxaUw6aRKfqWmpc=
|
github.com/teslamotors/vehicle-command v0.4.0 h1:39JJ7wGdS3dmW3tlsshg2C9/YWx+fxaUw6aRKfqWmpc=
|
||||||
github.com/teslamotors/vehicle-command v0.4.0/go.mod h1:aL0IRpLu+l205NxejRkuxpqAovw69rDKriBKKyP8gLI=
|
github.com/teslamotors/vehicle-command v0.4.0/go.mod h1:aL0IRpLu+l205NxejRkuxpqAovw69rDKriBKKyP8gLI=
|
||||||
|
github.com/tess1o/go-ecoflow v1.1.1-0.20251003083510-2ccc15a17e29 h1:whgDCnunp97KCC7TF94E3l1hGo080ICSRs6ei8jCnzs=
|
||||||
|
github.com/tess1o/go-ecoflow v1.1.1-0.20251003083510-2ccc15a17e29/go.mod h1:kqrfk4Y14pHCPKIwp6o7ZbgpohUzQHh7EMX+RUA4JCU=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E=
|
||||||
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY=
|
||||||
|
|
|
||||||
162
meter/ecoflow-powerocean.go
Normal file
162
meter/ecoflow-powerocean.go
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
package meter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/evcc-io/evcc/api"
|
||||||
|
"github.com/evcc-io/evcc/util"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"github.com/tess1o/go-ecoflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EcoFlowPowerOcean represents the EcoFlow PowerOcean meter
|
||||||
|
type EcoFlowPowerOcean struct {
|
||||||
|
usage string
|
||||||
|
accessKey string
|
||||||
|
secretKey string
|
||||||
|
deviceId string
|
||||||
|
cache time.Duration
|
||||||
|
client *ecoflow.Client
|
||||||
|
dataG func() (*ecoflow.GetCmdResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
registry.Add("ecoflow-powerocean", NewEcoFlowPowerOceanFromConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEcoFlowPowerOceanFromConfig creates an EcoFlow PowerOcean meter from generic config
|
||||||
|
func NewEcoFlowPowerOceanFromConfig(other map[string]any) (api.Meter, error) {
|
||||||
|
cc := struct {
|
||||||
|
AccessKey string
|
||||||
|
SecretKey string
|
||||||
|
DeviceId string
|
||||||
|
Usage string
|
||||||
|
Cache time.Duration
|
||||||
|
}{
|
||||||
|
Cache: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := util.DecodeOther(other, &cc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cc.AccessKey == "" {
|
||||||
|
return nil, errors.New("missing access key")
|
||||||
|
}
|
||||||
|
if cc.SecretKey == "" {
|
||||||
|
return nil, errors.New("missing secret key")
|
||||||
|
}
|
||||||
|
if cc.DeviceId == "" {
|
||||||
|
return nil, errors.New("missing device ID")
|
||||||
|
}
|
||||||
|
if cc.Usage == "" {
|
||||||
|
return nil, errors.New("missing usage")
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := NewEcoFlowPowerOcean(cc.AccessKey, cc.SecretKey, cc.DeviceId, cc.Usage, cc.Cache)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cc.Usage == "battery" {
|
||||||
|
return &EcoFlowPowerOceanBattery{m}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEcoFlowPowerOcean constructs the EcoFlowPowerOcean struct
|
||||||
|
func NewEcoFlowPowerOcean(accessKey, secretKey, deviceId, usage string, cache time.Duration) (*EcoFlowPowerOcean, error) {
|
||||||
|
client := ecoflow.NewEcoflowClient(accessKey, secretKey)
|
||||||
|
m := &EcoFlowPowerOcean{
|
||||||
|
accessKey: accessKey,
|
||||||
|
secretKey: secretKey,
|
||||||
|
deviceId: deviceId,
|
||||||
|
usage: usage,
|
||||||
|
cache: cache,
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
m.dataG = util.Cached(m.getData, cache)
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getData retrieves device parameters from EcoFlow API
|
||||||
|
func (m *EcoFlowPowerOcean) getData() (*ecoflow.GetCmdResponse, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var params []string
|
||||||
|
switch m.usage {
|
||||||
|
case "grid":
|
||||||
|
params = []string{"sysGridPwr"}
|
||||||
|
case "pv":
|
||||||
|
params = []string{"mpptPwr"}
|
||||||
|
case "battery":
|
||||||
|
params = []string{"bpPwr", "bpSoc"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.client.GetDeviceParameters(ctx, m.deviceId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ api.Meter = (*EcoFlowPowerOcean)(nil)
|
||||||
|
|
||||||
|
// CurrentPower implements the api.Meter interface
|
||||||
|
func (m *EcoFlowPowerOcean) CurrentPower() (float64, error) {
|
||||||
|
response, err := m.dataG()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysGridPwr responds with int
|
||||||
|
// mpptPwr responds with an array of floats
|
||||||
|
// bpPwr responds with int
|
||||||
|
// bpSoc responds with int
|
||||||
|
switch m.usage {
|
||||||
|
case "grid":
|
||||||
|
return extractFloat(response.Data, "sysGridPwr")
|
||||||
|
case "pv":
|
||||||
|
return extractFloat(response.Data, "mpptPwr")
|
||||||
|
case "battery":
|
||||||
|
pwr, err := extractFloat(response.Data, "bpPwr")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return -pwr, nil // invert battery power: ecoflow returns negative when discharging and positive when charging.
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid usage: %s", m.usage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFloat extracts a float64 or int value from a map by key.
|
||||||
|
func extractFloat(data map[string]any, key string) (float64, error) {
|
||||||
|
if data != nil {
|
||||||
|
if v, ok := data[key]; ok {
|
||||||
|
return cast.ToFloat64E(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("data not available for key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EcoFlowPowerOceanBattery represents the EcoFlow PowerOcean battery decorator
|
||||||
|
type EcoFlowPowerOceanBattery struct {
|
||||||
|
*EcoFlowPowerOcean
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soc implements the api.Battery interface for battery usage
|
||||||
|
func (m *EcoFlowPowerOceanBattery) Soc() (float64, error) {
|
||||||
|
response, err := m.dataG()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access the data from the GetCmdResponse
|
||||||
|
if response.Data != nil {
|
||||||
|
if soc, ok := response.Data["bpSoc"]; ok {
|
||||||
|
return cast.ToFloat64E(soc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, api.ErrNotAvailable
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,7 @@ var acceptable = []string{
|
||||||
"no Speedwire ping response for 127.0.0.1", // SMA
|
"no Speedwire ping response for 127.0.0.1", // SMA
|
||||||
"no such network interface", // SMA
|
"no such network interface", // SMA
|
||||||
"missing config values: username, password, key", // E3DC
|
"missing config values: username, password, key", // E3DC
|
||||||
|
"missing access key", // Ecoflow
|
||||||
"eebus not configured", // EEBus
|
"eebus not configured", // EEBus
|
||||||
"missing token", // HomeAssistant
|
"missing token", // HomeAssistant
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
templates/definition/meter/ecoflow-powerocean.yaml
Normal file
56
templates/definition/meter/ecoflow-powerocean.yaml
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
template: ecoflow-powerocean
|
||||||
|
products:
|
||||||
|
- brand: EcoFlow
|
||||||
|
description:
|
||||||
|
generic: PowerOcean
|
||||||
|
requirements:
|
||||||
|
description:
|
||||||
|
de: |
|
||||||
|
Für die Nutzung des EcoFlow PowerOcean Meters benötigen Sie:
|
||||||
|
- Einen gültigen Access Key von der EcoFlow Developer Console
|
||||||
|
- Den entsprechenden Secret Key
|
||||||
|
- Die Device ID Ihres PowerOcean Systems
|
||||||
|
|
||||||
|
Diese Credentials erhalten Sie über die EcoFlow Developer Console nach der Registrierung Ihrer Anwendung.
|
||||||
|
en: |
|
||||||
|
To use the EcoFlow PowerOcean meter you need:
|
||||||
|
- A valid Access Key from the EcoFlow Developer Console
|
||||||
|
- The corresponding Secret Key
|
||||||
|
- The Device ID of your PowerOcean system
|
||||||
|
|
||||||
|
These credentials can be obtained through the EcoFlow Developer Console after registering your application.
|
||||||
|
params:
|
||||||
|
- name: usage
|
||||||
|
choice: ["grid", "pv", "battery"]
|
||||||
|
allinone: true
|
||||||
|
- name: accesskey
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
generic: Access Key
|
||||||
|
help:
|
||||||
|
en: Access Key from EcoFlow Developer Console
|
||||||
|
de: Access Key aus der EcoFlow Developer Console
|
||||||
|
- name: secretkey
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
generic: Secret Key
|
||||||
|
help:
|
||||||
|
en: Secret Key from EcoFlow Developer Console
|
||||||
|
de: Secret Key aus der EcoFlow Developer Console
|
||||||
|
- name: deviceid
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
generic: Device ID
|
||||||
|
help:
|
||||||
|
en: Device ID of your EcoFlow PowerOcean system (e.g., HJ31ZD1AZH5G0342)
|
||||||
|
de: Geräte-ID Ihres EcoFlow PowerOcean Systems (z.B. HJ31ZD1AZH5G0342)
|
||||||
|
- name: cache
|
||||||
|
default: 30s
|
||||||
|
advanced: true
|
||||||
|
render: |
|
||||||
|
type: ecoflow-powerocean
|
||||||
|
accesskey: {{ .accesskey }}
|
||||||
|
secretkey: {{ .secretkey }}
|
||||||
|
deviceid: {{ .deviceid }}
|
||||||
|
usage: {{ .usage }}
|
||||||
|
cache: {{ .cache }}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue