Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ocpp: async RemoteStartTransaction by StatusNotification #15872

Merged
merged 13 commits into from
Sep 5, 2024
49 changes: 5 additions & 44 deletions charger/ocpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ type OCPP struct {
log *util.Logger
cp *ocpp.CP
conn *ocpp.Connector
idtag string
phases int
enabled bool
current float64

timeout time.Duration
remoteStart bool
stackLevelZero bool
lp loadpoint.API
}
Expand Down Expand Up @@ -160,21 +158,19 @@ func NewOCPP(id string, connector int, idtag string,
return nil, fmt.Errorf("invalid connector: %d", connector)
}

conn, err := ocpp.NewConnector(log, connector, cp, timeout)
if err != nil {
return nil, err
}

if idtag == defaultIdTag && cp.IdTag != "" {
idtag = cp.IdTag
}

conn, err := ocpp.NewConnector(log, connector, cp, timeout, remoteStart, idtag)
if err != nil {
return nil, err
}

c := &OCPP{
log: log,
cp: cp,
conn: conn,
idtag: idtag,
remoteStart: remoteStart,
stackLevelZero: stackLevelZero,
timeout: timeout,
}
Expand All @@ -195,13 +191,6 @@ func (c *OCPP) Connector() *ocpp.Connector {
return c.conn
}

func (c *OCPP) effectiveIdTag() string {
if idtag := c.conn.IdTag(); idtag != "" {
return idtag
}
return c.idtag
}

