Repository: evcc-io/evcc
Pull request #3860 (2022-07-15 16:49:20)

Add test if vehicle defaults are applied


Patch:
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 75322276..2230a8c5 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -244,8 +244,7 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
 		lp.log.WARN.Printf("locking phase config to %dp for switchable charger", lp.DefaultPhases)
 	}
 
-	// allow target charge handler to access loadpoint
-	lp.socTimer = soc.NewTimer(lp.log, &adapter{LoadPoint: lp})
+	// validate thresholds
 	if lp.Enable.Threshold > lp.Disable.Threshold {
 		lp.log.WARN.Printf("PV mode enable threshold (%.0fW) is larger than disable threshold (%.0fW)", lp.Enable.Threshold, lp.Disable.Threshold)
 	} else if lp.Enable.Threshold > 0 {
@@ -277,6 +276,9 @@ func NewLoadPoint(log *util.Logger) *LoadPoint {
 		tasks:         aq.New(),
 	}
 
+	// allow target charge handler to access loadpoint
+	lp.socTimer = soc.NewTimer(lp.log, &adapter{LoadPoint: lp})
+
 	return lp
 }
 
@@ -443,12 +445,13 @@ func (lp *LoadPoint) evVehicleDisconnectHandler() {
 
 	// remove active vehicle if not default
 	if lp.vehicle != lp.defaultVehicle {
-		lp.setActiveVehicle(nil)
+		lp.setActiveVehicle(lp.defaultVehicle)
 		lp.unpublishVehicle()
 	}
 
 	// set default mode on disconnect
 	if lp.ResetOnDisconnect {
+		// TODO respect defaultVehicle
 		lp.applyAction(lp.onDisconnect)
 	}
 


Test patch:
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index 13c6ac57..e72a86cb 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -33,9 +33,7 @@ func (n *Null) ChargingTime() (time.Duration, error) {
 	return 0, nil
 }
 
-func attachListeners(t *testing.T, lp *LoadPoint) {
-	Voltage = 230 // V
-
+func createChannels(t *testing.T) (chan util.Param, chan push.Event, chan *LoadPoint) {
 	uiChan := make(chan util.Param)
 	pushChan := make(chan push.Event)
 	lpChan := make(chan *LoadPoint)
@@ -60,11 +58,24 @@ func attachListeners(t *testing.T, lp *LoadPoint) {
 		}
 	}()
 
+	return uiChan, pushChan, lpChan
+}
+
+func attachChannels(lp *LoadPoint, uiChan chan util.Param, pushChan chan push.Event, lpChan chan *LoadPoint) {
+	lp.uiChan = uiChan
+	lp.pushChan = pushChan
+	lp.lpChan = lpChan
+}
+
+func attachListeners(t *testing.T, lp *LoadPoint) {
+	Voltage = 230 // V
+
 	if charger, ok := lp.charger.(*mock.MockCharger); ok && charger != nil {
 		charger.EXPECT().Enabled().Return(true, nil)
 		charger.EXPECT().MaxCurrent(int64(lp.MinCurrent)).Return(nil)
 	}
 
+	uiChan, pushChan, lpChan := createChannels(t)
 	lp.Prepare(uiChan, pushChan, lpChan)
 }
 
@@ -287,7 +298,6 @@ func TestPVHysteresis(t *testing.T) {
 	}
 
 	for _, status := range []api.ChargeStatus{api.StatusB, api.StatusC} {
-
 		for _, tc := range tc {
 			t.Log(tc)
 
@@ -823,3 +833,115 @@ func TestVehicleDetectByID(t *testing.T) {
 		}
 	}
 }
+
+func TestDefaultVehicle(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	dflt := mock.NewMockVehicle(ctrl)
+	dflt.EXPECT().Title().Return("default").AnyTimes()
+	dflt.EXPECT().Capacity().AnyTimes()
+	dflt.EXPECT().Phases().AnyTimes()
+	dflt.EXPECT().OnIdentified().AnyTimes()
+
+	vehicle := mock.NewMockVehicle(ctrl)
+	vehicle.EXPECT().Title().Return("target").AnyTimes()
+	vehicle.EXPECT().Capacity().AnyTimes()
+	vehicle.EXPECT().Phases().AnyTimes()
+	vehicle.EXPECT().OnIdentified().AnyTimes()
+
+	lp := NewLoadPoint(util.NewLogger("foo"))
+	lp.defaultVehicle = dflt
+
+	// populate channels
+	x, y, z := createChannels(t)
+	attachChannels(lp, x, y, z)
+
+	title := func(v api.Vehicle) string {
+		if v == nil {
+			return "<nil>"
+		}
+		return v.Title()
+	}
+
+	// non-default vehicle identified
+	lp.setActiveVehicle(vehicle)
+	if lp.vehicle != vehicle {
+		t.Errorf("expected %v, got %v", title(vehicle), title(lp.vehicle))
+	}
+
+	// non-default vehicle disconnected
+	lp.evVehicleDisconnectHandler()
+	if lp.vehicle != dflt {
+		t.Errorf("expected %v, got %v", title(dflt), title(lp.vehicle))
+	}
+
+	// default vehicle disconnected
+	lp.evVehicleDisconnectHandler()
+	if lp.vehicle != dflt {
+		t.Errorf("expected %v, got %v", title(dflt), title(lp.vehicle))
+	}
+
+	// guest connected
+	lp.setActiveVehicle(nil)
+	if lp.vehicle != nil {
+		t.Errorf("expected %v, got %v", nil, title(lp.vehicle))
+	}
+}
+
+func TestApplyVehicleDefaults(t *testing.T) {
+	ctrl := gomock.NewController(t)
+
+	newConfig := func(mode api.ChargeMode, minCurrent, maxCurrent float64, minSoC, targetSoC int) api.ActionConfig {
+		return api.ActionConfig{
+			Mode:       &mode,
+			MinCurrent: &minCurrent,
+			MaxCurrent: &maxCurrent,
+			MinSoC:     &minSoC,
+			TargetSoC:  &targetSoC,
+		}
+	}
+
+	assertConfig := func(lp *LoadPoint, conf api.ActionConfig) {
+		if lp.Mode != *conf.Mode {
+			t.Errorf("expected mode %v, got %v", *conf.Mode, lp.Mode)
+		}
+		if lp.MinCurrent != *conf.MinCurrent {
+			t.Errorf("expected minCurrent %v, got %v", *conf.MinCurrent, lp.MinCurrent)
+		}
+		if lp.MaxCurrent != *conf.MaxCurrent {
+			t.Errorf("expected maxCurrent %v, got %v", *conf.MaxCurrent, lp.MaxCurrent)
+		}
+		if lp.SoC.Min != *conf.MinSoC {
+			t.Errorf("expected minSoC %v, got %v", *conf.MinSoC, lp.SoC.Min)
+		}
+		if lp.SoC.Target != *conf.TargetSoC {
+			t.Errorf("expected targetSoC %v, got %v", *conf.TargetSoC, lp.SoC.Target)
+		}
+	}
+
+	oi := newConfig(api.ModePV, 7, 17, 1, 99)
+	od := newConfig(api.ModeOff, 5, 15, 2, 98)
+
+	vehicle := mock.NewMockVehicle(ctrl)
+	vehicle.EXPECT().Title().AnyTimes()
+	vehicle.EXPECT().Capacity().AnyTimes()
+	vehicle.EXPECT().OnIdentified().Return(oi)
+
+	lp := NewLoadPoint(util.NewLogger("foo"))
+
+	// populate channels
+	x, y, z := createChannels(t)
+	attachChannels(lp, x, y, z)
+
+	lp.onDisconnect = od
+	lp.ResetOnDisconnect = true
+
+	// vehicle identified
+	lp.setActiveVehicle(vehicle)
+	assertConfig(lp, oi)
+
+	// vehicle disconnected
+	vehicle.EXPECT().Phases().AnyTimes()
+	lp.evVehicleDisconnectHandler()
+	assertConfig(lp, od)
+}