Repository: evcc-io/evcc
Pull request #4932 (2022-10-24 17:13:01)

Fix status transitions to honor direct changes between A and C
Refs https://github.com/evcc-io/evcc/discussions/4877#discussioncomment-3953309


Patch:
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 3812a678..f2164782 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -449,8 +449,6 @@ func (lp *LoadPoint) evVehicleDisconnectHandler() {
 	lp.publish("chargedEnergy", lp.chargedEnergy)
 	lp.publish("connectedDuration", lp.clock.Since(lp.connectedTime))
 
-	lp.pushEvent(evVehicleDisconnect)
-
 	// remove charger vehicle id and stop potential detection
 	lp.setVehicleIdentifier("")
 	lp.stopVehicleDetection()
@@ -1047,6 +1045,33 @@ func (lp *LoadPoint) vehicleOdometer() {
 	}
 }
 
+// statusEvents converts the observed charger status change into a logical sequence of events
+func statusEvents(prevStatus, status api.ChargeStatus) []string {
+	res := make([]string, 0, 2)
+
+	// changed from A - connected
+	if prevStatus == api.StatusA || (status != api.StatusA && prevStatus == api.StatusNone) {
+		res = append(res, evVehicleConnect)
+	}
+
+	// changed to C - start charging
+	if status == api.StatusC {
+		res = append(res, evChargeStart)
+	}
+
+	// changed from C - stop charging
+	if prevStatus == api.StatusC {
+		res = append(res, evChargeStop)
+	}
+
+	// changed to A - disconnected
+	if status == api.StatusA {
+		res = append(res, evVehicleDisconnect)
+	}
+
+	return res
+}
+
 // updateChargerStatus updates charger status and detects car connected/disconnected events
 func (lp *LoadPoint) updateChargerStatus() error {
 	status, err := lp.charger.Status()
@@ -1059,26 +1084,18 @@ func (lp *LoadPoint) updateChargerStatus() error {
 	if prevStatus := lp.GetStatus(); status != prevStatus {
 		lp.setStatus(status)
 
-		// changed to A - disconnected - don't send on startup
-		if status == api.StatusA && prevStatus != api.StatusNone {
-			lp.bus.Publish(evVehicleDisconnect)
-		}
+		for _, ev := range statusEvents(prevStatus, status) {
+			lp.bus.Publish(ev)
 
-		// changed to B - connected - don't send on startup
-		if status == api.StatusB && prevStatus != api.StatusC {
+			// send connect/disconnect events except during startup
 			if prevStatus != api.StatusNone {
-				// send connected message if not startup
-				lp.pushEvent(evVehicleConnect)
+				switch ev {
+				case evVehicleConnect:
+					lp.pushEvent(evVehicleConnect)
+				case evVehicleDisconnect:
+					lp.pushEvent(evVehicleDisconnect)
+				}
 			}
-
-			lp.bus.Publish(evVehicleConnect)
-		}
-
-		// changed to C - start/stop charging cycle - handle before disconnect to update energy
-		if lp.charging() {
-			lp.bus.Publish(evChargeStart)
-		} else if prevStatus == api.StatusC {
-			lp.bus.Publish(evChargeStop)
 		}
 
 		// update whenever there is a state change


Test patch:
diff --git a/core/loadpoint_status_test.go b/core/loadpoint_status_test.go
new file mode 100644
index 00000000..55e7980e
--- /dev/null
+++ b/core/loadpoint_status_test.go
@@ -0,0 +1,33 @@
+package core
+
+import (
+	"testing"
+
+	"github.com/evcc-io/evcc/api"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStatusEvents(t *testing.T) {
+	tc := []struct {
+		from, to api.ChargeStatus
+		events   []string
+	}{
+		{api.StatusNone, api.StatusA, []string{evVehicleDisconnect}},
+		{api.StatusNone, api.StatusB, []string{evVehicleConnect}},
+		{api.StatusNone, api.StatusC, []string{evVehicleConnect, evChargeStart}},
+
+		{api.StatusA, api.StatusB, []string{evVehicleConnect}},
+		{api.StatusA, api.StatusC, []string{evVehicleConnect, evChargeStart}},
+
+		{api.StatusB, api.StatusA, []string{evVehicleDisconnect}},
+		{api.StatusB, api.StatusC, []string{evChargeStart}},
+
+		{api.StatusC, api.StatusA, []string{evChargeStop, evVehicleDisconnect}},
+		{api.StatusC, api.StatusB, []string{evChargeStop}},
+	}
+
+	for _, tc := range tc {
+		ev := statusEvents(tc.from, tc.to)
+		assert.Equalf(t, tc.events, ev, "from %s to %s got: %v", tc.from, tc.to, ev)
+	}
+}
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index c15c5f3d..e4caf6ad 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -418,6 +418,7 @@ func TestDisableAndEnableAtTargetSoC(t *testing.T) {
 
 	lp.enabled = true
 	lp.chargeCurrent = float64(minA)
+	lp.status = api.StatusC
 
 	t.Log("charging below soc target")
 	vehicle.EXPECT().SoC().Return(85.0, nil)