// wait waits for a CP roundtrip with timeout
func (c *OCPP) wait(err error, rc chan error) error {
return ocpp.Wait(err, rc, c.timeout)
Expand All @@ -214,18 +203,6 @@ func (c *OCPP) Status() (api.ChargeStatus, error) {
return api.StatusNone, err
}

if c.conn.NeedsAuthentication() {
if c.remoteStart {
// lock the cable by starting remote transaction after vehicle connected
if err := c.initTransaction(); err != nil {
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")
}
}

switch status {
case
core.ChargePointStatusAvailable, // "Available"
Expand Down Expand Up @@ -321,22 +298,6 @@ func (c *OCPP) Enable(enable bool) error {
return err
}

func (c *OCPP) initTransaction() error {
rc := make(chan error, 1)
err := ocpp.Instance().RemoteStartTransaction(c.cp.ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) {
if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted {
err = errors.New(string(resp.Status))
}

rc <- err
}, c.effectiveIdTag(), func(request *core.RemoteStartTransactionRequest) {
connector := c.conn.ID()
request.ConnectorId = &connector
})

return c.wait(err, rc)
}

func (c *OCPP) setChargingProfile(profile *types.ChargingProfile) error {
rc := make(chan error, 1)
err := ocpp.Instance().SetChargingProfile(c.cp.ID(), func(resp *smartcharging.SetChargingProfileConfirmation, err error) {
Expand Down
32 changes: 31 additions & 1 deletion charger/ocpp/connector.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ocpp

import (
"errors"
"fmt"
"strconv"
"strings"
Expand Down Expand Up @@ -32,9 +33,12 @@ type Connector struct {
txnCount int // change initial value to the last known global transaction. Needs persistence
txnId int
idTag string

remoteStart bool
remoteIdTag string
}

func NewConnector(log *util.Logger, id int, cp *CP, timeout time.Duration) (*Connector, error) {
func NewConnector(log *util.Logger, id int, cp *CP, timeout time.Duration, remoteStart bool, remoteIdTag string) (*Connector, error) {
premultiply marked this conversation as resolved.
Show resolved Hide resolved
conn := &Connector{
log: log,
cp: cp,
Expand All @@ -43,6 +47,8 @@ func NewConnector(log *util.Logger, id int, cp *CP, timeout time.Duration) (*Con
statusC: make(chan struct{}, 1),
measurements: make(map[types.Measurand]types.SampledValue),
timeout: timeout,
remoteStart: remoteStart,
remoteIdTag: remoteIdTag,
}

err := cp.registerConnector(id, conn)
Expand Down Expand Up @@ -147,9 +153,33 @@ func (conn *Connector) NeedsAuthentication() bool {
conn.mu.Lock()
defer conn.mu.Unlock()

return conn.isWaitingForAuth()
}

// isWaitingForAuth checks if meter values are outdated.
// Must only be called while holding lock.
func (conn *Connector) isWaitingForAuth() bool {
return conn.status != nil && conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing
}

func (conn *Connector) initTransaction() {
rc := make(chan error, 1)
err := Instance().RemoteStartTransaction(conn.cp.ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) {
if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted {
err = errors.New(string(resp.Status))
}

rc <- err
}, conn.remoteIdTag, func(request *core.RemoteStartTransactionRequest) {
connector := conn.id
request.ConnectorId = &connector
})

if err := Wait(err, rc, conn.timeout); err != nil {
conn.log.WARN.Printf("failed to start remote transaction: %v", err)
premultiply marked this conversation as resolved.
Show resolved Hide resolved
}
}

// isMeterTimeout checks if meter values are outdated.
// Must only be called while holding lock.
func (conn *Connector) isMeterTimeout() bool {
Expand Down
8 changes: 8 additions & 0 deletions charger/ocpp/connector_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ func (conn *Connector) StatusNotification(request *core.StatusNotificationReques
conn.log.TRACE.Printf("ignoring status: %s < %s", request.Timestamp.Time, conn.status.Timestamp)
}

if conn.isWaitingForAuth() {
if conn.remoteStart {
premultiply marked this conversation as resolved.
Show resolved Hide resolved
defer conn.initTransaction()
} else {
defer conn.log.DEBUG.Printf("waiting for local authentication")
premultiply marked this conversation as resolved.
Show resolved Hide resolved
premultiply marked this conversation as resolved.
Show resolved Hide resolved
}
}

return new(core.StatusNotificationConfirmation), nil
}

Expand Down
2 changes: 1 addition & 1 deletion charger/ocpp/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type connTestSuite struct {

func (suite *connTestSuite) SetupTest() {
suite.cp = NewChargePoint(util.NewLogger("foo"), "abc")
suite.conn, _ = NewConnector(util.NewLogger("foo"), 1, suite.cp, time.Minute)
suite.conn, _ = NewConnector(util.NewLogger("foo"), 1, suite.cp, time.Minute, false, "")

suite.clock = clock.NewMock()
suite.conn.clock = suite.clock
Expand Down
14 changes: 4 additions & 10 deletions charger/ocpp/cp_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ var (
)

func (cp *CP) Authorize(request *core.AuthorizeRequest) (*core.AuthorizeConfirmation, error) {
// TODO check if this authorizes foreign RFID tags
res := &core.AuthorizeConfirmation{
IdTagInfo: &types.IdTagInfo{
Status: types.AuthorizationStatusAccepted,
Expand All @@ -34,7 +33,7 @@ func (cp *CP) Authorize(request *core.AuthorizeRequest) (*core.AuthorizeConfirma
func (cp *CP) BootNotification(request *core.BootNotificationRequest) (*core.BootNotificationConfirmation, error) {
res := &core.BootNotificationConfirmation{
CurrentTime: types.Now(),
Interval: 60, // TODO
Interval: 60,
Status: core.RegistrationStatusAccepted,
}

Expand All @@ -58,16 +57,11 @@ func (cp *CP) StatusNotification(request *core.StatusNotificationRequest) (*core
return nil, ErrInvalidRequest
}

if request.ConnectorId == 0 {
return new(core.StatusNotificationConfirmation), nil
if conn := cp.connectorByID(request.ConnectorId); conn != nil {
return conn.StatusNotification(request)
}

conn := cp.connectorByID(request.ConnectorId)
if conn == nil {
return nil, ErrInvalidConnector
}

return conn.StatusNotification(request)
return new(core.StatusNotificationConfirmation), nil
}

func (cp *CP) DataTransfer(request *core.DataTransferRequest) (*core.DataTransferConfirmation, error) {
Expand Down
Loading