EVSEWifi: support 10mA resolution (#1164)

This commit is contained in:
andig 2021-06-20 18:51:37 +02:00 • committed by GitHub
parent ece30ae030
commit 3a467f8492
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 230 additions and 36 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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")
}
}