Repository: evcc-io/evcc
Pull request #9461 (2023-08-20 10:26:38)

Schneider: fix ChargedEnergy and init
Fixes https://github.com/evcc-io/evcc/pull/9436#issuecomment-1685236569


Patch:
diff --git a/assets/js/app.js b/assets/js/app.js
index 06de1214..a52ad19d 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -3,7 +3,6 @@ import smoothscroll from "smoothscroll-polyfill";
 import "../css/app.css";
 import { createApp, h } from "vue";
 import { createMetaManager, plugin as metaPlugin } from "vue-meta";
-import Vue3linkify from "vue-3-linkify";
 import App from "./views/App.vue";
 import setupRouter from "./router";
 import setupI18n from "./i18n";
@@ -69,7 +68,6 @@ app.use(setupRouter(i18n));
 app.use(createMetaManager());
 app.use(metaPlugin);
 app.use(featureflags);
-app.use(Vue3linkify);
 window.app = app.mount("#app");
 
 watchThemeChanges();
diff --git a/assets/js/components/Config/FormRow.vue b/assets/js/components/Config/FormRow.vue
index 63a896de..61192e07 100644
--- a/assets/js/components/Config/FormRow.vue
+++ b/assets/js/components/Config/FormRow.vue
@@ -1,3 +1,4 @@
+<!-- eslint-disable vue/no-v-html -->
 <template>
 	<div class="mb-3">
 		<label :for="id">
@@ -11,12 +12,14 @@
 		</div>
 		<div class="form-text">
 			<div v-if="example">{{ $t("config.form.example") }}: {{ example }}</div>
-			<div v-if="help" v-linkify:options="{ target: '_blank' }">{{ help }}</div>
+			<div v-if="help" v-html="helpHtml"></div>
 		</div>
 	</div>
 </template>
 
 <script>
