OCPP: proactively trigger BootNotification after WebSocket connect (#28540)

This commit is contained in:
Nick Veenhof 2026-04-12 13:41:43 +02:00 • committed by GitHub
parent d5c9db02c4
commit 20de0b9da1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 41 additions and 0 deletions

View file

@ -4,6 +4,11 @@ import "time"
var Timeout = time.Minute // default request / response timeout on protocol level
// triggerBootDelay defines how long to wait after WebSocket connect before
// proactively triggering a BootNotification. This allows the connection to
// stabilize and gives the charger a chance to send a spontaneous BootNotification.
const triggerBootDelay = 5 * time.Second
const (
// Core profile keys
KeyMeterValueSampleInterval = "MeterValueSampleInterval"

View file

@ -8,6 +8,7 @@ import (
"github.com/evcc-io/evcc/util"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
)
@ -142,12 +143,47 @@ func (cp *CP) connect(connect bool) {
// onTransportConnect is called when the WebSocket connection is established.
// Instead of marking the CP as connected immediately, it waits for the
// BootNotification handshake to complete (or a timeout to expire).
//
// Some chargers (e.g. Wallbox Pulsar Pro FW 6.x) do not send BootNotification
// spontaneously on connect. For these chargers, we proactively trigger it
// after a short delay, which typically yields a response within 1 second
// instead of waiting for the full timeout.
func (cp *CP) onTransportConnect() {
cp.mu.Lock()
defer cp.mu.Unlock()
cp.stopBootTimer()
cp.bootTimer = time.AfterFunc(Timeout, cp.onBootTimeout)
// Proactively trigger BootNotification after a short delay.
// This helps chargers that don't send it spontaneously (e.g. Wallbox FW 6.x).
// The TriggerMessage is sent directly via the OCPP instance, bypassing the
// Connected() check which would fail at this point.
time.AfterFunc(triggerBootDelay, func() {
cp.mu.RLock()
// If BootNotification already arrived or timer was cancelled (disconnect),
// there is nothing to do.
if cp.bootTimer == nil || cp.BootNotificationResult != nil {
cp.mu.RUnlock()
return
}
cp.mu.RUnlock()
cp.log.DEBUG.Printf("proactively triggering BootNotification")
if err := Instance().TriggerMessage(
cp.id,
func(conf *remotetrigger.TriggerMessageConfirmation, err error) {
if err != nil {
cp.log.ERROR.Printf("trigger BootNotification response error: %v", err)
}
},
core.BootNotificationFeatureName,
func(request *remotetrigger.TriggerMessageRequest) {},
); err != nil {
cp.log.ERROR.Printf("failed to trigger BootNotification: %v", err)
}
})
}
// onBootTimeout is called when the BootNotification wait timer expires.