Easee: validate LIFETIME_ENERGY and SESSION_ENERGY (#9940)
This commit is contained in:
parent
31a4c7a7d5
commit
6b61239ce5
2 changed files with 106 additions and 26 deletions
123
charger/easee.go
123
charger/easee.go
|
|
@ -43,28 +43,32 @@ import (
|
|||
// Easee charger implementation
|
||||
type Easee struct {
|
||||
*request.Helper
|
||||
charger string
|
||||
site, circuit int
|
||||
updated time.Time
|
||||
log *util.Logger
|
||||
mux sync.Mutex
|
||||
done chan struct{}
|
||||
dynamicChargerCurrent float64
|
||||
current float64
|
||||
chargerEnabled bool
|
||||
smartCharging bool
|
||||
authorize bool
|
||||
enabled bool
|
||||
opMode int
|
||||
reasonForNoCurrent int
|
||||
phaseMode int
|
||||
charger string
|
||||
site, circuit int
|
||||
updated time.Time
|
||||
lastEnergyPollTriggered time.Time
|
||||
log *util.Logger
|
||||
mux sync.Mutex
|
||||
lastEnergyPollMux sync.Mutex
|
||||
done chan struct{}
|
||||
dynamicChargerCurrent float64
|
||||
current float64
|
||||
chargerEnabled bool
|
||||
smartCharging bool
|
||||
authorize bool
|
||||
enabled bool
|
||||
opMode int
|
||||
reasonForNoCurrent int
|
||||
phaseMode int
|
||||
sessionStartEnergy *float64
|
||||
currentPower, sessionEnergy, totalEnergy,
|
||||
currentL1, currentL2, currentL3 float64
|
||||
rfid string
|
||||
lp loadpoint.API
|
||||
cmdC chan easee.SignalRCommandResponse
|
||||
obsC chan easee.Observation
|
||||
obsTime map[easee.ObservationID]time.Time
|
||||
rfid string
|
||||
lp loadpoint.API
|
||||
cmdC chan easee.SignalRCommandResponse
|
||||
obsC chan easee.Observation
|
||||
obsTime map[easee.ObservationID]time.Time
|
||||
stopTicker chan struct{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -280,6 +284,7 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
|
|||
// received observation is outdated, ignoring
|
||||
return
|
||||
}
|
||||
|
||||
c.obsTime[res.ID] = res.Timestamp
|
||||
|
||||
switch res.ID {
|
||||
|
|
@ -292,9 +297,17 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
|
|||
case easee.TOTAL_POWER:
|
||||
c.currentPower = 1e3 * value.(float64)
|
||||
case easee.SESSION_ENERGY:
|
||||
c.sessionEnergy = value.(float64)
|
||||
// SESSION_ENERGY must not be set to 0 by Productupdates, they occur erratic
|
||||
// Reset to 0 is done in case CHARGER_OP_MODE
|
||||
if value.(float64) != 0 {
|
||||
c.sessionEnergy = value.(float64)
|
||||
}
|
||||
case easee.LIFETIME_ENERGY:
|
||||
c.totalEnergy = value.(float64)
|
||||
if c.sessionStartEnergy == nil {
|
||||
f := c.totalEnergy
|
||||
c.sessionStartEnergy = &f
|
||||
}
|
||||
case easee.IN_CURRENT_T3:
|
||||
c.currentL1 = value.(float64)
|
||||
case easee.IN_CURRENT_T4:
|
||||
|
|
@ -305,8 +318,55 @@ func (c *Easee) ProductUpdate(i json.RawMessage) {
|
|||
c.phaseMode = value.(int)
|
||||
case easee.DYNAMIC_CHARGER_CURRENT:
|
||||
c.dynamicChargerCurrent = value.(float64)
|
||||
|
||||
case easee.CHARGER_OP_MODE:
|
||||
c.opMode = value.(int)
|
||||
opMode := value.(int)
|
||||
|
||||
// New charging session pending, reset internal value of SESSION_ENERGY to 0, and its observation timestamp to "now".
|
||||
// This should be done in a proper way by the api, but it's not.
|
||||
// Remember value of LIFETIME_ENERGY as start value of the charging session
|
||||
if c.opMode <= easee.ModeDisconnected && opMode >= easee.ModeAwaitingStart {
|
||||
c.sessionEnergy = 0
|
||||
c.obsTime[easee.SESSION_ENERGY] = time.Now()
|
||||
c.sessionStartEnergy = nil
|
||||
}
|
||||
|
||||
// OpMode changed TO charging. Start ticker for periodic requests to update LIFETIME_ENERGY
|
||||
if c.opMode != easee.ModeCharging && opMode == easee.ModeCharging {
|
||||
if c.stopTicker == nil {
|
||||
c.stopTicker = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
for {
|
||||
select {
|
||||
case <-c.stopTicker:
|
||||
return
|
||||
case <-ticker.C:
|
||||
c.requestLifetimeEnergyUpdate()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// OpMode changed FROM charging to something else - stop ticker if channel exists
|
||||
if c.opMode == easee.ModeCharging && opMode != easee.ModeCharging && c.stopTicker != nil {
|
||||
close(c.stopTicker)
|
||||
c.stopTicker = nil
|
||||
}
|
||||
|
||||
// for relevant OpModes changes indicating a start or stop of the charging session, request new update of LIFETIME_ENERGY
|
||||
// relevant OpModes: leaving op modes 1 (car connected, charging will start uncontrolled if unauthorized)
|
||||
// and 3 (charging stopped or pause), or reaching op mode 1 (car disconnected) and 7 (charging paused/ended by de-authenticating)
|
||||
if c.opMode != opMode && // only if op mode actually changed AND
|
||||
(c.opMode == easee.ModeDisconnected || c.opMode == easee.ModeCharging || // from these op modes
|
||||
opMode == easee.ModeDisconnected || opMode == easee.ModeAwaitingAuthentication) { // or to these op modes
|
||||
c.requestLifetimeEnergyUpdate()
|
||||
}
|
||||
|
||||
c.opMode = opMode
|
||||
|
||||
case easee.REASON_FOR_NO_CURRENT:
|
||||
c.reasonForNoCurrent = value.(int)
|
||||
}
|
||||
|
|
@ -680,6 +740,18 @@ func (c *Easee) CurrentPower() (float64, error) {
|
|||
return c.currentPower, nil
|
||||
}
|
||||
|
||||
func (c *Easee) requestLifetimeEnergyUpdate() {
|
||||
c.lastEnergyPollMux.Lock()
|
||||
defer c.lastEnergyPollMux.Unlock()
|
||||
if time.Since(c.lastEnergyPollTriggered) > time.Minute*3 { // api rate limit, max once in 3 minutes
|
||||
uri := fmt.Sprintf("%s/chargers/%s/commands/%s", easee.API, c.charger, easee.PollLifetimeEnergy)
|
||||
if _, err := c.Post(uri, request.JSONContent, request.MarshalJSON(nil)); err != nil {
|
||||
c.log.WARN.Printf("Failed to trigger an update of LIFETIME_ENERGY: %v", err)
|
||||
}
|
||||
c.lastEnergyPollTriggered = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
var _ api.ChargeRater = (*Easee)(nil)
|
||||
|
||||
// ChargedEnergy implements the api.ChargeRater interface
|
||||
|
|
@ -687,6 +759,13 @@ func (c *Easee) ChargedEnergy() (float64, error) {
|
|||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
// return either the self calced session energy (current LIFETIME_ENERGY minus remembered start value),
|
||||
// or the SESSION_ENERGY value by the API. Each value could be lower than the other, depending on
|
||||
// order and receive timestamp of the product update. We want to return the higher (and newer) value.
|
||||
if c.sessionStartEnergy != nil {
|
||||
return max(c.sessionEnergy, c.totalEnergy-*c.sessionStartEnergy), nil
|
||||
}
|
||||
|
||||
return c.sessionEnergy, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ package easee
|
|||
const API = "https://api.easee.com/api"
|
||||
|
||||
const (
|
||||
ChargeStart = "start_charging"
|
||||
ChargeStop = "stop_charging"
|
||||
ChargePause = "pause_charging"
|
||||
ChargeResume = "resume_charging"
|
||||
ChargeStart = "start_charging"
|
||||
ChargeStop = "stop_charging"
|
||||
ChargePause = "pause_charging"
|
||||
ChargeResume = "resume_charging"
|
||||
PollLifetimeEnergy = "poll_lifetimeenergy"
|
||||
)
|
||||
|
||||
// charge mode definition
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue