Warp2: support energy manager (BC) (#7995)

This commit is contained in:
andig 2023-05-19 16:31:06 +02:00 • committed by GitHub
parent cf5394484c
commit ea6d1601f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 551 additions and 569 deletions

View file

@ -41,7 +41,7 @@ func init() {
registry.Add("go-e", NewGoEFromConfig)
}
// go:generate go run ../cmd/tools/decorate.go -f decorateGoE -b *GoE -r api.Charger -t "api.MeterEnergy,func() (float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) (error)"
// go:generate go run ../cmd/tools/decorate.go -f decorateGoE -b *GoE -r api.Charger -t "api.MeterEnergy,func() (float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewGoEFromConfig creates a go-e charger from generic config
func NewGoEFromConfig(other map[string]interface{}) (api.Charger, error) {

View file

@ -96,7 +96,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
return decorateOCPP(c, powerG, totalEnergyG, currentsG, phasesS), nil
}
// go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) (error)"
// go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewOCPP creates OCPP charger
func NewOCPP(id string, connector int, idtag string,

View file

@ -29,7 +29,7 @@ type OpenWB struct {
authS func(string) error
}
// go:generate go run ../cmd/tools/decorate.go -f decorateOpenWB -b *OpenWB -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) (error)" -t "api.Battery,Soc,func() (float64, error)"
// go:generate go run ../cmd/tools/decorate.go -f decorateOpenWB -b *OpenWB -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Battery,Soc,func() (float64, error)"
// NewOpenWBFromConfig creates a new configurable charger
func NewOpenWBFromConfig(other map[string]interface{}) (api.Charger, error) {

View file

@ -1,313 +0,0 @@
package charger
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/warp"
v1 "github.com/evcc-io/evcc/charger/warp/v1"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/provider/mqtt"
"github.com/evcc-io/evcc/util"
)
// Warp is the Warp charger implementation
type Warp struct {
log *util.Logger
root string
client *mqtt.Client
enabledG func() (string, error)
statusG func() (string, error)
meterG func() (string, error)
meterDetailsG func() (string, error)
enableS func(bool) error
maxcurrentS func(int64) error
enabled bool // cache
}
func init() {
registry.Add("warp", NewWarpFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)"
// NewWarpFromConfig creates a new configurable charger
func NewWarpFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
mqtt.Config `mapstructure:",squash"`
Topic string
Timeout time.Duration
}{
Topic: warp.RootTopic,
Timeout: warp.Timeout,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
wb, err := NewWarp(cc.Config, cc.Topic, cc.Timeout)
if err != nil {
return nil, err
}
hasMeter := wb.hasMeter()
var currentPower, totalEnergy func() (float64, error)
if hasMeter {
currentPower = wb.currentPower
totalEnergy = wb.totalEnergy
}
var currents func() (float64, float64, float64, error)
if hasMeter && wb.hasCurrents() {
currents = wb.currents
}
return decorateWarp(wb, currentPower, totalEnergy, currents), err
}
// NewWarp creates a new configurable charger
func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, error) {
log := util.NewLogger("warp")
client, err := mqtt.RegisteredClientOrDefault(log, mqttconf)
if err != nil {
return nil, err
}
wb := &Warp{
log: log,
root: topic,
client: client,
}
// timeout handler
to := provider.NewTimeoutHandler(provider.NewMqtt(log, client,
fmt.Sprintf("%s/evse/state", topic), timeout,
).StringGetter())
stringG := func(topic string) func() (string, error) {
g := provider.NewMqtt(log, client, topic, 0).StringGetter()
return to.StringGetter(g)
}
wb.enabledG = stringG(fmt.Sprintf("%s/evse/auto_start_charging", topic))
wb.statusG = stringG(fmt.Sprintf("%s/evse/state", topic))
wb.meterG = stringG(fmt.Sprintf("%s/meter/state", topic))
wb.meterDetailsG = stringG(fmt.Sprintf("%s/meter/detailed_values", topic))
wb.enableS = provider.NewMqtt(log, client,
fmt.Sprintf("%s/evse/auto_start_charging_update", topic), 0).
WithPayload(`{ "auto_start_charging": ${enable} }`).
BoolSetter("enable")
wb.maxcurrentS = provider.NewMqtt(log, client,
fmt.Sprintf("%s/evse/current_limit", topic), 0).
WithPayload(`{ "current": ${maxcurrent} }`).
IntSetter("maxcurrent")
return wb, nil
}
func (wb *Warp) hasMeter() bool {
topic := fmt.Sprintf("%s/meter/state", wb.root)
if state, err := provider.NewMqtt(wb.log, wb.client, topic, 0).StringGetter()(); err == nil {
var res v1.MeterState
if err := json.Unmarshal([]byte(state), &res); err == nil {
return res.State == 2 || len(res.PhasesConnected) > 0
}
}
return false
}
func (wb *Warp) hasCurrents() bool {
topic := fmt.Sprintf("%s/meter/detailed_values", wb.root)
if state, err := provider.NewMqtt(wb.log, wb.client, topic, 0).StringGetter()(); err == nil {
var res []float64
if err := json.Unmarshal([]byte(state), &res); err == nil {
return len(res) > 5
}
}
return false
}
// Enable implements the api.Charger interface
func (wb *Warp) Enable(enable bool) error {
// set auto_start_charging
if err := wb.enableS(enable); err != nil {
return err
}
// trigger start/stop
action := "stop_charging"
if enable {
action = "start_charging"
}
topic := fmt.Sprintf("%s/%s/%s", wb.root, "evse", action)
err := wb.client.Publish(topic, false, "null")
if err == nil {
wb.enabled = enable
}
return err
}
func (wb *Warp) status() (v1.EvseState, error) {
var res v1.EvseState
s, err := wb.statusG()
if err == nil {
err = json.Unmarshal([]byte(s), &res)
}
return res, err
}
// autostart reads the enabled state from charger
// use function instead of jq to honor evse/state updates
func (wb *Warp) autostart() (bool, error) {
var res struct {
AutoStartCharging bool `json:"auto_start_charging"`
}
s, err := wb.enabledG()
if err == nil {
err = json.Unmarshal([]byte(s), &res)
}
return res.AutoStartCharging, err
}
// isEnabled reads enabled status from mqtt
func (wb *Warp) isEnabled() (bool, error) {
enabled, err := wb.autostart()
var status v1.EvseState
if err == nil {
status, err = wb.status()
}
if enabled {
// check that charge_release is not blocked
enabled = status.ChargeRelease != 2
} else {
// check that vehicle is really not charging
enabled = status.VehicleState == 2
}
return enabled, err
}
// Enabled implements the api.Charger interface
func (wb *Warp) Enabled() (bool, error) {
enabled, err := wb.isEnabled()
if err == nil && enabled != wb.enabled {
start := time.Now()
// retry to avoid out of sync errors in case of slow warp updates
for time.Since(start) <= 2*time.Second {
if enabled, err = wb.isEnabled(); err != nil {
break
}
if enabled == wb.enabled {
break
}
time.Sleep(50 * time.Millisecond)
}
}
return enabled, err
}
// Status implements the api.Charger interface
func (wb *Warp) Status() (api.ChargeStatus, error) {
var status v1.EvseState
s, err := wb.statusG()
if err == nil {
err = json.Unmarshal([]byte(s), &status)
}
res := api.StatusNone
switch status.VehicleState {
case 0:
res = api.StatusA
case 1:
res = api.StatusB
case 2:
res = api.StatusC
default:
if err == nil {
err = fmt.Errorf("invalid status: %d", status.VehicleState)
}
}
return res, err
}
// MaxCurrent implements the api.Charger interface
func (wb *Warp) MaxCurrent(current int64) error {
return wb.maxcurrentS(1000 * current)
}
var _ api.ChargerEx = (*Warp)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (wb *Warp) MaxCurrentMillis(current float64) error {
return wb.maxcurrentS(int64(1000 * current))
}
// CurrentPower implements the api.Meter interface
func (wb *Warp) currentPower() (float64, error) {
var res v1.MeterState
s, err := wb.meterG()
if err == nil {
err = json.Unmarshal([]byte(s), &res)
}
return res.Power, err
}
// TotalEnergy implements the api.MeterEnergy interface
func (wb *Warp) totalEnergy() (float64, error) {
var res v1.MeterState
s, err := wb.meterG()
if err == nil {
err = json.Unmarshal([]byte(s), &res)
}
return res.EnergyAbs, err
}
// currents implements the api.MeterCurrrents interface
func (wb *Warp) currents() (float64, float64, float64, error) {
var res []float64
s, err := wb.meterDetailsG()
if err == nil {
if err = json.Unmarshal([]byte(s), &res); err == nil {
if len(res) > 5 {
return res[3], res[4], res[5], nil
}
err = errors.New("invalid length")
}
}
return 0, 0, 0, err
}

View file

@ -0,0 +1,86 @@
// Code generated by "enumer -type ExternalControl -trimprefix ExternalControl -transform whitespace"; DO NOT EDIT.
package warp
import (
"fmt"
"strings"
)
const _ExternalControlName = "availabledeactivatedruntime conditions not metcurrently switching"
var _ExternalControlIndex = [...]uint8{0, 9, 20, 46, 65}
const _ExternalControlLowerName = "availabledeactivatedruntime conditions not metcurrently switching"
func (i ExternalControl) String() string {
if i < 0 || i >= ExternalControl(len(_ExternalControlIndex)-1) {
return fmt.Sprintf("ExternalControl(%d)", i)
}
return _ExternalControlName[_ExternalControlIndex[i]:_ExternalControlIndex[i+1]]
}
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
func _ExternalControlNoOp() {
var x [1]struct{}
_ = x[ExternalControlAvailable-(0)]
_ = x[ExternalControlDeactivated-(1)]
_ = x[ExternalControlRuntimeConditionsNotMet-(2)]
_ = x[ExternalControlCurrentlySwitching-(3)]
}
var _ExternalControlValues = []ExternalControl{ExternalControlAvailable, ExternalControlDeactivated, ExternalControlRuntimeConditionsNotMet, ExternalControlCurrentlySwitching}
var _ExternalControlNameToValueMap = map[string]ExternalControl{
_ExternalControlName[0:9]: ExternalControlAvailable,
_ExternalControlLowerName[0:9]: ExternalControlAvailable,
_ExternalControlName[9:20]: ExternalControlDeactivated,
_ExternalControlLowerName[9:20]: ExternalControlDeactivated,
_ExternalControlName[20:46]: ExternalControlRuntimeConditionsNotMet,
_ExternalControlLowerName[20:46]: ExternalControlRuntimeConditionsNotMet,
_ExternalControlName[46:65]: ExternalControlCurrentlySwitching,
_ExternalControlLowerName[46:65]: ExternalControlCurrentlySwitching,
}
var _ExternalControlNames = []string{
_ExternalControlName[0:9],
_ExternalControlName[9:20],
_ExternalControlName[20:46],
_ExternalControlName[46:65],
}
// ExternalControlString retrieves an enum value from the enum constants string name.
// Throws an error if the param is not part of the enum.
func ExternalControlString(s string) (ExternalControl, error) {
if val, ok := _ExternalControlNameToValueMap[s]; ok {
return val, nil
}
if val, ok := _ExternalControlNameToValueMap[strings.ToLower(s)]; ok {
return val, nil
}
return 0, fmt.Errorf("%s does not belong to ExternalControl values", s)
}
// ExternalControlValues returns all values of the enum
func ExternalControlValues() []ExternalControl {
return _ExternalControlValues
}
// ExternalControlStrings returns a slice of all String values of the enum
func ExternalControlStrings() []string {
strs := make([]string, len(_ExternalControlNames))
copy(strs, _ExternalControlNames)
return strs
}
// IsAExternalControl returns "true" if the value is listed in the enum definition. "false" otherwise
func (i ExternalControl) IsAExternalControl() bool {
for _, v := range _ExternalControlValues {
if i == v {
return true
}
}
return false
}

View file

@ -1,4 +1,4 @@
package v2
package warp
const (
FeatureMeter = "meter"
@ -80,3 +80,27 @@ type LastNfcTag struct {
Type int `json:"tag_type"`
ID string `json:"tag_id"`
}
type EmConfig struct {
ContactorInstalled bool `json:"contactor_installed"`
PhaseSwitchingMode int `json:"phase_switching_mode"`
}
//go:generate enumer -type ExternalControl -trimprefix ExternalControl -transform whitespace
type ExternalControl int
const (
ExternalControlAvailable ExternalControl = iota
ExternalControlDeactivated
ExternalControlRuntimeConditionsNotMet
ExternalControlCurrentlySwitching
)
type EmState struct {
ExternalControl ExternalControl `json:"external_control"`
PhasesSwitched int `json:"phases_switched"`
Input3State bool `json:"input3_state"`
Input4State bool `json:"input4_state"`
RelayState bool `json:"relay_state"`
ErrorFlags int `json:"error_flags"`
}

View file

@ -1,46 +0,0 @@
package v1
import "time"
const (
RootTopic = "warp"
Timeout = 30 * time.Second
FeatureMeter = "meter"
FeatureMeterPhases = "meter_phases"
FeatureNfc = "nfc"
)
// https://www.warp-charger.com/api.html#evse_state
type EvseState struct {
Iec61851State int `json:"iec61851_state"`
VehicleState int `json:"vehicle_state"`
ChargeRelease int `json:"charge_release"`
ContactorState int `json:"contactor_state"`
ContactorError int `json:"contactor_error"`
AllowedChargingCurrent int64 `json:"allowed_charging_current"`
ErrorState int `json:"error_state"`
LockState int `json:"lock_state"`
TimeSinceStateChange int64 `json:"time_since_state_change"`
Uptime int64 `json:"uptime"`
}
// https://www.warp-charger.com/api.html#evse_low_level_state
type LowLevelState struct {
LedState int `json:"led_state"`
CpPwmDutyCycle int `json:"cp_pwm_duty_cycle"`
AdcValues []int `json:"adc_values"`
Voltages []int
Resistances []int
Gpio []bool
}
// https://www.warp-charger.com/api.html#meter_state
type MeterState struct {
State int `json:"state"` // Warp 1 only
Power float64 `json:"power"`
EnergyRel float64 `json:"energy_rel"`
EnergyAbs float64 `json:"energy_abs"`
PhasesActive []bool `json:"phases_active"`
PhasesConnected []bool `json:"phases_connected"`
}

View file

@ -8,7 +8,6 @@ import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/warp"
v2 "github.com/evcc-io/evcc/charger/warp/v2"
"github.com/evcc-io/evcc/provider"
"github.com/evcc-io/evcc/provider/mqtt"
"github.com/evcc-io/evcc/util"
@ -18,7 +17,6 @@ import (
// Warp2 is the Warp charger v2 firmware implementation
type Warp2 struct {
log *util.Logger
root string
client *mqtt.Client
features []string
maxcurrentG func() (string, error)
@ -28,21 +26,26 @@ type Warp2 struct {
chargeG func() (string, error)
userconfigG func() (string, error)
maxcurrentS func(int64) error
current int64
// emConfigG func() (string, error)
emStateG func() (string, error)
phasesS func(int64) error
current int64
}
func init() {
registry.Add("warp-fw2", NewWarp2FromConfig)
registry.Add("warp2", NewWarp2FromConfig)
registry.Add("warp-fw2", NewWarp2FromConfig) // deprecated
}
//go:generate go run ../cmd/tools/decorate.go -f decorateWarp2 -b *Warp2 -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.Identifier,Identify,func() (string, error)"
// go:generate go run ../cmd/tools/decorate.go -f decorateWarp2 -b *Warp2 -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewWarpFromConfig creates a new configurable charger
func NewWarp2FromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
mqtt.Config `mapstructure:",squash"`
Topic string
Timeout time.Duration
mqtt.Config `mapstructure:",squash"`
Topic string
EnergyManager string
Timeout time.Duration
}{
Topic: warp.RootTopic,
Timeout: warp.Timeout,
@ -52,32 +55,39 @@ func NewWarp2FromConfig(other map[string]interface{}) (api.Charger, error) {
return nil, err
}
wb, err := NewWarp2(cc.Config, cc.Topic, cc.Timeout)
wb, err := NewWarp2(cc.Config, cc.Topic, cc.EnergyManager, cc.Timeout)
if err != nil {
return nil, err
}
var currentPower, totalEnergy func() (float64, error)
if wb.hasFeature(v2.FeatureMeter) {
if wb.hasFeature(cc.Topic, warp.FeatureMeter) {
currentPower = wb.currentPower
totalEnergy = wb.totalEnergy
}
var currents func() (float64, float64, float64, error)
if wb.hasFeature(v2.FeatureMeterPhases) {
if wb.hasFeature(cc.Topic, warp.FeatureMeterPhases) {
currents = wb.currents
}
var identity func() (string, error)
if wb.hasFeature(v2.FeatureNfc) {
if wb.hasFeature(cc.Topic, warp.FeatureNfc) {
identity = wb.identify
}
return decorateWarp2(wb, currentPower, totalEnergy, currents, identity), err
var phases func(int) error
if cc.EnergyManager != "" {
if res, err := wb.emState(); err == nil && res.ExternalControl != 1 {
phases = wb.phases1p3p
}
}
return decorateWarp2(wb, currentPower, totalEnergy, currents, identity, phases), err
}
// NewWarp2 creates a new configurable charger
func NewWarp2(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp2, error) {
func NewWarp2(mqttconf mqtt.Config, topic, emTopic string, timeout time.Duration) (*Warp2, error) {
log := util.NewLogger("warp")
client, err := mqtt.RegisteredClientOrDefault(log, mqttconf)
@ -87,7 +97,6 @@ func NewWarp2(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp2
wb := &Warp2{
log: log,
root: topic,
client: client,
current: 6000, // mA
}
@ -114,15 +123,22 @@ func NewWarp2(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp2
WithPayload(`{ "current": ${maxcurrent} }`).
IntSetter("maxcurrent")
// wb.emConfigG = stringG(fmt.Sprintf("%s/energy_manager/config", emTopic))
wb.emStateG = stringG(fmt.Sprintf("%s/energy_manager/state", emTopic))
wb.phasesS = provider.NewMqtt(log, client,
fmt.Sprintf("%s/energy_manager/external_control_update", emTopic), 0).
WithPayload(`{ "phases_wanted": ${phases} }`).
IntSetter("phases")
return wb, nil
}
func (wb *Warp2) hasFeature(feature string) bool {
func (wb *Warp2) hasFeature(root, feature string) bool {
if wb.features != nil {
return slices.Contains(wb.features, feature)
}
topic := fmt.Sprintf("%s/info/features", wb.root)
topic := fmt.Sprintf("%s/info/features", root)
if data, err := provider.NewMqtt(wb.log, wb.client, topic, 0).StringGetter()(); err == nil {
if err := json.Unmarshal([]byte(data), &wb.features); err == nil {
@ -144,7 +160,7 @@ func (wb *Warp2) Enable(enable bool) error {
// Enabled implements the api.Charger interface
func (wb *Warp2) Enabled() (bool, error) {
var res v2.EvseExternalCurrent
var res warp.EvseExternalCurrent
s, err := wb.maxcurrentG()
if err == nil {
@ -156,14 +172,18 @@ func (wb *Warp2) Enabled() (bool, error) {
// Status implements the api.Charger interface
func (wb *Warp2) Status() (api.ChargeStatus, error) {
var status v2.EvseState
res := api.StatusNone
s, err := wb.statusG()
if err == nil {
err = json.Unmarshal([]byte(s), &status)
if err != nil {
return res, err
}
var status warp.EvseState
if err := json.Unmarshal([]byte(s), &status); err != nil {
return res, err
}
res := api.StatusNone
switch status.Iec61851State {
case 0:
res = api.StatusA
@ -172,9 +192,7 @@ func (wb *Warp2) Status() (api.ChargeStatus, error) {
case 2:
res = api.StatusC
default:
if err == nil {
err = fmt.Errorf("invalid status: %d", status.Iec61851State)
}
err = fmt.Errorf("invalid status: %d", status.Iec61851State)
}
return res, err
@ -203,7 +221,7 @@ func (wb *Warp2) MaxCurrentMillis(current float64) error {
// CurrentPower implements the api.Meter interface
func (wb *Warp2) currentPower() (float64, error) {
var res v2.MeterValues
var res warp.MeterValues
s, err := wb.meterG()
if err == nil {
@ -215,7 +233,7 @@ func (wb *Warp2) currentPower() (float64, error) {
// TotalEnergy implements the api.MeterEnergy interface
func (wb *Warp2) totalEnergy() (float64, error) {
var res v2.MeterValues
var res warp.MeterValues
s, err := wb.meterG()
if err == nil {
@ -230,21 +248,23 @@ func (wb *Warp2) currents() (float64, float64, float64, error) {
var res []float64
s, err := wb.meterDetailsG()
if err == nil {
if err = json.Unmarshal([]byte(s), &res); err == nil {
if len(res) > 5 {
return res[3], res[4], res[5], nil
}
err = errors.New("invalid length")
}
if err != nil {
return 0, 0, 0, err
}
return 0, 0, 0, err
if err := json.Unmarshal([]byte(s), &res); err != nil {
return 0, 0, 0, err
}
if len(res) <= 5 {
return 0, 0, 0, errors.New("invalid length")
}
return res[3], res[4], res[5], nil
}
func (wb *Warp2) identify() (string, error) {
var res v2.ChargeTrackerCurrentCharge
var res warp.ChargeTrackerCurrentCharge
s, err := wb.chargeG()
if err == nil {
@ -253,3 +273,38 @@ func (wb *Warp2) identify() (string, error) {
return res.AuthorizationInfo.TagId, err
}
// func (wb *Warp2) emConfig() (warp.EmConfig, error) {
// var res warp.EmConfig
// s, err := wb.emConfigG()
// if err == nil {
// err = json.Unmarshal([]byte(s), &res)
// }
// return res, err
// }
func (wb *Warp2) emState() (warp.EmState, error) {
var res warp.EmState
s, err := wb.emStateG()
if err == nil {
err = json.Unmarshal([]byte(s), &res)
}
return res, err
}
func (wb *Warp2) phases1p3p(phases int) error {
res, err := wb.emState()
if err != nil {
return err
}
if res.ExternalControl > 0 {
return fmt.Errorf("external control not available: %s", res.ExternalControl.String())
}
return wb.phasesS(int64(phases))
}

View file

@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), identifier func() (string, error)) api.Charger {
func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), identifier func() (string, error), phaseSwitcher func(int) error) api.Charger {
switch {
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil:
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return base
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil:
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Meter
@ -22,7 +22,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil:
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.MeterEnergy
@ -33,7 +33,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil:
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Meter
@ -48,7 +48,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil:
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.PhaseCurrents
@ -59,7 +59,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil:
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Meter
@ -74,7 +74,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil:
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.MeterEnergy
@ -89,7 +89,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil:
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Meter
@ -108,7 +108,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil:
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -119,7 +119,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil:
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -134,7 +134,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil:
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -149,7 +149,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil:
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -168,7 +168,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil:
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -183,7 +183,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil:
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -202,7 +202,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil:
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -221,7 +221,7 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil:
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Warp2
api.Identifier
@ -243,6 +243,310 @@ func decorateWarp2(base *Warp2, meter func() (float64, error), meterEnergy func(
phaseCurrents: phaseCurrents,
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.PhaseSwitcher
}{
Warp2: base,
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Meter
api.PhaseSwitcher
}{
Warp2: base,
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.MeterEnergy
api.PhaseSwitcher
}{
Warp2: base,
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
Warp2: base,
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.Meter
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.MeterEnergy
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Warp2
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Warp2: base,
Identifier: &decorateWarp2IdentifierImpl{
identifier: identifier,
},
Meter: &decorateWarp2MeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarp2MeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarp2PhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateWarp2PhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
@ -279,3 +583,11 @@ type decorateWarp2PhaseCurrentsImpl struct {
func (impl *decorateWarp2PhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
return impl.phaseCurrents()
}
type decorateWarp2PhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
func (impl *decorateWarp2PhaseSwitcherImpl) Phases1p3p(phases int) error {
return impl.phaseSwitcher(phases)
}

View file

@ -1,137 +0,0 @@
package charger
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateWarp(base *Warp, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger {
switch {
case meter == nil && meterEnergy == nil && phaseCurrents == nil:
return base
case meter != nil && meterEnergy == nil && phaseCurrents == nil:
return &struct {
*Warp
api.Meter
}{
Warp: base,
Meter: &decorateWarpMeterImpl{
meter: meter,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents == nil:
return &struct {
*Warp
api.MeterEnergy
}{
Warp: base,
MeterEnergy: &decorateWarpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents == nil:
return &struct {
*Warp
api.Meter
api.MeterEnergy
}{
Warp: base,
Meter: &decorateWarpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter == nil && meterEnergy == nil && phaseCurrents != nil:
return &struct {
*Warp
api.PhaseCurrents
}{
Warp: base,
PhaseCurrents: &decorateWarpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy == nil && phaseCurrents != nil:
return &struct {
*Warp
api.Meter
api.PhaseCurrents
}{
Warp: base,
Meter: &decorateWarpMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateWarpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents != nil:
return &struct {
*Warp
api.MeterEnergy
api.PhaseCurrents
}{
Warp: base,
MeterEnergy: &decorateWarpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents != nil:
return &struct {
*Warp
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
Warp: base,
Meter: &decorateWarpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateWarpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateWarpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
}
return nil
}
type decorateWarpMeterImpl struct {
meter func() (float64, error)
}
func (impl *decorateWarpMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decorateWarpMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decorateWarpMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateWarpPhaseCurrentsImpl struct {
phaseCurrents func() (float64, float64, float64, error)
}
func (impl *decorateWarpPhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
return impl.phaseCurrents()
}

View file

@ -11,20 +11,21 @@ products:
capabilities: ["mA"]
requirements:
description:
en: An MQTT broker is required. The charger must be have a configured MQTT connection.
de: Es muss ein MQTT Broker installiert sein. Die MQTT Verbindung muss in der Wallbox eingerichtet sein.
en: Firmware v2 required.
de: Firmware v2 erforderlich.
uri: https://docs.evcc.io/docs/devices/chargers#tinkerforge
params:
- name: fw2
required: true
type: bool
help:
de: Firmware v2 installiert
en: Firmware v2 installed
- preset: mqtt
- name: topic
default: warp
- name: energymanager
help:
de: EnergyManager MQTT Topic (falls installiert)
en: EnergyManager MQTT topic if installed
render: |
type: {{ if eq .fw2 "true" -}} warp-fw2 {{ else -}} warp {{ end }}
type: warp2
{{ include "mqtt" . }}
topic: {{ .topic }}
{{- if .energymanager }}
energymanager: {{ .energymanager }}
{{- end }}

View file

@ -3,22 +3,22 @@ product:
description: WARP Charger Smart
capabilities: ["mA"]
description: |
Es muss ein MQTT Broker installiert sein. Die MQTT Verbindung muss in der Wallbox eingerichtet sein.
Firmware v2 erforderlich.
render:
- default: |
type: template
template: tinkerforge-warp
fw2: # Firmware v2 installiert
host: 192.0.2.2 # IP Adresse oder der Hostname des MQTT Brokers
port: 1883 # MQTT Broker Port (Optional)
topic: warp # Topic (ohne / am Anfang) (Optional)
energymanager: # EnergyManager MQTT Topic (falls installiert) (Optional)
advanced: |
type: template
template: tinkerforge-warp
fw2: # Firmware v2 installiert
host: 192.0.2.2 # IP Adresse oder der Hostname des MQTT Brokers
port: 1883 # MQTT Broker Port (Optional)
user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.) (Optional)
password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen) (Optional)
topic: warp # Topic (ohne / am Anfang) (Optional)
timeout: 30s # Akzeptiere keine Daten die älter sind als dieser Wert (Optional)
energymanager: # EnergyManager MQTT Topic (falls installiert) (Optional)

View file

@ -3,22 +3,22 @@ product:
description: WARP Charger Pro
capabilities: ["mA"]
description: |
Es muss ein MQTT Broker installiert sein. Die MQTT Verbindung muss in der Wallbox eingerichtet sein.
Firmware v2 erforderlich.
render:
- default: |
type: template
template: tinkerforge-warp
fw2: # Firmware v2 installiert
host: 192.0.2.2 # IP Adresse oder der Hostname des MQTT Brokers
port: 1883 # MQTT Broker Port (Optional)
topic: warp # Topic (ohne / am Anfang) (Optional)
energymanager: # EnergyManager MQTT Topic (falls installiert) (Optional)
advanced: |
type: template
template: tinkerforge-warp
fw2: # Firmware v2 installiert
host: 192.0.2.2 # IP Adresse oder der Hostname des MQTT Brokers
port: 1883 # MQTT Broker Port (Optional)
user: # Benutzerkonto (bspw. E-Mail Adresse, User Id, etc.) (Optional)
password: # Passwort des Benutzerkontos (bei führenden Nullen bitte in einfache Hochkommata setzen) (Optional)
topic: warp # Topic (ohne / am Anfang) (Optional)
timeout: 30s # Akzeptiere keine Daten die älter sind als dieser Wert (Optional)
energymanager: # EnergyManager MQTT Topic (falls installiert) (Optional)