parent
0b20540f74
commit
f2a69620cc
5 changed files with 69 additions and 0 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
|
||||
|
|
@ -212,6 +213,27 @@ func (cp *CP) HasConnected() <-chan struct{} {
|
|||
return cp.connectC
|
||||
}
|
||||
|
||||
// waitConnected blocks until the charge point is connected (i.e. a
|
||||
// BootNotification has been received after the most recent disconnect).
|
||||
// Returns immediately if already connected, otherwise waits for the next
|
||||
// BootNotification on the coalescing request channel, bounded by Timeout.
|
||||
func (cp *CP) waitConnected(ctx context.Context) error {
|
||||
if cp.Connected() {
|
||||
return nil
|
||||
}
|
||||
|
||||
cp.log.DEBUG.Printf("charge point disconnected, waiting for reconnect")
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(Timeout):
|
||||
return api.ErrTimeout
|
||||
case <-cp.bootNotificationRequestC:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MonitorReboot ensures the given function runs only once per CP instance.
|
||||
// Used to start the reboot monitor goroutine for multi-connector charge points.
|
||||
func (cp *CP) MonitorReboot(ctx context.Context, setup func() error) {
|
||||
|
|
|
|||
|
|
@ -239,3 +239,29 @@ func TestMonitorRebootOnlyOnce(t *testing.T) {
|
|||
require.Eventually(t, func() bool { return callCount.Load() == 1 }, time.Second, 10*time.Millisecond,
|
||||
"setup should be called exactly once")
|
||||
}
|
||||
|
||||
func TestWaitConnectedReturnsImmediatelyWhenConnected(t *testing.T) {
|
||||
log := util.NewLogger("test")
|
||||
cp := NewChargePoint(log, "test-cp")
|
||||
|
||||
cp.connect(true)
|
||||
require.NoError(t, cp.waitConnected(t.Context()))
|
||||
}
|
||||
|
||||
func TestWaitConnectedResumesOnReconnectBoot(t *testing.T) {
|
||||
log := util.NewLogger("test")
|
||||
cp := NewChargePoint(log, "test-cp")
|
||||
|
||||
// not connected, simulates the CP having dropped mid-setup
|
||||
require.False(t, cp.Connected())
|
||||
|
||||
// schedule a reconnect's BootNotification after a short delay
|
||||
go func() {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
_, _ = cp.OnBootNotification(&core.BootNotificationRequest{
|
||||
ChargePointModel: "TestModel",
|
||||
})
|
||||
}()
|
||||
|
||||
require.NoError(t, cp.waitConnected(t.Context()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@ func (cp *CP) Setup(ctx context.Context, meterValues string, meterInterval time.
|
|||
cp.log.DEBUG.Printf("failed configuring availability: %v", err)
|
||||
}
|
||||
|
||||
// Some chargers (e.g. EN+/EVSEDO FW 1.1.805, issue #30113) hang on ChangeAvailability
|
||||
// and drop the WebSocket. Without this, every subsequent request fails with
|
||||
// "no client exists" and aborts charger creation fatally.
|
||||
if err := cp.waitConnected(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// auto configuration
|
||||
desiredMeasurands := "Power.Active.Import,Energy.Active.Import.Register,Current.Import,Voltage,Current.Offered,Power.Offered,SoC"
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ func wait(err error, rc chan error) error {
|
|||
if oe, ok := errors.AsType[*ocpp.Error](err); ok && oe.Code == ocppj.GenericError {
|
||||
err = api.ErrTimeout
|
||||
}
|
||||
} else if strings.Contains(err.Error(), "no client") {
|
||||
// The ocpp-go dispatcher returns "cannot send request ..., no client ... exists"
|
||||
// when the CP disconnected before the request could be queued. Treat as transient
|
||||
// timeout so callers can react to a reconnect instead of aborting setup fatally.
|
||||
err = api.ErrTimeout
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
package ocpp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWaitMapsNoClientToTimeout(t *testing.T) {
|
||||
// dispatcher returns this verbatim when the CP disconnected before the request could be queued
|
||||
err := errors.New("cannot send request 4711, no client ABC123 exists")
|
||||
got := wait(err, nil)
|
||||
assert.ErrorIs(t, got, api.ErrTimeout)
|
||||
}
|
||||
|
||||
func TestSortByAge(t *testing.T) {
|
||||
assert.Equal(t, []types.MeterValue{
|
||||
{Timestamp: nil},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue