Repository: andig/evcc (go)

Issue:
Keep settings and states in sync between EVSE and EVCC
Major settings like charge current and charge enabled should be kept in sync between internal EVCC and EVSE.

Due to any connection problem (shutdown or reboot of EVSE, bad wireless connection, EMP, manual settings on EVSE, internal EVSE logic etc.) settings can be desynced between EVCCs will and what the charger really does.
So EVCC should read the major settings from EVSE, compare and update them if they differ on a regular base where possible. Not all types of EVSE allow for reading the internal settings.

Appart from that it should make sure that the connection to EVSE is alive by implementing something like "heartbeat" or "ping" to monitor the connection #122 

Pull request description:
Synchronize enabled state with charger if state doesn't match
Fix #124 (partially), #169 and #157.

Der NRGKick scheint bzgl. Start des Ladevorgangs ein Eigenleben zu haben, die EVSE braucht eine Extraeinladung wenn sie schonmal geladen hat. Dieser PR sollte beide Probleme lösen und würde gleichzeitig auch bei einem Restart des Chargers aktiv werden.

Patch:
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 19137484..f198cb6d 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -494,6 +494,19 @@ func (lp *LoadPoint) resetGuard() {
 	lp.guardUpdated = lp.clock.Now().Add(-2 * lp.GuardDuration)
 }
 
+// syncSettings synchronizes charger settings to expected state
+func (lp *LoadPoint) syncSettings() {
+	enabled, err := lp.charger.Enabled()
+	if err == nil && enabled != lp.enabled {
+		log.ERROR.Printf("%s sync enabled", lp.Name)
+		err = lp.charger.Enable(lp.enabled)
+	}
+
+	if err != nil {
+		log.ERROR.Printf("%s charge controller error: %v", lp.Name, err)
+	}
+}
+
 // update is the main control function. It reevaluates meters and charger state
 func (lp *LoadPoint) update() {
 	// read and publish meters first
@@ -513,6 +526,11 @@ func (lp *LoadPoint) update() {
 	lp.publish("connected", lp.connected())
 	lp.publish("charging", lp.charging)
 
+	// sync settings with charger
+	if lp.status != api.StatusA {
+		lp.syncSettings()
+	}
+
 	// check if car connected and ready for charging
 	var err error
 


Test patch:
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index 3caec860..d19be765 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -185,6 +185,11 @@ func TestInitialUpdate(t *testing.T) {
 			cm.EXPECT().CurrentPower().Return(minPower, nil)
 		}
 
+		// syncSettings
+		if tc.status != api.StatusA {
+			wb.EXPECT().Enabled().Return(true, nil)
+		}
+
 		// disable if not connected
 		if tc.mode == api.ModeOff {
 			wb.EXPECT().Enable(false)
@@ -262,6 +267,7 @@ func TestImmediateOnOff(t *testing.T) {
 			wb.EXPECT().MaxCurrent(lpMaxCurrent)
 		}
 
+		wb.EXPECT().Enabled().Return(true, nil) // syncSettings
 		lp.update()
 
 		// max current if connected & mode now
@@ -294,6 +300,7 @@ func TestImmediateOnOff(t *testing.T) {
 
 		wb.EXPECT().MaxCurrent(2 * lpMinCurrent)
 
+		wb.EXPECT().Enabled().Return(true, nil) // syncSettings
 		lp.update()
 
 		// -- round 3
@@ -310,6 +317,8 @@ func TestImmediateOnOff(t *testing.T) {
 		wb.EXPECT().MaxCurrent(lpMinCurrent)
 
 		lp.SetMode(api.ModeOff)
+
+		wb.EXPECT().Enabled().Return(true, nil) // syncSettings
 		lp.update()
 
 		ctrl.Finish()