Skip to content

Commit

Permalink
Ocpp: async RemoteStartTransaction by StatusNotification (#15872)
Browse files Browse the repository at this point in the history
  • Loading branch information
premultiply authored Sep 5, 2024
1 parent 333bcfa commit 901e487
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 59 deletions.
54 changes: 7 additions & 47 deletions charger/ocpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package charger

import (
"cmp"
"errors"
"fmt"
"math"
"slices"
Expand All @@ -14,19 +13,18 @@ import (
"github.com/evcc-io/evcc/util"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
"github.com/samber/lo"
)

// OCPP charger implementation
type OCPP struct {
log *util.Logger
cp *ocpp.CP
conn *ocpp.Connector
idtag string
phases int
enabled bool
current float64

remoteStart bool
stackLevelZero bool
lp loadpoint.API
}
Expand Down Expand Up @@ -58,7 +56,6 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
RemoteStart bool
}{
Connector: 1,
IdTag: defaultIdTag,
MeterInterval: 10 * time.Second,
ConnectTimeout: 5 * time.Minute,
}
Expand Down Expand Up @@ -118,7 +115,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
//go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Battery,Soc,func() (float64, error)"

// NewOCPP creates OCPP charger
func NewOCPP(id string, connector int, idtag string,
func NewOCPP(id string, connector int, idTag string,
meterValues string, meterInterval time.Duration,
stackLevelZero, remoteStart bool,
connectTimeout time.Duration,
Expand Down Expand Up @@ -157,21 +154,19 @@ func NewOCPP(id string, connector int, idtag string,
return nil, fmt.Errorf("invalid connector: %d", connector)
}

conn, err := ocpp.NewConnector(log, connector, cp)
if err != nil {
return nil, err
if remoteStart {
idTag = lo.CoalesceOrEmpty(idTag, cp.IdTag, defaultIdTag)
}

if idtag == defaultIdTag && cp.IdTag != "" {
idtag = cp.IdTag
conn, err := ocpp.NewConnector(log, connector, cp, idTag)
if err != nil {
return nil, err
}

c := &OCPP{
log: log,
cp: cp,
conn: conn,
idtag: idtag,
remoteStart: remoteStart,
stackLevelZero: stackLevelZero,
}

Expand All @@ -191,32 +186,13 @@ 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
}

// Status implements the api.Charger interface
func (c *OCPP) Status() (api.ChargeStatus, error) {
status, err := c.conn.Status()
if err != nil {
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 @@ -312,22 +288,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 ocpp.Wait(err, rc)
}

// setCurrent sets the TxDefaultChargingProfile with given current
func (c *OCPP) setCurrent(current float64) error {
err := c.conn.SetChargingProfile(c.createTxDefaultChargingProfile(math.Trunc(10*current) / 10))
Expand Down
31 changes: 30 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 @@ -31,16 +32,20 @@ type Connector struct {
txnCount int // change initial value to the last known global transaction. Needs persistence
txnId int
idTag string

remoteIdTag string
}

func NewConnector(log *util.Logger, id int, cp *CP) (*Connector, error) {
func NewConnector(log *util.Logger, id int, cp *CP, idTag string) (*Connector, error) {
conn := &Connector{
log: log,
cp: cp,
id: id,
clock: clock.New(),
statusC: make(chan struct{}, 1),
measurements: make(map[types.Measurand]types.SampledValue),

remoteIdTag: idTag,
}

err := cp.registerConnector(id, conn)
Expand Down Expand Up @@ -71,6 +76,24 @@ func (conn *Connector) TriggerMessageRequest(feature remotetrigger.MessageTrigge
})
}

func (conn *Connector) remoteStartTransactionRequest() {
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); err != nil {
conn.log.ERROR.Printf("failed to start remote transaction: %v", err)
}
}

func (conn *Connector) SetChargingProfile(profile *types.ChargingProfile) error {
return Instance().SetChargingProfileRequest(conn.cp.ID(), conn.id, profile)
}
Expand Down Expand Up @@ -166,6 +189,12 @@ 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
}

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.remoteIdTag != "" {
defer conn.remoteStartTransactionRequest()
} else {
conn.log.DEBUG.Printf("waiting for local authentication")
}
}

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)
suite.conn, _ = NewConnector(util.NewLogger("foo"), 1, suite.cp, "")

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 @@ -15,7 +15,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 @@ -28,7 +27,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 @@ -52,16 +51,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

0 comments on commit 901e487

Please sign in to comment.