Easee: control at charger instead of circuit level (#1701)

This commit is contained in:
andig 2021-10-05 11:15:52 +02:00 • committed by GitHub
parent 8fc319708f
commit 56d13c2b46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 103 additions and 149 deletions

View file

@ -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: &current,
}
data := easee.CircuitSettings{
DynamicCircuitCurrentP1: &current,
DynamicCircuitCurrentP2: &current23,
DynamicCircuitCurrentP3: &current23,
}
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
}

View file

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