Ocpp: add fallback methods to Enabled() (#15242)

This commit is contained in:
premultiply 2024-08-05 17:38:56 +02:00 • committed by GitHub
parent 7453d1219e
commit 77a3cf2861
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 123 additions and 51 deletions

View file

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

View file

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