EVSEWifi: support 10mA resolution (#1164)
This commit is contained in:
parent
ece30ae030
commit
3a467f8492
3 changed files with 230 additions and 36 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue