From 56d13c2b46852ee4ff2f4fe0c1f729d9ce7e1ae5 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 5 Oct 2021 11:15:52 +0200 Subject: [PATCH] Easee: control at charger instead of circuit level (#1701) --- charger/easee.go | 220 ++++++++++++++++----------------------- charger/easee/message.go | 32 +++--- 2 files changed, 103 insertions(+), 149 deletions(-) diff --git a/charger/easee.go b/charger/easee.go index e2728f094..504ce27eb 100644 --- a/charger/easee.go +++ b/charger/easee.go @@ -41,19 +41,15 @@ import ( type Easee struct { *request.Helper charger string - site, circuit int updated time.Time chargeStatus api.ChargeStatus cache time.Duration log *util.Logger mux sync.Mutex - phases int chargerEnabled bool enabledStatus bool - current, currentPower, sessionEnergy, - circuitTotalPhaseConductorCurrentL1, - circuitTotalPhaseConductorCurrentL2, - circuitTotalPhaseConductorCurrentL3 float64 + currentPower, sessionEnergy, + currentL1, currentL2, currentL3 float64 } func init() { @@ -88,10 +84,8 @@ func NewEasee(user, password, charger string, circuit int, cache time.Duration) c := &Easee{ Helper: request.NewHelper(log), charger: charger, - circuit: circuit, cache: cache, log: log, - phases: 3, } if cache > 0 { @@ -123,33 +117,8 @@ func NewEasee(user, password, charger string, circuit int, cache time.Duration) c.charger = chargers[0].ID } - // find site - site, err := c.chargerDetails(c.charger) - if err != nil { - return c, err - } - - c.site = site.ID - - // find circuit - if circuit == 0 { - if len(site.Circuits) != 1 { - return c, fmt.Errorf("cannot determine circuit id, found: %v", funk.Map(site.Circuits, func(c easee.Circuit) int { return c.ID })) - } - - c.circuit = site.Circuits[0].ID - } - + // subscribe for updates err = c.subscribe(ts) - if err != nil { - return c, err - } - - // verify charger config - config, err := c.chargerConfig(c.charger) - if err == nil && config.PhaseMode != 2 { - c.log.WARN.Println("expected PhaseMode auto- switching phases will NOT work") - } return c, err } @@ -172,7 +141,7 @@ func (c *Easee) subscribe(ts oauth2.TokenSource) error { client, err := signalr.NewClient(context.Background(), conn, signalr.Receiver(c), - // signalr.Logger(easee.SignalrLogger(c.log.TRACE), false), + signalr.Logger(easee.SignalrLogger(c.log.TRACE), false), ) if err != nil { return err @@ -199,64 +168,68 @@ func (c *Easee) subscribe(ts oauth2.TokenSource) error { func (c *Easee) observe(typ string, i json.RawMessage) { var res easee.Observation - if err := json.Unmarshal(i, &res); err == nil { - var floatValue float64 - var boolValue bool - var intValue int - switch res.DataType { - case easee.Boolean: - boolValue = res.Value == "1" - case easee.Double: - floatValue, err = strconv.ParseFloat(res.Value, 64) - if err != nil { - c.log.ERROR.Printf("float conversion: %s", res.Value) - return - } - case easee.Integer: - intValue, err = strconv.Atoi(res.Value) - if err != nil { - c.log.ERROR.Printf("int conversion: %s", res.Value) - return - } - } - - c.mux.Lock() - defer c.mux.Unlock() - - switch res.ID { - case easee.IS_ENABLED: - c.chargerEnabled = boolValue - case easee.TOTAL_POWER: - c.currentPower = 1e3 * floatValue - case easee.SESSION_ENERGY: - c.sessionEnergy = floatValue - case easee.CIRCUIT_TOTAL_PHASE_CONDUCTOR_CURRENT_L1: - c.circuitTotalPhaseConductorCurrentL1 = floatValue - case easee.CIRCUIT_TOTAL_PHASE_CONDUCTOR_CURRENT_L2: - c.circuitTotalPhaseConductorCurrentL2 = floatValue - case easee.CIRCUIT_TOTAL_PHASE_CONDUCTOR_CURRENT_L3: - c.circuitTotalPhaseConductorCurrentL3 = floatValue - case easee.CHARGER_OP_MODE: - switch intValue { - case easee.ModeDisconnected: - c.chargeStatus = api.StatusA - case easee.ModeAwaitingStart, easee.ModeCompleted, easee.ModeReadyToCharge: - c.chargeStatus = api.StatusB - case easee.ModeCharging: - c.chargeStatus = api.StatusC - case easee.ModeError: - c.chargeStatus = api.StatusF - default: - c.log.ERROR.Printf("unknown opmode: %d", intValue) - c.chargeStatus = api.StatusNone - } - c.enabledStatus = intValue == easee.ModeCharging || intValue == easee.ModeReadyToCharge - } - c.updated = time.Now() - c.log.TRACE.Printf("%s: %+v", typ, res) - } else { + err := json.Unmarshal(i, &res) + if err != nil { c.log.ERROR.Printf("invalid message: %s %s %v", i, typ, err) + return } + + var floatValue float64 + var boolValue bool + var intValue int + + switch res.DataType { + case easee.Boolean: + boolValue = res.Value == "1" + case easee.Double: + floatValue, err = strconv.ParseFloat(res.Value, 64) + if err != nil { + c.log.ERROR.Printf("float conversion: %s", res.Value) + return + } + case easee.Integer: + intValue, err = strconv.Atoi(res.Value) + if err != nil { + c.log.ERROR.Printf("int conversion: %s", res.Value) + return + } + } + + c.mux.Lock() + defer c.mux.Unlock() + + switch res.ID { + case easee.IS_ENABLED: + c.chargerEnabled = boolValue + case easee.TOTAL_POWER: + c.currentPower = 1e3 * floatValue + case easee.SESSION_ENERGY: + c.sessionEnergy = floatValue + case easee.IN_CURRENT_T3: + c.currentL1 = floatValue + case easee.IN_CURRENT_T4: + c.currentL2 = floatValue + case easee.IN_CURRENT_T5: + c.currentL3 = floatValue + case easee.CHARGER_OP_MODE: + switch intValue { + case easee.ModeDisconnected: + c.chargeStatus = api.StatusA + case easee.ModeAwaitingStart, easee.ModeCompleted, easee.ModeReadyToCharge: + c.chargeStatus = api.StatusB + case easee.ModeCharging: + c.chargeStatus = api.StatusC + case easee.ModeError: + c.chargeStatus = api.StatusF + default: + c.chargeStatus = api.StatusNone + c.log.ERROR.Printf("unknown opmode: %d", intValue) + } + c.enabledStatus = intValue == easee.ModeCharging || intValue == easee.ModeReadyToCharge + } + + c.log.TRACE.Printf("%s: %+v", typ, res) + c.updated = time.Now() } // ProductUpdate implements the signalr receiver @@ -285,28 +258,6 @@ func (c *Easee) chargers() (res []easee.Charger, err error) { return res, err } -func (c *Easee) chargerConfig(charger string) (res easee.ChargerConfig, err error) { - uri := fmt.Sprintf("%s/chargers/%s/config", easee.API, charger) - - req, err := request.New(http.MethodGet, uri, nil, request.JSONEncoding) - if err == nil { - err = c.DoJSON(req, &res) - } - - return res, err -} - -func (c *Easee) chargerDetails(charger string) (res easee.Site, err error) { - uri := fmt.Sprintf("%s/chargers/%s/site", easee.API, charger) - - req, err := request.New(http.MethodGet, uri, nil, request.JSONEncoding) - if err == nil { - err = c.DoJSON(req, &res) - } - - return res, err -} - // Status implements the api.Charger interface func (c *Easee) Status() (api.ChargeStatus, error) { c.mux.Lock() @@ -325,10 +276,11 @@ func (c *Easee) Enabled() (bool, error) { // Enable implements the api.Charger interface func (c *Easee) Enable(enable bool) error { - // enable charger once if it's switched off c.mux.Lock() enablingRequired := enable && !c.chargerEnabled c.mux.Unlock() + + // enable charger once if it's switched off if enablingRequired { data := easee.ChargerSettings{ Enabled: &enable, @@ -336,10 +288,10 @@ func (c *Easee) Enable(enable bool) error { uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger) resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data)) - if err == nil { - resp.Body.Close() + if err != nil { return err } + resp.Body.Close() } // resume/stop charger @@ -363,22 +315,14 @@ var _ api.ChargerEx = (*Easee)(nil) // MaxCurrentMillis implements the api.ChargerEx interface func (c *Easee) MaxCurrentMillis(current float64) error { - var current23 float64 - if c.phases > 1 { - current23 = current + data := easee.ChargerSettings{ + DynamicChargerCurrent: ¤t, } - data := easee.CircuitSettings{ - DynamicCircuitCurrentP1: ¤t, - DynamicCircuitCurrentP2: ¤t23, - DynamicCircuitCurrentP3: ¤t23, - } - - uri := fmt.Sprintf("%s/sites/%d/circuits/%d/settings", easee.API, c.site, c.circuit) + uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger) resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data)) if err == nil { resp.Body.Close() - c.current = current } return err @@ -388,8 +332,21 @@ var _ api.ChargePhases = (*Easee)(nil) // Phases1p3p implements the api.ChargePhases interface func (c *Easee) Phases1p3p(phases int) error { - c.phases = phases - return c.MaxCurrentMillis(c.current) + if phases == 3 { + phases = 2 + } + + data := easee.ChargerSettings{ + PhaseMode: &phases, + } + + uri := fmt.Sprintf("%s/chargers/%s/settings", easee.API, c.charger) + resp, err := c.Post(uri, request.JSONContent, request.MarshalJSON(data)) + if err == nil { + resp.Body.Close() + } + + return err } var _ api.Meter = (*Easee)(nil) @@ -419,8 +376,5 @@ func (c *Easee) Currents() (float64, float64, float64, error) { c.mux.Lock() defer c.mux.Unlock() - return c.circuitTotalPhaseConductorCurrentL1, - c.circuitTotalPhaseConductorCurrentL2, - c.circuitTotalPhaseConductorCurrentL3, - nil + return c.currentL1, c.currentL2, c.currentL3, nil } diff --git a/charger/easee/message.go b/charger/easee/message.go index 577ed4bde..28c9cd627 100644 --- a/charger/easee/message.go +++ b/charger/easee/message.go @@ -122,22 +122,22 @@ type ChargerStatus struct { // ChargerSettings is the charger settings type type ChargerSettings struct { - Enabled *bool `json:"enabled,omitempty"` - EnableIdleCurrent *bool `json:"enableIdleCurrent,omitempty"` - LimitToSinglePhaseCharging *bool `json:"limitToSinglePhaseCharging,omitempty"` - LockCablePermanently *bool `json:"lockCablePermanently,omitempty"` - SmartButtonEnabled *bool `json:"smartButtonEnabled,omitempty"` - PhaseMode *int `json:"phaseMode,omitempty"` - SmartCharging *bool `json:"smartCharging,omitempty"` - LocalPreAuthorizeEnabled *bool `json:"localPreAuthorizeEnabled,omitempty"` - LocalAuthorizeOfflineEnabled *bool `json:"localAuthorizeOfflineEnabled,omitempty"` - AllowOfflineTxForUnknownID *bool `json:"allowOfflineTxForUnknownId,omitempty"` - OfflineChargingMode *int `json:"offlineChargingMode,omitempty"` - AuthorizationRequired *bool `json:"authorizationRequired,omitempty"` - RemoteStartRequired *bool `json:"remoteStartRequired,omitempty"` - LedStripBrightness *int `json:"ledStripBrightness,omitempty"` - MaxChargerCurrent *int `json:"maxChargerCurrent,omitempty"` - DynamicChargerCurrent *int `json:"dynamicChargerCurrent,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + EnableIdleCurrent *bool `json:"enableIdleCurrent,omitempty"` + LimitToSinglePhaseCharging *bool `json:"limitToSinglePhaseCharging,omitempty"` + LockCablePermanently *bool `json:"lockCablePermanently,omitempty"` + SmartButtonEnabled *bool `json:"smartButtonEnabled,omitempty"` + PhaseMode *int `json:"phaseMode,omitempty"` + SmartCharging *bool `json:"smartCharging,omitempty"` + LocalPreAuthorizeEnabled *bool `json:"localPreAuthorizeEnabled,omitempty"` + LocalAuthorizeOfflineEnabled *bool `json:"localAuthorizeOfflineEnabled,omitempty"` + AllowOfflineTxForUnknownID *bool `json:"allowOfflineTxForUnknownId,omitempty"` + OfflineChargingMode *int `json:"offlineChargingMode,omitempty"` + AuthorizationRequired *bool `json:"authorizationRequired,omitempty"` + RemoteStartRequired *bool `json:"remoteStartRequired,omitempty"` + LedStripBrightness *int `json:"ledStripBrightness,omitempty"` + MaxChargerCurrent *int `json:"maxChargerCurrent,omitempty"` + DynamicChargerCurrent *float64 `json:"dynamicChargerCurrent,omitempty"` } // CircuitSettings is the circuit settings type