From a05b524b84d9e02838ddfa6ef1a6ef17c954cf9c Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 29 Dec 2021 16:44:12 +0100 Subject: [PATCH] Warp: add currents for Warp2 (#2141) --- charger/warp.go | 131 +++++++++++++++++++++---------------- charger/warp/types.go | 36 ++++++++++ charger/warp_decorators.go | 54 +++------------ 3 files changed, 121 insertions(+), 100 deletions(-) create mode 100644 charger/warp/types.go diff --git a/charger/warp.go b/charger/warp.go index b49ee4534..2b2eafa67 100644 --- a/charger/warp.go +++ b/charger/warp.go @@ -2,23 +2,36 @@ package charger import ( "encoding/json" + "errors" "fmt" "time" "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/charger/warp" "github.com/evcc-io/evcc/provider" "github.com/evcc-io/evcc/provider/mqtt" "github.com/evcc-io/evcc/util" ) +// Warp configures generic charger and charge meter for an Warp loadpoint +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) } -const ( - warpRootTopic = "warp" - warpTimeout = 30 * time.Second -) +//go:generate go run ../cmd/tools/decorate.go -f decorateWarp -b *Warp -r api.Charger -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" // NewWarpFromConfig creates a new configurable charger func NewWarpFromConfig(other map[string]interface{}) (api.Charger, error) { @@ -26,45 +39,47 @@ func NewWarpFromConfig(other map[string]interface{}) (api.Charger, error) { mqtt.Config `mapstructure:",squash"` Topic string Timeout time.Duration - UseMeter bool + UseMeter interface{} }{ - Topic: warpRootTopic, - Timeout: warpTimeout, + Topic: warp.RootTopic, + Timeout: warp.Timeout, } if err := util.DecodeOther(other, &cc); err != nil { return nil, err } - m, err := NewWarp(cc.Config, cc.Topic, cc.Timeout) + wb, err := NewWarp(cc.Config, cc.Topic, cc.Timeout) if err != nil { return nil, err } - var currentPower func() (float64, error) - var totalEnergy func() (float64, error) - if cc.UseMeter { - currentPower = m.currentPower - totalEnergy = m.totalEnergy + if cc.UseMeter != nil { + // TODO remove + util.NewLogger("warp").WARN.Println("usemeter is deprecated and will be removed in a future release") } - return decorateWarp(m, currentPower, totalEnergy), err -} + detectPro := provider.NewMqtt(wb.log, wb.client, + fmt.Sprintf("%s/evse/low_level_state", wb.root), 1, cc.Timeout, + ).StringGetter() -// Warp configures generic charger and charge meter for an Warp loadpoint -type Warp struct { - log *util.Logger - root string - client *mqtt.Client - enabledG func() (string, error) - statusG func() (string, error) - meterG func() (string, error) - enableS func(bool) error - maxcurrentS func(int64) error - enabled bool // cache -} + var isPro bool + if state, err := detectPro(); err == nil { + var res warp.LowLevelState + if err := json.Unmarshal([]byte(state), &res); err != nil { + return nil, err + } -//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)" + isPro = len(res.AdcValues) > 2 + } + + var currents func() (float64, float64, float64, error) + if isPro { + currents = wb.currents + } + + return decorateWarp(wb, currents), err +} // NewWarp creates a new configurable charger func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, error) { @@ -99,6 +114,7 @@ func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, m.enabledG = stringG(fmt.Sprintf("%s/evse/auto_start_charging", topic)) m.statusG = stringG(fmt.Sprintf("%s/evse/state", topic)) m.meterG = stringG(fmt.Sprintf("%s/meter/state", topic)) + m.meterDetailsG = stringG(fmt.Sprintf("%s/meter/detailed_values", topic)) m.enableS = provider.NewMqtt(log, client, fmt.Sprintf("%s/evse/auto_start_charging_update", topic), 1, 0). @@ -113,19 +129,6 @@ func NewWarp(mqttconf mqtt.Config, topic string, timeout time.Duration) (*Warp, return m, nil } -type warpStatus 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"` -} - // Enable implements the api.Charger interface func (m *Warp) Enable(enable bool) error { // set auto_start_charging @@ -149,8 +152,8 @@ func (m *Warp) Enable(enable bool) error { return err } -func (m *Warp) status() (warpStatus, error) { - var res warpStatus +func (m *Warp) status() (warp.Status, error) { + var res warp.Status s, err := m.statusG() if err == nil { @@ -179,7 +182,7 @@ func (m *Warp) autostart() (bool, error) { func (m *Warp) isEnabled() (bool, error) { enabled, err := m.autostart() - var status warpStatus + var status warp.Status if err == nil { status, err = m.status() } @@ -221,7 +224,7 @@ func (m *Warp) Enabled() (bool, error) { // Status implements the api.Charger interface func (m *Warp) Status() (api.ChargeStatus, error) { - var status warpStatus + var status warp.Status s, err := m.statusG() if err == nil { @@ -257,15 +260,11 @@ func (m *Warp) MaxCurrentMillis(current float64) error { return m.maxcurrentS(int64(1000 * current)) } -type powerStatus struct { - Power float64 `json:"power"` - EnergyRel float64 `json:"energy_rel"` - EnergyAbs float64 `json:"energy_abs"` -} +var _ api.Meter = (*Warp)(nil) -// currentPower implements the api.Meter interface -func (m *Warp) currentPower() (float64, error) { - var res powerStatus +// CurrentPower implements the api.Meter interface +func (m *Warp) CurrentPower() (float64, error) { + var res warp.PowerStatus s, err := m.meterG() if err == nil { @@ -275,9 +274,11 @@ func (m *Warp) currentPower() (float64, error) { return res.Power, err } -// totalEnergy implements the api.MeterEnergy interface -func (m *Warp) totalEnergy() (float64, error) { - var res powerStatus +var _ api.MeterEnergy = (*Warp)(nil) + +// TotalEnergy implements the api.MeterEnergy interface +func (m *Warp) TotalEnergy() (float64, error) { + var res warp.PowerStatus s, err := m.meterG() if err == nil { @@ -286,3 +287,21 @@ func (m *Warp) totalEnergy() (float64, error) { return res.EnergyAbs, err } + +// currents implements the api.MeterCurrrents interface +func (m *Warp) currents() (float64, float64, float64, error) { + var res []float64 + + s, err := m.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 +} diff --git a/charger/warp/types.go b/charger/warp/types.go new file mode 100644 index 000000000..c5c777b3c --- /dev/null +++ b/charger/warp/types.go @@ -0,0 +1,36 @@ +package warp + +import "time" + +const ( + RootTopic = "warp" + Timeout = 30 * time.Second +) + +type Status 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"` +} + +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 +} + +type PowerStatus struct { + Power float64 `json:"power"` + EnergyRel float64 `json:"energy_rel"` + EnergyAbs float64 `json:"energy_abs"` +} diff --git a/charger/warp_decorators.go b/charger/warp_decorators.go index 0858111ad..a3c96d974 100644 --- a/charger/warp_decorators.go +++ b/charger/warp_decorators.go @@ -6,45 +6,19 @@ import ( "github.com/evcc-io/evcc/api" ) -func decorateWarp(base *Warp, meter func() (float64, error), meterEnergy func() (float64, error)) api.Charger { +func decorateWarp(base *Warp, meterCurrent func() (float64, float64, float64, error)) api.Charger { switch { - case meter == nil && meterEnergy == nil: + case meterCurrent == nil: return base - case meter != nil && meterEnergy == nil: + case meterCurrent != nil: return &struct { *Warp - api.Meter + api.MeterCurrent }{ Warp: base, - Meter: &decorateWarpMeterImpl{ - meter: meter, - }, - } - - case meter == nil && meterEnergy != nil: - return &struct { - *Warp - api.MeterEnergy - }{ - Warp: base, - MeterEnergy: &decorateWarpMeterEnergyImpl{ - meterEnergy: meterEnergy, - }, - } - - case meter != nil && meterEnergy != nil: - return &struct { - *Warp - api.Meter - api.MeterEnergy - }{ - Warp: base, - Meter: &decorateWarpMeterImpl{ - meter: meter, - }, - MeterEnergy: &decorateWarpMeterEnergyImpl{ - meterEnergy: meterEnergy, + MeterCurrent: &decorateWarpMeterCurrentImpl{ + meterCurrent: meterCurrent, }, } } @@ -52,18 +26,10 @@ func decorateWarp(base *Warp, meter func() (float64, error), meterEnergy func() return nil } -type decorateWarpMeterImpl struct { - meter func() (float64, error) +type decorateWarpMeterCurrentImpl struct { + meterCurrent func() (float64, float64, 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() +func (impl *decorateWarpMeterCurrentImpl) Currents() (float64, float64, float64, error) { + return impl.meterCurrent() }