Easee: control at charger instead of circuit level (#1701)
This commit is contained in:
parent
8fc319708f
commit
56d13c2b46
2 changed files with 103 additions and 149 deletions
220
charger/easee.go
220
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue