Ocpp: fix crash when unconfigured chargepoint connects (#10039)
This commit is contained in:
parent
e20224c242
commit
eb4fa2f081
5 changed files with 96 additions and 76 deletions
20
charger/ocpp/const.go
Normal file
20
charger/ocpp/const.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package ocpp
|
||||
|
||||
const (
|
||||
// Core profile keys
|
||||
KeyNumberOfConnectors = "NumberOfConnectors"
|
||||
|
||||
// Meter profile keys
|
||||
KeyMeterValuesSampledData = "MeterValuesSampledData"
|
||||
KeyMeterValueSampleInterval = "MeterValueSampleInterval"
|
||||
|
||||
// Smart Charging profile keys
|
||||
KeyChargeProfileMaxStackLevel = "ChargeProfileMaxStackLevel"
|
||||
KeyChargingScheduleAllowedChargingRateUnit = "ChargingScheduleAllowedChargingRateUnit"
|
||||
KeyChargingScheduleMaxPeriods = "ChargingScheduleMaxPeriods"
|
||||
KeyConnectorSwitch3to1PhaseSupported = "ConnectorSwitch3to1PhaseSupported"
|
||||
KeyMaxChargingProfilesInstalled = "MaxChargingProfilesInstalled"
|
||||
|
||||
// Alfen specific keys
|
||||
KeyAlfenPlugAndChargeIdentifier = "PlugAndChargeIdentifier"
|
||||
)
|
||||
|
|
@ -15,25 +15,6 @@ import (
|
|||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Core profile keys
|
||||
KeyNumberOfConnectors = "NumberOfConnectors"
|
||||
|
||||
// Meter profile keys
|
||||
KeyMeterValuesSampledData = "MeterValuesSampledData"
|
||||
KeyMeterValueSampleInterval = "MeterValueSampleInterval"
|
||||
|
||||
// Smart Charging profile keys
|
||||
KeyChargeProfileMaxStackLevel = "ChargeProfileMaxStackLevel"
|
||||
KeyChargingScheduleAllowedChargingRateUnit = "ChargingScheduleAllowedChargingRateUnit"
|
||||
KeyChargingScheduleMaxPeriods = "ChargingScheduleMaxPeriods"
|
||||
KeyConnectorSwitch3to1PhaseSupported = "ConnectorSwitch3to1PhaseSupported"
|
||||
KeyMaxChargingProfilesInstalled = "MaxChargingProfilesInstalled"
|
||||
|
||||
// Alfen specific keys
|
||||
KeyAlfenPlugAndChargeIdentifier = "PlugAndChargeIdentifier"
|
||||
)
|
||||
|
||||
// TODO support multiple connectors
|
||||
// Since ocpp-go interfaces at charge point level, we need to manage multiple connector separately
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ type CS struct {
|
|||
cps map[string]*CP
|
||||
}
|
||||
|
||||
// Register registers a chargepoint with the central system.
|
||||
// The chargepoint identified by id may already be connected in which case initial connection is triggered.
|
||||
// Register registers a charge point with the central system.
|
||||
// The charge point identified by id may already be connected in which case initial connection is triggered.
|
||||
func (cs *CS) Register(id string, cp *CP) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if _, ok := cs.cps[id]; ok && id == "" {
|
||||
return errors.New("cannot have >1 chargepoint with empty station id")
|
||||
return errors.New("cannot have >1 charge point with empty station id")
|
||||
}
|
||||
|
||||
// trigger unknown chargepoint connected
|
||||
// trigger unknown charge point connected
|
||||
if unknown, ok := cs.cps[id]; ok && unknown == nil {
|
||||
cp.connect(true)
|
||||
}
|
||||
|
|
@ -43,63 +43,67 @@ func (cs *CS) errorHandler(errC <-chan error) {
|
|||
}
|
||||
}
|
||||
|
||||
// chargepointByID returns a configured charge point identified by id.
|
||||
func (cs *CS) chargepointByID(id string) (*CP, error) {
|
||||
cp, ok := cs.cps[id]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown charge point: %s", id)
|
||||
}
|
||||
if cp == nil {
|
||||
return nil, fmt.Errorf("charge point not configured: %s", id)
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// NewChargePoint implements ocpp16.ChargePointConnectionHandler
|
||||
func (cs *CS) NewChargePoint(chargePoint ocpp16.ChargePointConnection) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if cp, err := cs.chargepointByID(chargePoint.ID()); err != nil {
|
||||
// check for anonymous chargepoint
|
||||
if cp, err := cs.chargepointByID(""); err == nil {
|
||||
cs.log.INFO.Printf("chargepoint connected, registering: %s", chargePoint.ID())
|
||||
// check for configured charge point
|
||||
cp, ok := cs.cps[chargePoint.ID()]
|
||||
if ok {
|
||||
cs.log.DEBUG.Printf("charge point connected: %s", chargePoint.ID())
|
||||
|
||||
// update id
|
||||
cp.RegisterID(chargePoint.ID())
|
||||
|
||||
cs.cps[chargePoint.ID()] = cp
|
||||
delete(cs.cps, "")
|
||||
|
||||
cp.connect(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cs.log.WARN.Printf("chargepoint connected, unknown: %s", chargePoint.ID())
|
||||
|
||||
// register unknown chargepoint
|
||||
// when chargepoint setup is complete, it will eventually be associated with the connected id
|
||||
cs.cps[chargePoint.ID()] = nil
|
||||
} else {
|
||||
cs.log.DEBUG.Printf("chargepoint connected: %s", chargePoint.ID())
|
||||
|
||||
// trigger initial connection if chargepoint is already setup
|
||||
// trigger initial connection if charge point is already setup
|
||||
if cp != nil {
|
||||
cp.connect(true)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// check for configured anonymous charge point
|
||||
cp, ok = cs.cps[""]
|
||||
if ok && cp != nil {
|
||||
cs.log.INFO.Printf("charge point connected, registering: %s", chargePoint.ID())
|
||||
|
||||
// update id
|
||||
cp.RegisterID(chargePoint.ID())
|
||||
|
||||
cs.cps[chargePoint.ID()] = cp
|
||||
delete(cs.cps, "")
|
||||
|
||||
cp.connect(true)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cs.log.WARN.Printf("unknown charge point connected: %s", chargePoint.ID())
|
||||
|
||||
// register unknown charge point
|
||||
// when charge point setup is complete, it will eventually be associated with the connected id
|
||||
cs.cps[chargePoint.ID()] = nil
|
||||
}
|
||||
|
||||
// ChargePointDisconnected implements ocpp16.ChargePointConnectionHandler
|
||||
func (cs *CS) ChargePointDisconnected(chargePoint ocpp16.ChargePointConnection) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if cp, err := cs.chargepointByID(chargePoint.ID()); err != nil {
|
||||
cs.log.ERROR.Printf("chargepoint disconnected: %v", err)
|
||||
} else {
|
||||
cs.log.DEBUG.Printf("chargepoint disconnected: %s", chargePoint.ID())
|
||||
cs.log.DEBUG.Printf("charge point disconnected: %s", chargePoint.ID())
|
||||
|
||||
if cp == nil {
|
||||
// remove unknown chargepoint
|
||||
delete(cs.cps, chargePoint.ID())
|
||||
} else {
|
||||
cp.connect(false)
|
||||
}
|
||||
if cp, err := cs.chargepointByID(chargePoint.ID()); err != nil {
|
||||
cp.connect(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package ocpp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
|
||||
|
|
@ -73,6 +75,7 @@ func (cs *CS) OnBootNotification(id string, request *core.BootNotificationReques
|
|||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println("OnBootNotification id", id)
|
||||
return cp.BootNotification(request)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,50 +27,52 @@ func TestOcpp(t *testing.T) {
|
|||
type ocppTestSuite struct {
|
||||
suite.Suite
|
||||
clock *clock.Mock
|
||||
cp ocpp16.ChargePoint
|
||||
}
|
||||
|
||||
func (suite *ocppTestSuite) SetupSuite() {
|
||||
// setup cs
|
||||
suite.NotNil(ocpp.Instance())
|
||||
|
||||
// setup cp
|
||||
suite.clock = clock.NewMock()
|
||||
cp := ocpp16.NewChargePoint("test", nil, nil)
|
||||
suite.NotNil(ocpp.Instance())
|
||||
}
|
||||
|
||||
func (suite *ocppTestSuite) startChargePoint(id string) ocpp16.ChargePoint {
|
||||
// set a handler for all callback functions
|
||||
triggerC := make(chan remotetrigger.MessageTrigger, 1)
|
||||
handler := &ChargePointHandler{triggerC: triggerC}
|
||||
handler := &ChargePointHandler{
|
||||
triggerC: make(chan remotetrigger.MessageTrigger, 1),
|
||||
}
|
||||
|
||||
// create charge point with handler
|
||||
cp := ocpp16.NewChargePoint(id, nil, nil)
|
||||
cp.SetCoreHandler(handler)
|
||||
cp.SetRemoteTriggerHandler(handler)
|
||||
|
||||
// let cs handle the trigger messages
|
||||
go func() {
|
||||
for msg := range triggerC {
|
||||
suite.handleTrigger(msg)
|
||||
for msg := range handler.triggerC {
|
||||
suite.handleTrigger(cp, msg)
|
||||
}
|
||||
}()
|
||||
|
||||
suite.cp = cp
|
||||
return cp
|
||||
}
|
||||
|
||||
func (suite *ocppTestSuite) handleTrigger(msg remotetrigger.MessageTrigger) {
|
||||
func (suite *ocppTestSuite) handleTrigger(cp ocpp16.ChargePoint, msg remotetrigger.MessageTrigger) {
|
||||
switch msg {
|
||||
case core.BootNotificationFeatureName:
|
||||
if res, err := suite.cp.BootNotification("demo", "evcc"); err != nil {
|
||||
if res, err := cp.BootNotification("demo", "evcc"); err != nil {
|
||||
suite.T().Log("BootNotification:", err)
|
||||
} else {
|
||||
suite.T().Log("BootNotification:", res)
|
||||
}
|
||||
|
||||
case core.StatusNotificationFeatureName:
|
||||
if res, err := suite.cp.StatusNotification(ocppTestConnector, core.NoError, core.ChargePointStatusAvailable); err != nil {
|
||||
if res, err := cp.StatusNotification(ocppTestConnector, core.NoError, core.ChargePointStatusAvailable); err != nil {
|
||||
suite.T().Log("StatusNotification:", err)
|
||||
} else {
|
||||
suite.T().Log("StatusNotification:", res)
|
||||
}
|
||||
|
||||
case core.MeterValuesFeatureName:
|
||||
if res, err := suite.cp.MeterValues(1, []types.MeterValue{
|
||||
if res, err := cp.MeterValues(1, []types.MeterValue{
|
||||
{
|
||||
Timestamp: types.NewDateTime(suite.clock.Now()),
|
||||
SampledValue: []types.SampledValue{
|
||||
|
|
@ -91,20 +93,21 @@ func (suite *ocppTestSuite) handleTrigger(msg remotetrigger.MessageTrigger) {
|
|||
|
||||
func (suite *ocppTestSuite) TestConnect() {
|
||||
// start cp client
|
||||
suite.NoError(suite.cp.Start(ocppTestUrl))
|
||||
suite.True(suite.cp.IsConnected())
|
||||
cp := suite.startChargePoint("test")
|
||||
suite.NoError(cp.Start(ocppTestUrl))
|
||||
suite.True(cp.IsConnected())
|
||||
|
||||
// start cp server
|
||||
c, err := NewOCPP("test", ocppTestConnector, "", "", 0, false, false, ocppTestConnectTimeout, ocppTestTimeout)
|
||||
suite.NoError(err)
|
||||
|
||||
if err != nil {
|
||||
suite.NoError(err)
|
||||
return
|
||||
}
|
||||
|
||||
suite.clock.Add(ocppTestTimeout)
|
||||
c.cp.TestClock(suite.clock)
|
||||
|
||||
// status
|
||||
_, err = c.Status()
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
@ -117,4 +120,13 @@ func (suite *ocppTestSuite) TestConnect() {
|
|||
f, err = c.totalEnergy()
|
||||
suite.NoError(err)
|
||||
suite.Equal(1.2, f)
|
||||
|
||||
// 2nd charge point
|
||||
cp2 := suite.startChargePoint("test2")
|
||||
suite.NoError(cp2.Start(ocppTestUrl))
|
||||
suite.True(cp2.IsConnected())
|
||||
|
||||
// error on unconfigured 2nd charge point
|
||||
_, err = cp2.BootNotification("demo", "evcc")
|
||||
suite.Error(err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue