From 77a3cf2861774f5b90c684543bef42c901e5f4ff Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:38:56 +0200 Subject: [PATCH] Ocpp: add fallback methods to Enabled() (#15242) --- charger/ocpp.go | 103 ++++++++++++++++++++++++++++++-------- charger/ocpp/connector.go | 71 +++++++++++++++----------- 2 files changed, 123 insertions(+), 51 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 6274e5400..f65798e46 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -26,6 +26,7 @@ type OCPP struct { idtag string phases int current float64 + enabled bool meterValuesSample string timeout time.Duration phaseSwitching bool @@ -349,37 +350,95 @@ func (c *OCPP) wait(err error, rc chan error) error { // Status implements the api.Charger interface func (c *OCPP) Status() (api.ChargeStatus, error) { - if c.remoteStart { - needtxn, err := c.conn.NeedsTransaction() - if err != nil { - return api.StatusNone, err - } + status, err := c.conn.Status() + if err != nil { + return api.StatusNone, err + } - if needtxn { + if c.conn.NeedsAuthentication() { + if c.remoteStart { // lock the cable by starting remote transaction after vehicle connected if err := c.initTransaction(); err != nil { - return api.StatusNone, err + c.log.WARN.Printf("failed to start remote transaction: %v", err) } + } else { + // TODO: bring this status to UI + c.log.WARN.Printf("waiting for local authentication") } } - return c.conn.Status() + switch status { + case + core.ChargePointStatusAvailable, // "Available" + core.ChargePointStatusUnavailable: // "Unavailable" + return api.StatusA, nil + case + core.ChargePointStatusPreparing, // "Preparing" + core.ChargePointStatusSuspendedEVSE, // "SuspendedEVSE" + core.ChargePointStatusSuspendedEV, // "SuspendedEV" + core.ChargePointStatusFinishing: // "Finishing" + return api.StatusB, nil + case + core.ChargePointStatusCharging: // "Charging" + return api.StatusC, nil + case + core.ChargePointStatusReserved, // "Reserved" + core.ChargePointStatusFaulted: // "Faulted" + return api.StatusF, fmt.Errorf("chargepoint status: %s", status) + default: + return api.StatusNone, fmt.Errorf("invalid chargepoint status: %s", status) + } } // Enabled implements the api.Charger interface func (c *OCPP) Enabled() (bool, error) { - current, err := c.getCurrent() + if s, err := c.conn.Status(); err == nil { + switch s { + case + core.ChargePointStatusSuspendedEVSE: + return false, nil + case + core.ChargePointStatusCharging, + core.ChargePointStatusSuspendedEV: + return true, nil + } + } - return current > 0, err + // fallback to the "offered" measurands + if c.hasMeasurement(types.MeasurandCurrentOffered) { + if v, err := c.getMaxCurrent(); err == nil { + return v > 0, nil + } + } + if c.hasMeasurement(types.MeasurandPowerOffered) { + if v, err := c.getMaxPower(); err == nil { + return v > 0, err + } + } + + // fallback to querying the active charging profile schedule limit + if v, err := c.getScheduleLimit(); err == nil { + return v > 0, nil + } + + // fallback to cached value as last resort + return c.enabled, nil } +// Enable implements the api.Charger interface func (c *OCPP) Enable(enable bool) error { var current float64 if enable { current = c.current } - return c.setCurrent(current) + err := c.setCurrent(current) + if err == nil { + // cache enabled state as last fallback option + c.enabled = enable + } + + return err } func (c *OCPP) initTransaction() error { @@ -421,15 +480,12 @@ func (c *OCPP) setCurrent(current float64) error { return err } -// getCurrent returns the internal current offered by the chargepoint -func (c *OCPP) getCurrent() (float64, error) { - var current float64 +// getScheduleLimit queries the current or power limit the charge point is currently set to offer +func (c *OCPP) getScheduleLimit() (float64, error) { + const duration = 60 // duration of requested schedule in seconds - if c.hasMeasurement(types.MeasurandCurrentOffered) { - return c.getMaxCurrent() - } + var limit float64 - // fallback to GetCompositeSchedule request rc := make(chan error, 1) err := ocpp.Instance().GetCompositeSchedule(c.conn.ChargePoint().ID(), func(resp *smartcharging.GetCompositeScheduleConfirmation, err error) { if err == nil && resp != nil && resp.Status != smartcharging.GetCompositeScheduleStatusAccepted { @@ -438,18 +494,19 @@ func (c *OCPP) getCurrent() (float64, error) { if err == nil { if resp.ChargingSchedule != nil && len(resp.ChargingSchedule.ChargingSchedulePeriod) > 0 { - current = resp.ChargingSchedule.ChargingSchedulePeriod[0].Limit + // return first (current) period limit + limit = resp.ChargingSchedule.ChargingSchedulePeriod[0].Limit } else { err = fmt.Errorf("invalid ChargingSchedule") } } rc <- err - }, c.conn.ID(), 1) + }, c.conn.ID(), duration) err = c.wait(err, rc) - return current, err + return limit, err } // createTxDefaultChargingProfile returns a TxDefaultChargingProfile with given current @@ -506,6 +563,10 @@ func (c *OCPP) getMaxCurrent() (float64, error) { return c.conn.GetMaxCurrent() } +func (c *OCPP) getMaxPower() (float64, error) { + return c.conn.GetMaxPower() +} + // currentPower implements the api.Meter interface func (c *OCPP) currentPower() (float64, error) { return c.conn.CurrentPower() diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 2f79e712a..363eac0b5 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -128,52 +128,36 @@ func (conn *Connector) TransactionID() (int, error) { return conn.txnId, nil } -func (conn *Connector) Status() (api.ChargeStatus, error) { +// Status returns the unmapped charge point status +func (conn *Connector) Status() (core.ChargePointStatus, error) { + if !conn.cp.Connected() { + return "", api.ErrTimeout + } + conn.mu.Lock() defer conn.mu.Unlock() - res := api.StatusNone - - if !conn.cp.Connected() { - return res, api.ErrTimeout + if conn.status == nil { + return core.ChargePointStatusUnavailable, nil } if conn.status.ErrorCode != core.NoError { - return res, fmt.Errorf("%s: %s", conn.status.ErrorCode, conn.status.Info) + return "", fmt.Errorf("%s: %s", conn.status.ErrorCode, conn.status.Info) } - switch conn.status.Status { - case core.ChargePointStatusAvailable, // "Available" - core.ChargePointStatusUnavailable: // "Unavailable" - res = api.StatusA - case - core.ChargePointStatusPreparing, // "Preparing" - core.ChargePointStatusSuspendedEVSE, // "SuspendedEVSE" - core.ChargePointStatusSuspendedEV, // "SuspendedEV" - core.ChargePointStatusFinishing: // "Finishing" - res = api.StatusB - case core.ChargePointStatusCharging: // "Charging" - res = api.StatusC - case core.ChargePointStatusReserved, // "Reserved" - core.ChargePointStatusFaulted: // "Faulted" - return api.StatusF, fmt.Errorf("chargepoint status: %s", conn.status.ErrorCode) - default: - return api.StatusNone, fmt.Errorf("invalid chargepoint status: %s", conn.status.Status) - } - - return res, nil + return conn.status.Status, nil } -// NeedsTransaction checks if an initial RemoteStart of a transaction is required -func (conn *Connector) NeedsTransaction() (bool, error) { +// NeedsAuthentication checks if local authentication or an initial RemoteStartTransaction is required +func (conn *Connector) NeedsAuthentication() bool { if !conn.cp.Connected() { - return false, api.ErrTimeout + return false } conn.mu.Lock() defer conn.mu.Unlock() - return conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing, nil + return conn.status != nil && conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing } // isMeterTimeout checks if meter values are outdated. @@ -182,6 +166,9 @@ func (conn *Connector) isMeterTimeout() bool { return conn.timeout > 0 && conn.clock.Since(conn.meterUpdated) > conn.timeout } +var _ api.CurrentGetter = (*Connector)(nil) + +// GetMaxCurrent returns the maximum phase current the charge point is set to offer func (conn *Connector) GetMaxCurrent() (float64, error) { if !conn.cp.Connected() { return 0, api.ErrTimeout @@ -203,6 +190,30 @@ func (conn *Connector) GetMaxCurrent() (float64, error) { return 0, api.ErrNotAvailable } +// GetMaxPower returns the maximum power the charge point is set to offer +func (conn *Connector) GetMaxPower() (float64, error) { + if !conn.cp.Connected() { + return 0, api.ErrTimeout + } + + conn.mu.Lock() + defer conn.mu.Unlock() + + // fallthrough for last value on timeout when no transaction is running + if conn.txnId != 0 && conn.isMeterTimeout() { + return 0, api.ErrTimeout + } + + if m, ok := conn.measurements[types.MeasurandPowerOffered]; ok { + f, err := strconv.ParseFloat(m.Value, 64) + return scale(f, m.Unit), err + } + + return 0, api.ErrNotAvailable +} + +var _ api.Meter = (*Connector)(nil) + func (conn *Connector) CurrentPower() (float64, error) { if !conn.cp.Connected() { return 0, api.ErrTimeout