From 3a467f849288cb2c7608cbdce8a7906ee2172ac6 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 20 Jun 2021 18:51:37 +0200 Subject: [PATCH] EVSEWifi: support 10mA resolution (#1164) --- charger/evsewifi.go | 76 ++++++++++------ charger/evsewifi_decorators.go | 162 +++++++++++++++++++++++++++++++-- charger/evsewifi_test.go | 28 ++++++ 3 files changed, 230 insertions(+), 36 deletions(-) diff --git a/charger/evsewifi.go b/charger/evsewifi.go index 49c5b8d90..db68da58c 100644 --- a/charger/evsewifi.go +++ b/charger/evsewifi.go @@ -27,22 +27,23 @@ type EVSEParameterResponse struct { // EVSEListEntry is EVSEParameterResponse.List type EVSEListEntry struct { - VehicleState int64 `json:"vehicleState"` - EvseState bool `json:"evseState"` - MaxCurrent int64 `json:"maxCurrent"` - ActualCurrent int64 `json:"actualCurrent"` - ActualPower float64 `json:"actualPower"` - Duration int64 `json:"duration"` - AlwaysActive bool `json:"alwaysActive"` - UseMeter bool `json:"useMeter"` - LastActionUser string `json:"lastActionUser"` - LastActionUID string `json:"lastActionUID"` - Energy float64 `json:"energy"` - Mileage float64 `json:"mileage"` - MeterReading float64 `json:"meterReading"` - CurrentP1 float64 `json:"currentP1"` - CurrentP2 float64 `json:"currentP2"` - CurrentP3 float64 `json:"currentP3"` + VehicleState int64 `json:"vehicleState"` + EvseState bool `json:"evseState"` + MaxCurrent int64 `json:"maxCurrent"` + ActualCurrent int64 `json:"actualCurrent"` + ActualCurrentMA *int64 `json:"actualCurrentMA"` // 1/100 A + ActualPower float64 `json:"actualPower"` + Duration int64 `json:"duration"` + AlwaysActive bool `json:"alwaysActive"` + UseMeter bool `json:"useMeter"` + LastActionUser string `json:"lastActionUser"` + LastActionUID string `json:"lastActionUID"` + Energy float64 `json:"energy"` + Mileage float64 `json:"mileage"` + MeterReading float64 `json:"meterReading"` + CurrentP1 float64 `json:"currentP1"` + CurrentP2 float64 `json:"currentP2"` + CurrentP3 float64 `json:"currentP3"` } // EVSEWifi charger implementation @@ -51,14 +52,15 @@ type EVSEWifi struct { log *util.Logger uri string alwaysActive bool - current int64 + current int64 // current will always be the physical value sent to the API + hires bool } func init() { registry.Add("evsewifi", NewEVSEWifiFromConfig) } -//go:generate go run ../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" +// go:generate go run ../cmd/tools/decorate.go -f decorateEVSE -b *EVSEWifi -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)" -t "api.ChargerEx,MaxCurrentMillis,func(current float64) error" // NewEVSEWifiFromConfig creates a EVSEWifi charger from generic config func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { @@ -78,13 +80,22 @@ func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { return evse, err } - // auto-detect EVSE meter - if meter, err := evse.HasMeter(); meter && err == nil { + // auto-detect capabilities + params, err := evse.getParameters() + if err != nil { + return evse, err + } + + if params.UseMeter { cc.Meter.Energy = true cc.Meter.Energy = true cc.Meter.Currents = true } + if params.ActualCurrentMA != nil { + evse.hires = true + } + // decorate Charger with Meter var currentPower func() (float64, error) if cc.Meter.Energy { @@ -103,7 +114,14 @@ func NewEVSEWifiFromConfig(other map[string]interface{}) (api.Charger, error) { currents = evse.currents } - return decorateEVSE(evse, currentPower, totalEnergy, currents), nil + // decorate Charger with MaxCurrentEx + var maxCurrentEx func(float64) error + if evse.hires { + maxCurrentEx = evse.maxCurrentEx + evse.current = 100 * evse.current + } + + return decorateEVSE(evse, currentPower, totalEnergy, currents, maxCurrentEx), nil } // NewEVSEWifi creates EVSEWifi charger @@ -146,12 +164,6 @@ func (evse *EVSEWifi) getParameters() (EVSEListEntry, error) { return params, nil } -// HasMeter returns the useMeter api response -func (evse *EVSEWifi) HasMeter() (bool, error) { - params, err := evse.getParameters() - return params.UseMeter, err -} - // Status implements the api.Charger interface func (evse *EVSEWifi) Status() (api.ChargeStatus, error) { params, err := evse.getParameters() @@ -206,11 +218,21 @@ func (evse *EVSEWifi) Enable(enable bool) error { // MaxCurrent implements the api.Charger interface func (evse *EVSEWifi) MaxCurrent(current int64) error { + if evse.hires { + current = 100 * current + } evse.current = current url := fmt.Sprintf("%s?current=%d", evse.apiURL(evseSetCurrent), current) return evse.get(url) } +// maxCurrentEx implements the api.ChargerEx interface +func (evse *EVSEWifi) maxCurrentEx(current float64) error { + evse.current = int64(100 * current) + url := fmt.Sprintf("%s?current=%d", evse.apiURL(evseSetCurrent), evse.current) + return evse.get(url) +} + var _ api.ChargeTimer = (*EVSEWifi)(nil) // ChargingTime implements the api.ChargeTimer interface diff --git a/charger/evsewifi_decorators.go b/charger/evsewifi_decorators.go index 8e5d15d73..cd6a5e218 100644 --- a/charger/evsewifi_decorators.go +++ b/charger/evsewifi_decorators.go @@ -6,12 +6,12 @@ import ( "github.com/andig/evcc/api" ) -func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Charger { +func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error), chargerEx func(current float64) error) api.Charger { switch { - case meter == nil && meterCurrent == nil && meterEnergy == nil: + case chargerEx == nil && meter == nil && meterCurrent == nil && meterEnergy == nil: return base - case meter != nil && meterCurrent == nil && meterEnergy == nil: + case chargerEx == nil && meter != nil && meterCurrent == nil && meterEnergy == nil: return &struct { *EVSEWifi api.Meter @@ -22,7 +22,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter == nil && meterCurrent == nil && meterEnergy != nil: + case chargerEx == nil && meter == nil && meterCurrent == nil && meterEnergy != nil: return &struct { *EVSEWifi api.MeterEnergy @@ -33,7 +33,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter != nil && meterCurrent == nil && meterEnergy != nil: + case chargerEx == nil && meter != nil && meterCurrent == nil && meterEnergy != nil: return &struct { *EVSEWifi api.Meter @@ -48,7 +48,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter == nil && meterCurrent != nil && meterEnergy == nil: + case chargerEx == nil && meter == nil && meterCurrent != nil && meterEnergy == nil: return &struct { *EVSEWifi api.MeterCurrent @@ -59,7 +59,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter != nil && meterCurrent != nil && meterEnergy == nil: + case chargerEx == nil && meter != nil && meterCurrent != nil && meterEnergy == nil: return &struct { *EVSEWifi api.Meter @@ -74,7 +74,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter == nil && meterCurrent != nil && meterEnergy != nil: + case chargerEx == nil && meter == nil && meterCurrent != nil && meterEnergy != nil: return &struct { *EVSEWifi api.MeterCurrent @@ -89,7 +89,7 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun }, } - case meter != nil && meterCurrent != nil && meterEnergy != nil: + case chargerEx == nil && meter != nil && meterCurrent != nil && meterEnergy != nil: return &struct { *EVSEWifi api.Meter @@ -107,11 +107,155 @@ func decorateEVSE(base *EVSEWifi, meter func() (float64, error), meterEnergy fun meterEnergy: meterEnergy, }, } + + case chargerEx != nil && meter == nil && meterCurrent == nil && meterEnergy == nil: + return &struct { + *EVSEWifi + api.ChargerEx + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + } + + case chargerEx != nil && meter != nil && meterCurrent == nil && meterEnergy == nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.Meter + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + Meter: &decorateEVSEMeterImpl{ + meter: meter, + }, + } + + case chargerEx != nil && meter == nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.MeterEnergy + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case chargerEx != nil && meter != nil && meterCurrent == nil && meterEnergy != nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.Meter + api.MeterEnergy + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + Meter: &decorateEVSEMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case chargerEx != nil && meter == nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.MeterCurrent + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case chargerEx != nil && meter != nil && meterCurrent != nil && meterEnergy == nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.Meter + api.MeterCurrent + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + Meter: &decorateEVSEMeterImpl{ + meter: meter, + }, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + } + + case chargerEx != nil && meter == nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.MeterCurrent + api.MeterEnergy + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case chargerEx != nil && meter != nil && meterCurrent != nil && meterEnergy != nil: + return &struct { + *EVSEWifi + api.ChargerEx + api.Meter + api.MeterCurrent + api.MeterEnergy + }{ + EVSEWifi: base, + ChargerEx: &decorateEVSEChargerExImpl{ + chargerEx: chargerEx, + }, + Meter: &decorateEVSEMeterImpl{ + meter: meter, + }, + MeterCurrent: &decorateEVSEMeterCurrentImpl{ + meterCurrent: meterCurrent, + }, + MeterEnergy: &decorateEVSEMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } } return nil } +type decorateEVSEChargerExImpl struct { + chargerEx func(current float64) error +} + +func (impl *decorateEVSEChargerExImpl) MaxCurrentMillis(current float64) error { + return impl.chargerEx(current) +} + type decorateEVSEMeterImpl struct { meter func() (float64, error) } diff --git a/charger/evsewifi_test.go b/charger/evsewifi_test.go index 784413aed..c71db038a 100644 --- a/charger/evsewifi_test.go +++ b/charger/evsewifi_test.go @@ -1,13 +1,22 @@ package charger import ( + "fmt" + "net/http" + "net/http/httptest" "testing" "github.com/andig/evcc/api" ) func TestEvseWifi(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintln(w, `{"list":[{"useMeter":true}]}`) + })) + defer ts.Close() + wb, err := NewEVSEWifiFromConfig(map[string]interface{}{ + "uri": ts.URL, "meter": map[string]interface{}{ "power": true, "energy": true, @@ -31,3 +40,22 @@ func TestEvseWifi(t *testing.T) { t.Error("missing ChargeTimer api") } } + +func TestEvseWifiEx(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintln(w, `{"list":[{"actualCurrentMA":600}]}`) + })) + defer ts.Close() + + wb, err := NewEVSEWifiFromConfig(map[string]interface{}{ + "uri": ts.URL, + }) + + if err != nil { + t.Error(err) + } + + if _, ok := wb.(api.ChargerEx); !ok { + t.Error("missing api.ChargerEx") + } +}