+import linkify from "../../utils/linkify";
+
 export default {
 	name: "FormRow",
 	props: {
@@ -27,5 +30,10 @@ export default {
 		smallValue: Boolean,
 		example: String,
 	},
+	computed: {
+		helpHtml() {
+			return linkify(this.help);
+		},
+	},
 };
 </script>
diff --git a/assets/js/router.js b/assets/js/router.js
index e050d6f1..39f375b4 100644
--- a/assets/js/router.js
+++ b/assets/js/router.js
@@ -1,8 +1,6 @@
 import { createRouter, createWebHashHistory } from "vue-router";
 
 import Main from "./views/Main.vue";
-import ChargingSessions from "./views/ChargingSessions.vue";
-import Config from "./views/Config.vue";
 import { ensureCurrentLocaleMessages } from "./i18n";
 
 export default function setupRouter(i18n) {
@@ -10,10 +8,10 @@ export default function setupRouter(i18n) {
     history: createWebHashHistory(),
     routes: [
       { path: "/", component: Main, props: true },
-      { path: "/config", component: Config, props: true },
+      { path: "/config", component: () => import("./views/Config.vue"), props: true },
       {
         path: "/sessions",
-        component: ChargingSessions,
+        component: () => import("./views/ChargingSessions.vue"),
         props: (route) => {
           const { month, year, loadpoint, vehicle } = route.query;
           return {
diff --git a/assets/js/utils/linkify.js b/assets/js/utils/linkify.js
new file mode 100644
index 00000000..fb3fee3d
--- /dev/null
+++ b/assets/js/utils/linkify.js
@@ -0,0 +1,7 @@
+export default function linkify(text) {
+  const urlRegex = /https?:\/\/[^\s]+/g;
+
+  return text.replace(urlRegex, function (url) {
+    return `<a href="${url}" target="_blank">${url}</a>`;
+  });
+}
diff --git a/charger/schneider.go b/charger/schneider.go
index d76ffab5..4febdabb 100644
--- a/charger/schneider.go
+++ b/charger/schneider.go
@@ -50,7 +50,6 @@ const (
 	schneiderRegChargingTime        = 4007
 	schneiderRegSessionChargingTime = 4009
 	schneiderRegLastStopCause       = 4011
-	schneiderRegSessionEnergy       = 4012
 
 	schneiderDisabled = uint16(0)
 )
@@ -94,8 +93,17 @@ func NewSchneider(uri string, id uint8) (api.Charger, error) {
 		curr: 6,
 	}
 
+	// get initial state from charger
+	b, err := wb.conn.ReadHoldingRegisters(schneiderRegSetPoint, 1)
+	if err != nil {
+		return nil, fmt.Errorf("current limit: %w", err)
+	}
+	if u := encoding.Uint16(b); u > wb.curr {
+		wb.curr = u
+	}
+
 	// heartbeat
-	b, err := wb.conn.ReadHoldingRegisters(schneiderRegLifebit, 1)
+	b, err = wb.conn.ReadHoldingRegisters(schneiderRegLifebit, 1)
 	if err != nil {
 		return nil, fmt.Errorf("heartbeat timeout: %w", err)
 	}
@@ -188,18 +196,6 @@ func (wb *Schneider) TotalEnergy() (float64, error) {
 	return float64(encoding.Uint64LswFirst(b)) / 1e3, nil
 }
 
-var _ api.ChargeRater = (*Schneider)(nil)
-
-// ChargedEnergy implements the api.ChargeRater interface
-func (wb *Schneider) ChargedEnergy() (float64, error) {
-	b, err := wb.conn.ReadHoldingRegisters(schneiderRegSessionEnergy, 2)
-	if err != nil {
-		return 0, err
-	}
-
-	return float64(encoding.Float32LswFirst(b)), nil
-}
-
 var _ api.PhaseCurrents = (*Schneider)(nil)
 
 // Currents implements the api.PhaseCurrents interface
diff --git a/core/loadpoint.go b/core/loadpoint.go
index e7cef270..94c780ce 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -667,6 +667,7 @@ func (lp *Loadpoint) syncCharger() error {
 		if lp.guardGracePeriodElapsed() && lp.phaseSwitchCompleted() && (enabled || lp.connected()) {
 			lp.log.WARN.Printf("charger out of sync: expected %vd, got %vd", status[lp.enabled], status[enabled])
 		}
+		lp.elapseGuard()
 		return nil
 	}
 
@@ -674,6 +675,7 @@ func (lp *Loadpoint) syncCharger() error {
 		if lp.guardGracePeriodElapsed() {
 			lp.log.WARN.Println("charger logic error: disabled but charging")
 		}
+		lp.elapseGuard()
 		return nil
 	}
 
@@ -1003,7 +1005,7 @@ func (lp *Loadpoint) fastCharging() error {
 }
 
 // pvScalePhases switches phases if necessary and returns if switch occurred
-func (lp *Loadpoint) pvScalePhases(availablePower, minCurrent, maxCurrent float64) bool {
+func (lp *Loadpoint) pvScalePhases(sitePower, minCurrent, maxCurrent float64) bool {
 	phases := lp.GetPhases()
 
 	// observed phase state inconsistency
@@ -1020,9 +1022,10 @@ func (lp *Loadpoint) pvScalePhases(availablePower, minCurrent, maxCurrent float6
 
 	var waiting bool
 	activePhases := lp.activePhases()
+	availablePower := lp.chargePower - sitePower
 
 	// scale down phases
-	if targetCurrent := powerToCurrent(availablePower, activePhases); targetCurrent < minCurrent && activePhases > 1 && lp.ConfiguredPhases < 3 {
+	if targetCurrent := powerToCurrent(availablePower, activePhases); sitePower > 0 && targetCurrent < minCurrent && activePhases > 1 && lp.ConfiguredPhases < 3 {
 		lp.log.DEBUG.Printf("available power %.0fW < %.0fW min %dp threshold", availablePower, float64(activePhases)*Voltage*minCurrent, activePhases)
 
 		if lp.phaseTimer.IsZero() {
@@ -1114,8 +1117,9 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter
 
 	// switch phases up/down
 	if _, ok := lp.charger.(api.PhaseSwitcher); ok {
-		availablePower := -sitePower + lp.chargePower
-		_ = lp.pvScalePhases(availablePower, minCurrent, maxCurrent)
+		if lp.pvScalePhases(sitePower, minCurrent, maxCurrent) {
+			return minCurrent
+		}
 	}
 
 	// calculate target charge current from delta power and actual current
diff --git a/package-lock.json b/package-lock.json
index 56f90acd..7a34f8dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,7 +39,6 @@
         "vite-plugin-toml": "^0.5.0",
         "vitest": "^0.33.0",
         "vue": "^3.2.29",
-        "vue-3-linkify": "^1.1.0",
         "vue-eslint-parser": "^9.1.0",
         "vue-hot-reload-api": "^2.3.4",
         "vue-i18n": "^9.2.2",
@@ -3058,11 +3057,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/cssfilter": {
-      "version": "0.0.10",
-      "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
-      "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="
-    },
     "node_modules/cssom": {
       "version": "0.5.0",
       "license": "MIT"
@@ -5075,14 +5069,6 @@
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/linkify-html": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/linkify-html/-/linkify-html-3.0.5.tgz",
-      "integrity": "sha512-3O7HEYjkugX+C/G2C2wyBmIt8Mt0pmeaHNIxRHodCFeQQeSxSoZHR+5hC1pi0WrmoEvfnSemyZyYTM8w3lo9cA==",
-      "peerDependencies": {
-        "linkifyjs": "^3.0.0"
-      }
-    },
     "node_modules/linkify-it": {
       "version": "3.0.3",
       "license": "MIT",
@@ -5090,11 +5076,6 @@
         "uc.micro": "^1.0.1"
       }
     },
-    "node_modules/linkifyjs": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-3.0.5.tgz",
-      "integrity": "sha512-1Y9XQH65eQKA9p2xtk+zxvnTeQBG7rdAXSkUG97DmuI/Xhji9uaUzaWxRj6rf9YC0v8KKHkxav7tnLX82Sz5Fg=="
-    },
     "node_modules/local-pkg": {
       "version": "0.4.3",
       "license": "MIT",
@@ -7091,19 +7072,6 @@
         "@vue/shared": "3.3.4"
       }
     },
-    "node_modules/vue-3-linkify": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/vue-3-linkify/-/vue-3-linkify-1.1.0.tgz",
-      "integrity": "sha512-UXiG4rLS17ZKviAXBlCkuL015BgJsblyyq6CcQfxfiIT/s/RiFhd7FQ/MGAEF/fpwjwn+zllMJPmK/QdsXAb8Q==",
-      "dependencies": {
-        "linkify-html": "^3.0.5",
-        "linkifyjs": "^3.0.5",
-        "xss": "^1.0.11"
-      },
-      "peerDependencies": {
-        "vue": "^3.2.0"
-      }
-    },
     "node_modules/vue-component-type-helpers": {
       "version": "1.6.5",
       "license": "MIT"
@@ -7406,21 +7374,6 @@
       "version": "2.2.0",
       "license": "MIT"
     },
-    "node_modules/xss": {
-      "version": "1.0.14",
-      "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz",
-      "integrity": "sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw==",
-      "dependencies": {
-        "commander": "^2.20.3",
-        "cssfilter": "0.0.10"
-      },
-      "bin": {
-        "xss": "bin/xss"
-      },
-      "engines": {
-        "node": ">= 0.10.0"
-      }
-    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "license": "ISC",
diff --git a/package.json b/package.json
index 0ce4ca5a..002e07e2 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,6 @@
     "vite-plugin-toml": "^0.5.0",
     "vitest": "^0.33.0",
     "vue": "^3.2.29",
-    "vue-3-linkify": "^1.1.0",
     "vue-eslint-parser": "^9.1.0",
     "vue-hot-reload-api": "^2.3.4",
     "vue-i18n": "^9.2.2",
diff --git a/vehicle/tesla.go b/vehicle/tesla.go
index acec7991..f7b952c3 100644
--- a/vehicle/tesla.go
+++ b/vehicle/tesla.go
@@ -212,7 +212,11 @@ var _ api.VehicleChargeController = (*Tesla)(nil)
 
 // StartCharge implements the api.VehicleChargeController interface
 func (v *Tesla) StartCharge() error {
-	return v.apiError(v.vehicle.StartCharging())
+	err := v.apiError(v.vehicle.StartCharging())
+	if err != nil && err.Error() == "is_charging" {
+		return nil
+	}
+	return err
 }
 
 // StopCharge implements the api.VehicleChargeController interface


Test patch:
diff --git a/assets/js/utils/linkify.test.js b/assets/js/utils/linkify.test.js
new file mode 100644
index 00000000..0dd0b406
--- /dev/null
+++ b/assets/js/utils/linkify.test.js
@@ -0,0 +1,25 @@
+import linkify from "./linkify";
+import { describe, expect, test } from "vitest";
+
+describe("linkify", () => {
+  test("should wrap links", () => {
+    expect(linkify("https://example.com")).eq(
+      `<a href="https://example.com" target="_blank">https://example.com</a>`
+    );
+  });
+  test("with surrounding text", () => {
+    expect(linkify("hello http://foo.bar/ world")).eq(
+      `hello <a href="http://foo.bar/" target="_blank">http://foo.bar/</a> world`
+    );
+  });
+  test("with query and hash", () => {
+    expect(linkify("a http://b.c/?d=e#f g")).eq(
+      `a <a href="http://b.c/?d=e#f" target="_blank">http://b.c/?d=e#f</a> g`
+    );
+  });
+  test("with multiple links", () => {
+    expect(linkify("hello http://foo.bar/ world https://bar.baz/ tadda!")).eq(
+      `hello <a href="http://foo.bar/" target="_blank">http://foo.bar/</a> world <a href="https://bar.baz/" target="_blank">https://bar.baz/</a> tadda!`
+    );
+  });
+});
diff --git a/core/loadpoint_phases_test.go b/core/loadpoint_phases_test.go
index 857dce20..aec7a414 100644
--- a/core/loadpoint_phases_test.go
+++ b/core/loadpoint_phases_test.go
@@ -22,48 +22,46 @@ type testCase struct {
 	scale string
 }
 
-var (
-	phaseTests = []testCase{
-		// 1p
-		{1, 1, 0, 0, 1, 1, ""},
-		{1, 1, 0, 1, 1, 1, ""},
-		{1, 1, 1, 0, 1, 1, ""},
-		{1, 1, 2, 0, 1, 1, ""},
-		{1, 1, 3, 0, 1, 1, ""},
-		// 3p
-		{3, 3, 0, 0, unknownPhases, 3, ""},
-		{3, 3, 0, 1, 1, 1, ""},
-		{3, 3, 0, 2, 2, 2, ""},
-		{3, 3, 0, 3, 3, 3, ""},
-		{3, 3, 1, 0, 1, 1, ""},
-		{3, 3, 2, 0, 2, 2, ""},
-		{3, 3, 3, 0, 3, 3, ""},
-		// 1p3p initial
-		{0, 0, 0, 0, unknownPhases, 3, "du"},
-		{0, 0, 0, 1, 1, 3, "u"},
-		{0, 0, 0, 2, 2, 3, "du"},
-		{0, 0, 0, 3, 3, 3, "du"},
-		{0, 0, 1, 0, 1, 1, ""},
-		{0, 0, 2, 0, 2, 2, "du"},
-		{0, 0, 3, 0, 3, 3, "du"},
-		// 1p3p, 1 currently active
-		{0, 1, 0, 0, 1, 3, "u"},
-		{0, 1, 0, 1, 1, 3, "u"},
-		// {0, 1, 0, 2, 2,2,"u"}, // 2p active > 1p configured must not happen
-		// {0, 1, 0, 3, 3,3,"u"}, // 3p active > 1p configured must not happen
-		{0, 1, 1, 0, 1, 1, ""},
-		{0, 1, 2, 0, 1, 2, "u"},
-		{0, 1, 3, 0, 1, 3, "u"},
-		// 1p3p, 3 currently active
-		{0, 3, 0, 0, unknownPhases, 3, "d"},
-		{0, 3, 0, 1, 1, 1, ""},
-		{0, 3, 0, 2, 2, 2, "d"},
-		{0, 3, 0, 3, 3, 3, "d"},
-		{0, 3, 1, 0, 1, 1, ""},
-		{0, 3, 2, 0, 2, 2, "d"},
-		{0, 3, 3, 0, 3, 3, "d"},
-	}
-)
+var phaseTests = []testCase{
+	// 1p
+	{1, 1, 0, 0, 1, 1, ""},
+	{1, 1, 0, 1, 1, 1, ""},
+	{1, 1, 1, 0, 1, 1, ""},
+	{1, 1, 2, 0, 1, 1, ""},
+	{1, 1, 3, 0, 1, 1, ""},
+	// 3p
+	{3, 3, 0, 0, unknownPhases, 3, ""},
+	{3, 3, 0, 1, 1, 1, ""},
+	{3, 3, 0, 2, 2, 2, ""},
+	{3, 3, 0, 3, 3, 3, ""},
+	{3, 3, 1, 0, 1, 1, ""},
+	{3, 3, 2, 0, 2, 2, ""},
+	{3, 3, 3, 0, 3, 3, ""},
+	// 1p3p initial
+	{0, 0, 0, 0, unknownPhases, 3, "du"},
+	{0, 0, 0, 1, 1, 3, "u"},
+	{0, 0, 0, 2, 2, 3, "du"},
+	{0, 0, 0, 3, 3, 3, "du"},
+	{0, 0, 1, 0, 1, 1, ""},
+	{0, 0, 2, 0, 2, 2, "du"},
+	{0, 0, 3, 0, 3, 3, "du"},
+	// 1p3p, 1 currently active
+	{0, 1, 0, 0, 1, 3, "u"},
+	{0, 1, 0, 1, 1, 3, "u"},
+	// {0, 1, 0, 2, 2,2,"u"}, // 2p active > 1p configured must not happen
+	// {0, 1, 0, 3, 3,3,"u"}, // 3p active > 1p configured must not happen
+	{0, 1, 1, 0, 1, 1, ""},
+	{0, 1, 2, 0, 1, 2, "u"},
+	{0, 1, 3, 0, 1, 3, "u"},
+	// 1p3p, 3 currently active
+	{0, 3, 0, 0, unknownPhases, 3, "d"},
+	{0, 3, 0, 1, 1, 1, ""},
+	{0, 3, 0, 2, 2, 2, "d"},
+	{0, 3, 0, 3, 3, 3, "d"},
+	{0, 3, 1, 0, 1, 1, ""},
+	{0, 3, 2, 0, 2, 2, "d"},
+	{0, 3, 3, 0, 3, 3, "d"},
+}
 
 func TestMaxActivePhases(t *testing.T) {
 	ctrl := gomock.NewController(t)
@@ -125,7 +123,7 @@ func TestMaxActivePhases(t *testing.T) {
 	}
 }
 
-func testScale(t *testing.T, lp *Loadpoint, power float64, direction string, tc testCase) {
+func testScale(t *testing.T, lp *Loadpoint, sitePower float64, direction string, tc testCase) {
 	act := lp.activePhases()
 	max := lp.maxActivePhases()
 
@@ -136,8 +134,8 @@ func testScale(t *testing.T, lp *Loadpoint, power float64, direction string, tc
 	if testDirection == "u" && strings.Contains(testExpectation, testDirection) {
 		// scale-up should only execute when the 1p max current is exceeded
 		// we're testing this here and remove the upscale expectation for the following test below 1p max current
-		if maxAmp := power / Voltage; maxAmp < maxA {
-			if scaled := lp.pvScalePhases(power, minA, maxAmp-0.0001); !scaled {
+		if maxAmp := -sitePower / Voltage; maxAmp < maxA {
+			if scaled := lp.pvScalePhases(sitePower, minA, maxAmp-0.0001); !scaled {
 				t.Errorf("%v act=%d max=%d missing scale %s at reduced max current %.1fA", tc, act, max, direction, maxAmp)
 			}
 
@@ -146,7 +144,7 @@ func testScale(t *testing.T, lp *Loadpoint, power float64, direction string, tc
 		}
 	}
 
-	scaled := lp.pvScalePhases(power, minA, maxA)
+	scaled := lp.pvScalePhases(sitePower, minA, maxA)
 
 	if strings.Contains(testExpectation, testDirection) {
 		if !scaled {
@@ -231,7 +229,7 @@ func TestPvScalePhases(t *testing.T) {
 		// scaling
 		if phaseCharger != nil {
 			// scale down
-			min1p := 1 * minA * Voltage
+			min1p := 0.1
 			lp.phaseTimer = time.Time{}
 
 			plainCharger.EXPECT().Enable(false).Return(nil).MaxTimes(1)
@@ -251,7 +249,7 @@ func TestPvScalePhases(t *testing.T) {
 			plainCharger.EXPECT().Enable(false).Return(nil).MaxTimes(1)
 			phaseCharger.EXPECT().Phases1p3p(3).Return(nil).MaxTimes(1)
 
-			testScale(t, lp, min3p, "up", tc)
+			testScale(t, lp, -min3p, "up", tc)
 			ctrl.Finish()
 		}
 	}
@@ -273,68 +271,68 @@ func TestPvScalePhasesTimer(t *testing.T) {
 	tc := []struct {
 		desc                   string
 		phases, measuredPhases int
-		availablePower         float64
+		sitePower              float64
 		toPhases               int
 		res                    bool
 		prepare                func(lp *Loadpoint)
 	}{
 		// switch up from 1p/1p configured/active
 		{"1/1->3, not enough power", 1, 1, 0, 1, false, nil},
-		{"1/1->3, kickoff", 1, 1, 3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
+		{"1/1->3, kickoff", 1, 1, -3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
 			lp.phaseTimer = time.Time{}
 		}},
-		{"1/1->3, timer running", 1, 1, 3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
+		{"1/1->3, timer running", 1, 1, -3 * Voltage * minA, 1, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now()
 		}},
-		{"1/1->3, timer elapsed", 1, 1, 3 * Voltage * minA, 3, true, func(lp *Loadpoint) {
+		{"1/1->3, timer elapsed", 1, 1, -3 * Voltage * minA, 3, true, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now().Add(-dt)
 		}},
 
 		// omit to switch up (again) from 3p/1p configured/active
 		{"3/1->3, not enough power", 3, 1, 0, 3, false, nil},
-		{"3/1->3, kickoff", 3, 1, 3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->3, kickoff", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = time.Time{}
 		}},
-		{"3/1->3, timer running", 3, 1, 3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->3, timer running", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now()
 		}},
-		{"3/1->3, timer elapsed", 3, 1, 3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->3, timer elapsed", 3, 1, -3 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now().Add(-dt)
 		}},
 
 		// omit to switch down from 3p/1p configured/active
 		{"3/1->1, not enough power", 3, 1, 0, 3, false, nil},
-		{"3/1->1, kickoff", 3, 1, 1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->1, kickoff", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = time.Time{}
 		}},
-		{"3/1->1, timer running", 3, 1, 1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->1, timer running", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now()
 		}},
-		{"3/1->1, timer elapsed", 3, 1, 1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
+		{"3/1->1, timer elapsed", 3, 1, -1 * Voltage * minA, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now().Add(-dt)
 		}},
 
 		// switch down from 3p/3p configured/active
-		{"3/3->1, enough power", 3, 3, 1 * Voltage * maxA, 3, false, nil},
-		{"3/3->1, kickoff", 3, 3, 1 * Voltage * maxA, 3, false, func(lp *Loadpoint) {
+		{"3/3->1, enough power", 3, 3, 0.1, 3, false, nil},
+		{"3/3->1, kickoff", 3, 3, 0.1, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = time.Time{}
 		}},
-		{"3/3->1, timer running", 3, 3, 1 * Voltage * maxA, 3, false, func(lp *Loadpoint) {
+		{"3/3->1, timer running", 3, 3, 0.1, 3, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now()
 		}},
-		{"3/3->1, timer elapsed", 3, 3, 1 * Voltage * maxA, 1, true, func(lp *Loadpoint) {
+		{"3/3->1, timer elapsed", 3, 3, 0.1, 1, true, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now().Add(-dt)
 		}},
 
-		// error states from 1p/3p misconfig - no correction for time being (stay at 1p)
-		{"1/3->1, enough power", 1, 3, 1 * Voltage * maxA, 1, false, nil},
-		{"1/3->1, kickoff, correct phase setting", 1, 3, 1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
+		// error states from 1p/3p misconfiguration - no correction for time being (stay at 1p)
+		{"1/3->1, enough power", 1, 3, -1 * Voltage * maxA, 1, false, nil},
+		{"1/3->1, kickoff, correct phase setting", 1, 3, -1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
 			lp.phaseTimer = time.Time{}
 		}},
-		{"1/3->1, timer running, correct phase setting", 1, 3, 1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
+		{"1/3->1, timer running, correct phase setting", 1, 3, -1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now()
 		}},
-		{"1/3->1, switch not executed", 1, 3, 1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
+		{"1/3->1, switch not executed", 1, 3, -1 * Voltage * maxA, 1, false, func(lp *Loadpoint) {
 			lp.phaseTimer = lp.clock.Now().Add(-dt)
 		}},
 	}
@@ -368,7 +366,7 @@ func TestPvScalePhasesTimer(t *testing.T) {
 			charger.MockPhaseSwitcher.EXPECT().Phases1p3p(tc.toPhases).Return(nil)
 		}
 
-		res := lp.pvScalePhases(tc.availablePower, minA, maxA)
+		res := lp.pvScalePhases(tc.sitePower, minA, maxA)
 
 		switch {
 		case tc.res != res: