Ocpp: async RemoteStartTransaction by StatusNotification (#15872)
This commit is contained in:
parent
333bcfab24
commit
901e487618
5 changed files with 50 additions and 59 deletions
|
|
@ -2,7 +2,6 @@ package charger
|
|||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"slices"
|
||||
|
|
@ -14,6 +13,7 @@ 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
|
||||
|
|
@ -21,12 +21,10 @@ 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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
@ -191,13 +186,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
|
||||
}
|
||||
|
||||
// Status implements the api.Charger interface
|
||||
func (c *OCPP) Status() (api.ChargeStatus, error) {
|
||||
status, err := c.conn.Status()
|
||||
|
|
@ -205,18 +193,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"
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package ocpp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -31,9 +32,11 @@ 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,
|
||||
|
|
@ -41,6 +44,8 @@ func NewConnector(log *util.Logger, id int, cp *CP) (*Connector, error) {
|
|||
clock: clock.New(),
|
||||
statusC: make(chan struct{}, 1),
|
||||
measurements: make(map[types.Measurand]types.SampledValue),
|
||||
|
||||
remoteIdTag: idTag,
|
||||
}
|
||||
|
||||
err := cp.registerConnector(id, conn)
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue