Ocpp: don't rely on charger sending boot notification (#4567)
This commit is contained in:
parent
20caf53a79
commit
421a993a34
5 changed files with 106 additions and 68 deletions
|
|
@ -46,8 +46,10 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
|
|||
MeterInterval time.Duration
|
||||
MeterValues string
|
||||
InitialReset core.ResetType
|
||||
Timeout time.Duration
|
||||
}{
|
||||
Connector: 1,
|
||||
Timeout: time.Minute,
|
||||
}
|
||||
|
||||
if err := util.DecodeOther(other, &cc); err != nil {
|
||||
|
|
@ -63,7 +65,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
|
|||
return nil, fmt.Errorf("unknown configuration option detected for reset: %s", cc.InitialReset)
|
||||
}
|
||||
|
||||
c, err := NewOCPP(cc.StationId, cc.Connector, cc.IdTag, cc.MeterValues, cc.MeterInterval, cc.InitialReset)
|
||||
c, err := NewOCPP(cc.StationId, cc.Connector, cc.IdTag, cc.MeterValues, cc.MeterInterval, cc.InitialReset, cc.Timeout)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
|
@ -89,28 +91,36 @@ 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.MeterCurrent,Currents,func() (float64, float64, float64, error)"
|
||||
|
||||
// NewOCPP creates OCPP charger
|
||||
func NewOCPP(id string, connector int, idtag string, meterValues string, meterInterval time.Duration, initialReset core.ResetType) (*OCPP, error) {
|
||||
cp, err := ocpp.Instance().Register(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewOCPP(id string, connector int, idtag string, meterValues string, meterInterval time.Duration, initialReset core.ResetType, timeout time.Duration) (*OCPP, error) {
|
||||
unit := "ocpp"
|
||||
if id != "" {
|
||||
unit = id
|
||||
}
|
||||
log := util.NewLogger(unit)
|
||||
|
||||
cp := ocpp.NewChargePoint(log, id, timeout)
|
||||
if err := ocpp.Instance().Register(id, cp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &OCPP{
|
||||
log: util.NewLogger(unit),
|
||||
log: log,
|
||||
cp: cp,
|
||||
connector: connector,
|
||||
idtag: idtag,
|
||||
}
|
||||
|
||||
if err := cp.Boot(); err != nil {
|
||||
return nil, err
|
||||
c.log.DEBUG.Printf("waiting for chargepoint: %v", timeout)
|
||||
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return nil, api.ErrTimeout
|
||||
case <-cp.HasConnected():
|
||||
}
|
||||
|
||||
// see who's there
|
||||
ocpp.Instance().TriggerMessageRequest(cp.ID(), core.BootNotificationFeatureName)
|
||||
|
||||
var (
|
||||
rc = make(chan error, 1)
|
||||
options []core.ConfigurationKey
|
||||
|
|
@ -118,7 +128,7 @@ func NewOCPP(id string, connector int, idtag string, meterValues string, meterIn
|
|||
)
|
||||
|
||||
// configured id may be empty, use registered id below
|
||||
err = ocpp.Instance().GetConfiguration(cp.ID(), func(resp *core.GetConfigurationConfirmation, err error) {
|
||||
err := ocpp.Instance().GetConfiguration(cp.ID(), func(resp *core.GetConfigurationConfirmation, err error) {
|
||||
if err != nil {
|
||||
rc <- err
|
||||
return
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ import (
|
|||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
)
|
||||
|
||||
const timeout = 2 * time.Minute
|
||||
|
||||
// Meter Profile Key
|
||||
const (
|
||||
KeyMeterValuesSampledData = "MeterValuesSampledData"
|
||||
|
|
@ -48,13 +46,15 @@ type smartChargingProfile struct {
|
|||
}
|
||||
|
||||
type CP struct {
|
||||
mu sync.Mutex
|
||||
log *util.Logger
|
||||
id string
|
||||
mu sync.Mutex
|
||||
log *util.Logger
|
||||
once sync.Once
|
||||
|
||||
bootC, statusC chan struct{}
|
||||
updated time.Time
|
||||
status *core.StatusNotificationRequest
|
||||
id string
|
||||
|
||||
connectC, statusC chan struct{}
|
||||
updated time.Time
|
||||
status *core.StatusNotificationRequest
|
||||
|
||||
timeout time.Duration
|
||||
meterUpdated time.Time
|
||||
|
|
@ -67,6 +67,17 @@ type CP struct {
|
|||
txnId int
|
||||
}
|
||||
|
||||
func NewChargePoint(log *util.Logger, id string, timeout time.Duration) *CP {
|
||||
return &CP{
|
||||
log: log,
|
||||
id: id,
|
||||
connectC: make(chan struct{}),
|
||||
statusC: make(chan struct{}),
|
||||
measurements: make(map[string]types.SampledValue),
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *CP) ID() string {
|
||||
cp.mu.Lock()
|
||||
defer cp.mu.Unlock()
|
||||
|
|
@ -85,6 +96,19 @@ func (cp *CP) RegisterID(id string) {
|
|||
cp.id = id
|
||||
}
|
||||
|
||||
func (cp *CP) Connect() {
|
||||
cp.mu.Lock()
|
||||
defer cp.mu.Unlock()
|
||||
|
||||
cp.once.Do(func() {
|
||||
close(cp.connectC)
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *CP) HasConnected() <-chan struct{} {
|
||||
return cp.connectC
|
||||
}
|
||||
|
||||
func (cp *CP) Initialized(timeout time.Duration) bool {
|
||||
cp.log.DEBUG.Printf("waiting for chargepoint status: %v", timeout)
|
||||
|
||||
|
|
@ -217,18 +241,6 @@ func parseIntOption(key SmartchargingChargeProfileKey, options map[string]core.C
|
|||
return val, nil
|
||||
}
|
||||
|
||||
// Boot waits for the CP to register itself
|
||||
func (cp *CP) Boot() error {
|
||||
cp.log.DEBUG.Printf("waiting for chargepoint: %v", timeout)
|
||||
|
||||
select {
|
||||
case <-cp.bootC:
|
||||
return nil
|
||||
case <-time.After(timeout):
|
||||
return api.ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// TransactionID returns the current transaction id
|
||||
func (cp *CP) TransactionID() int {
|
||||
cp.mu.Lock()
|
||||
|
|
@ -242,7 +254,7 @@ func (cp *CP) Status() (api.ChargeStatus, error) {
|
|||
|
||||
res := api.StatusNone
|
||||
|
||||
if time.Since(cp.updated) > timeout {
|
||||
if time.Since(cp.updated) > cp.timeout {
|
||||
return res, api.ErrTimeout
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,16 +29,6 @@ func (cp *CP) Authorize(request *core.AuthorizeRequest) (*core.AuthorizeConfirma
|
|||
func (cp *CP) BootNotification(request *core.BootNotificationRequest) (*core.BootNotificationConfirmation, error) {
|
||||
cp.log.TRACE.Printf("%T: %+v", request, request)
|
||||
|
||||
if request != nil {
|
||||
cp.log.DEBUG.Printf("chargepoint: %+v", request)
|
||||
}
|
||||
|
||||
// signal boot
|
||||
select {
|
||||
case cp.bootC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
res := &core.BootNotificationConfirmation{
|
||||
CurrentTime: types.NewDateTime(time.Now()),
|
||||
Interval: 60, // TODO
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/evcc-io/evcc/util"
|
||||
ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
)
|
||||
|
||||
type CS struct {
|
||||
|
|
@ -17,30 +16,17 @@ type CS struct {
|
|||
cps map[string]*CP
|
||||
}
|
||||
|
||||
func (cs *CS) Register(id string) (*CP, error) {
|
||||
unit := "ocpp"
|
||||
if id != "" {
|
||||
unit = id
|
||||
}
|
||||
|
||||
cp := &CP{
|
||||
id: id,
|
||||
log: util.NewLogger(unit),
|
||||
bootC: make(chan struct{}),
|
||||
statusC: make(chan struct{}),
|
||||
measurements: make(map[string]types.SampledValue),
|
||||
}
|
||||
|
||||
func (cs *CS) Register(id string, cp *CP) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if _, ok := cs.cps[id]; ok && id == "" {
|
||||
return nil, errors.New("cannot have >1 chargepoint with empty station id")
|
||||
return errors.New("cannot have >1 chargepoint with empty station id")
|
||||
}
|
||||
|
||||
cs.cps[id] = cp
|
||||
|
||||
return cp, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorHandler logs error channel
|
||||
|
|
@ -62,7 +48,7 @@ func (cs *CS) NewChargePoint(chargePoint ocpp16.ChargePointConnection) {
|
|||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if _, err := cs.chargepointByID(chargePoint.ID()); err != nil {
|
||||
if cp, err := cs.chargepointByID(chargePoint.ID()); err != nil {
|
||||
if cp, ok := cs.cps[""]; ok {
|
||||
cs.log.INFO.Printf("chargepoint connected, registering: %s", chargePoint.ID())
|
||||
|
||||
|
|
@ -71,12 +57,15 @@ func (cs *CS) NewChargePoint(chargePoint ocpp16.ChargePointConnection) {
|
|||
cs.cps[chargePoint.ID()] = cp
|
||||
delete(cs.cps, "")
|
||||
|
||||
cp.Connect()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
cs.log.WARN.Printf("chargepoint connected, ignoring: %s", chargePoint.ID())
|
||||
} else {
|
||||
cs.log.DEBUG.Printf("chargepoint connected: %s", chargePoint.ID())
|
||||
cp.Connect()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,17 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
"github.com/lorenzodonini/ocpp-go/ocppj"
|
||||
"github.com/lorenzodonini/ocpp-go/ws"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -16,9 +23,35 @@ const (
|
|||
)
|
||||
|
||||
func main() {
|
||||
chargePoint := ocpp16.NewChargePoint(chargePointId, nil, nil)
|
||||
// chargePoint := ocpp16.NewChargePoint(chargePointId, nil, nil)
|
||||
|
||||
// Set a handler for all callback functions
|
||||
// create websocket client
|
||||
client := ws.NewClient()
|
||||
client.AddOption(func(dialer *websocket.Dialer) {
|
||||
// Look for v1.6 subprotocol and add it, if not found
|
||||
alreadyExists := false
|
||||
for _, proto := range dialer.Subprotocols {
|
||||
if proto == types.V16Subprotocol {
|
||||
alreadyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyExists {
|
||||
dialer.Subprotocols = append(dialer.Subprotocols, types.V16Subprotocol)
|
||||
}
|
||||
})
|
||||
|
||||
// create chargepoint with connection tracking
|
||||
endpoint := ocppj.NewClient(chargePointId, client, nil, nil, core.Profile, localauth.Profile, firmware.Profile, reservation.Profile, remotetrigger.Profile, smartcharging.Profile)
|
||||
endpoint.SetOnReconnectedHandler(func() {
|
||||
fmt.Println("reconnect")
|
||||
})
|
||||
endpoint.SetOnDisconnectedHandler(func(err error) {
|
||||
fmt.Println("disconnect")
|
||||
})
|
||||
chargePoint := ocpp16.NewChargePoint(chargePointId, endpoint, client)
|
||||
|
||||
// set a handler for all callback functions
|
||||
handler := &ChargePointHandler{triggerC: make(chan remotetrigger.MessageTrigger, 1)}
|
||||
chargePoint.SetCoreHandler(handler)
|
||||
chargePoint.SetRemoteTriggerHandler(handler)
|
||||
|
|
@ -33,6 +66,13 @@ func main() {
|
|||
for msg := range handler.triggerC {
|
||||
fmt.Println("msg:", msg)
|
||||
switch msg {
|
||||
case core.BootNotificationFeatureName:
|
||||
if res, err := chargePoint.BootNotification("demo", "evcc"); err != nil {
|
||||
log.Println("BootNotification:", err)
|
||||
} else {
|
||||
log.Println("BootNotification:", res)
|
||||
}
|
||||
|
||||
case core.StatusNotificationFeatureName:
|
||||
if res, err := chargePoint.StatusNotification(1, core.NoError, core.ChargePointStatusAvailable); err != nil {
|
||||
log.Println("StatusNotification:", err)
|
||||
|
|
@ -41,12 +81,14 @@ func main() {
|
|||
}
|
||||
|
||||
case core.MeterValuesFeatureName:
|
||||
if _, err := chargePoint.MeterValues(1, []types.MeterValue{
|
||||
if res, err := chargePoint.MeterValues(1, []types.MeterValue{
|
||||
{SampledValue: []types.SampledValue{
|
||||
{Measurand: types.MeasurandPowerActiveImport, Value: "1000"},
|
||||
}},
|
||||
}); err != nil {
|
||||
log.Println("MeterValues:", err)
|
||||
} else {
|
||||
log.Println("MeterValues:", res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,11 +100,6 @@ func main() {
|
|||
}
|
||||
|
||||
log.Printf("connected to central system at %v", url)
|
||||
if res, err := chargePoint.BootNotification("model1", "vendor1"); err != nil {
|
||||
log.Fatal("BootNotification", err)
|
||||
} else {
|
||||
log.Printf("status: %v, interval: %v", res.Status, res.Interval)
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue