A raw key, a bare number, and two columns of dashes

Switch the control mode to the Anker cloud and the charging cards still read as
though they were built for Modbus. Three separate causes, and none of them was
the shared layout — the cards already branch on the control mode in sixteen
places, which is why the address fields, the reset button and the skip-delay
button each appear only under the transport that has them.

The loudest was a missing string. The cloud transport arrived with three keys the
templates call and the language files never got: cloudNote, cloudLocalFound and
skipDelay. A key missing from English returns itself, deliberately, so that a gap
shows up in the UI instead of rendering as a blank — and it did, as
"charging.control.cloudNote" sitting in the connection card where a sentence
belongs. All three are added, in both surfaces and all three languages, so the
Phone App is not left showing the same raw key.

The second was an enum wearing one name over two transports. Modbus register
20087 reports the phase mode as 1 single-phase or 3 three-phase; the cloud's own
field reports 0 automatic or 1 single-phase. Only phaseMode1 and phaseMode3 had
labels, so a cloud charger sitting on automatic rendered "Running on 0" — the
enum fell back to printing the number, which is the right fallback and the wrong
answer. phaseMode0 is added. Worth naming the shape of this one: it is the same
collision that made the cloud's d9 field wrong when it was called chargingMode
after the register at 20088, and a third transport reporting a 3 that means
something else would break it again.

The third was honest but useless. Reactive and apparent power are registers of
their own and the cloud has no message carrying either, so over that transport
those two columns could only ever be three dashes each. They now appear when the
charger actually reports them, which also tidies up a Modbus charger whose
firmware leaves them out.

The layout stays shared. A value both transports report should keep one name and
one row, and what each transport can be told still branches where it has to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-09-02 20:22:50 +02:00
co-authored by Claude Opus 5
parent 90558d60b2
commit 1dc20461de
8 changed files with 49 additions and 8 deletions
+4
View File
@@ -159,6 +159,9 @@
"forgetAddress": "Glem",
"forgetAddressConfirm": "Glem laderens lokale adresse? Styring over det lokale netværk stopper, indtil du indtaster den igen.",
"boost": "Boost denne session",
"skipDelay": "Spring startforsinkelsen over",
"cloudNote": "Der er intet at sætte op her: Anker-kontoen er legitimationen, og den indtastes i Indstillinger. Kommandoer sendes over den forbindelse, laderen i forvejen holder åben til Anker.",
"cloudLocalFound": "Denne lader oplyser sin egen lokale adresse som {address}. Skift kontroltilstand til Modbus TCP for at bruge den.",
"modbusHint": "Indtast laderens adresse på dette netværk og gem den. Laderen skal være tændt og på samme netværk som DriverVault."
},
"modbus": {
@@ -207,6 +210,7 @@
"mqttLink": "MQTT",
"alarmWord": "Ord {n}",
"alarmsHint": "Laderen melder en alarm. Anker offentliggør ikke, hvad de enkelte bit betyder, så ordene vises, som de kommer.",
"phaseMode0": "Automatisk",
"phaseMode1": "Enfaset",
"phaseMode3": "Trefaset",
"phaseSet0": "Automatisk",
+4
View File
@@ -159,6 +159,9 @@
"forgetAddress": "Forget",
"forgetAddressConfirm": "Forget this charger's local address? Control over the local network stops until you enter it again.",
"boost": "Boost this session",
"skipDelay": "Skip the start delay",
"cloudNote": "Nothing to set up here: the Anker account is the credential, and it is entered in Settings. Commands travel over the connection the charger already holds open to Anker.",
"cloudLocalFound": "This charger reports its own local address as {address}. Switch the control mode to Modbus TCP to use it.",
"modbusHint": "Enter the charger's address on this network and save it. The charger must be powered on and on the same network as DriverVault."
},
"modbus": {
@@ -207,6 +210,7 @@
"mqttLink": "MQTT",
"alarmWord": "Word {n}",
"alarmsHint": "The charger reports an alarm. Anker does not publish what the individual bits mean, so the words are shown as they arrive.",
"phaseMode0": "Automatic",
"phaseMode1": "Single-phase",
"phaseMode3": "Three-phase",
"phaseSet0": "Automatic",
+4
View File
@@ -161,6 +161,9 @@
"forgetAddress": "Zapomnij",
"forgetAddressConfirm": "Zapomnieć lokalny adres tej ładowarki? Sterowanie przez sieć lokalną przestanie działać, dopóki nie wpiszesz go ponownie.",
"boost": "Boost w tej sesji",
"skipDelay": "Pomiń opóźnienie startu",
"cloudNote": "Nie ma tu nic do skonfigurowania: poświadczeniem jest konto Anker, które podajesz w Ustawieniach. Polecenia idą połączeniem, które ładowarka i tak utrzymuje z Anker.",
"cloudLocalFound": "Ta ładowarka podaje swój lokalny adres: {address}. Przełącz tryb sterowania na Modbus TCP, aby go użyć.",
"modbusHint": "Podaj adres ładowarki w tej sieci i zapisz go. Ładowarka musi być włączona i w tej samej sieci co DriverVault."
},
"modbus": {
@@ -209,6 +212,7 @@
"mqttLink": "MQTT",
"alarmWord": "Słowo {n}",
"alarmsHint": "Ładowarka zgłasza alarm. Anker nie publikuje znaczenia poszczególnych bitów, więc słowa pokazane są tak, jak przychodzą.",
"phaseMode0": "Automatycznie",
"phaseMode1": "Jednofazowo",
"phaseMode3": "Trójfazowo",
"phaseSet0": "Automatycznie",
+12 -4
View File
@@ -1900,8 +1900,8 @@ class _HomeTabState extends State<_HomeTab> {
_th(context, t("charging.modbus.voltage")),
_th(context, t("charging.modbus.current")),
_th(context, t("charging.modbus.activePower")),
_th(context, t("charging.modbus.reactivePower")),
_th(context, t("charging.modbus.apparentPower")),
if (_phasesHaveVA(s)) _th(context, t("charging.modbus.reactivePower")),
if (_phasesHaveVA(s)) _th(context, t("charging.modbus.apparentPower")),
]),
for (final row in phases)
TableRow(children: [
@@ -2095,6 +2095,13 @@ class _HomeTabState extends State<_HomeTab> {
]);
}
/// Reactive and apparent power are registers of their own, and the cloud has
/// no message carrying either — so on that transport the two columns could
/// only ever be three dashes each. They appear when the charger actually
/// reports them, which also covers a Modbus charger that leaves them out.
bool _phasesHaveVA(ChargerStatus s) => [1, 2, 3]
.any((n) => s.raw["reactiveL$n"] != null || s.raw["apparentL$n"] != null);
/// The per-phase matrix, or nothing at all when the charger reported none of
/// it. A cell with no reading is a dash, so the columns still line up.
List<List<String>> _phaseRows(ChargerStatus s) {
@@ -2102,6 +2109,7 @@ class _HomeTabState extends State<_HomeTab> {
v == null ? "" : "${v.toStringAsFixed(digits)} $unit";
final any = ["voltageL1", "currentL1", "powerL1"].any((k) => s.raw[k] != null);
if (!any) return const [];
final va = _phasesHaveVA(s);
return [
for (final n in [1, 2, 3])
[
@@ -2109,8 +2117,8 @@ class _HomeTabState extends State<_HomeTab> {
cell(s.number("voltageL$n"), 1, "V"),
cell(s.number("currentL$n"), 2, "A"),
cell(s.number("powerL$n"), 0, "W"),
cell(s.number("reactiveL$n"), 0, "var"),
cell(s.number("apparentL$n"), 0, "VA"),
if (va) cell(s.number("reactiveL$n"), 0, "var"),
if (va) cell(s.number("apparentL$n"), 0, "VA"),
],
];
}
+4
View File
@@ -106,6 +106,9 @@
"forgetAddress": "Glem",
"forgetAddressConfirm": "Glem laderens lokale adresse? Styring over det lokale netværk stopper, indtil du indtaster den igen.",
"boost": "Boost denne session",
"skipDelay": "Spring startforsinkelsen over",
"cloudNote": "Der er intet at sætte op her: Anker-kontoen er legitimationen, og den indtastes i Indstillinger. Kommandoer sendes over den forbindelse, laderen i forvejen holder åben til Anker.",
"cloudLocalFound": "Denne lader oplyser sin egen lokale adresse som {address}. Skift kontroltilstand til Modbus TCP for at bruge den.",
"modbusHint": "Indtast laderens adresse på dette netværk og gem den. Laderen skal være tændt og på samme netværk som DriverVault."
},
"cards": {
@@ -168,6 +171,7 @@
"mqttLink": "MQTT",
"alarmWord": "Ord {n}",
"alarmsHint": "Laderen melder en alarm. Anker offentliggør ikke, hvad de enkelte bit betyder, så ordene vises, som de kommer.",
"phaseMode0": "Automatisk",
"phaseMode1": "Enfaset",
"phaseMode3": "Trefaset",
"phaseSet0": "Automatisk",
+4
View File
@@ -92,6 +92,9 @@
"forgetAddress": "Forget",
"forgetAddressConfirm": "Forget this charger's local address? Control over the local network stops until you enter it again.",
"boost": "Boost this session",
"skipDelay": "Skip the start delay",
"cloudNote": "Nothing to set up here: the Anker account is the credential, and it is entered in Settings. Commands travel over the connection the charger already holds open to Anker.",
"cloudLocalFound": "This charger reports its own local address as {address}. Switch the control mode to Modbus TCP to use it.",
"modbusHint": "Enter the charger's address on this network and save it. The charger must be powered on and on the same network as DriverVault."
},
"cards": {
@@ -154,6 +157,7 @@
"mqttLink": "MQTT",
"alarmWord": "Word {n}",
"alarmsHint": "The charger reports an alarm. Anker does not publish what the individual bits mean, so the words are shown as they arrive.",
"phaseMode0": "Automatic",
"phaseMode1": "Single-phase",
"phaseMode3": "Three-phase",
"phaseSet0": "Automatic",
+4
View File
@@ -108,6 +108,9 @@
"forgetAddress": "Zapomnij",
"forgetAddressConfirm": "Zapomnieć lokalny adres tej ładowarki? Sterowanie przez sieć lokalną przestanie działać, dopóki nie wpiszesz go ponownie.",
"boost": "Boost w tej sesji",
"skipDelay": "Pomiń opóźnienie startu",
"cloudNote": "Nie ma tu nic do skonfigurowania: poświadczeniem jest konto Anker, które podajesz w Ustawieniach. Polecenia idą połączeniem, które ładowarka i tak utrzymuje z Anker.",
"cloudLocalFound": "Ta ładowarka podaje swój lokalny adres: {address}. Przełącz tryb sterowania na Modbus TCP, aby go użyć.",
"modbusHint": "Podaj adres ładowarki w tej sieci i zapisz go. Ładowarka musi być włączona i w tej samej sieci co DriverVault."
},
"cards": {
@@ -170,6 +173,7 @@
"mqttLink": "MQTT",
"alarmWord": "Słowo {n}",
"alarmsHint": "Ładowarka zgłasza alarm. Anker nie publikuje znaczenia poszczególnych bitów, więc słowa pokazane są tak, jak przychodzą.",
"phaseMode0": "Automatycznie",
"phaseMode1": "Jednofazowo",
"phaseMode3": "Trójfazowo",
"phaseSet0": "Automatycznie",
+13 -4
View File
@@ -536,6 +536,15 @@ const devicePhases = computed(() => {
}));
});
// Reactive and apparent power are registers of their own, and the cloud has no
// message carrying either — so on that transport the two columns could only ever
// be three dashes each. They appear when the charger actually reports them,
// which also covers a Modbus charger whose firmware leaves them out.
const devicePhasesHaveVA = computed(() => {
const s = dev.value;
return [1, 2, 3].some((n) => isSet(s[`reactiveL${n}`]) || isSet(s[`apparentL${n}`]));
});
// Line-to-line voltages only mean anything on a three-phase supply, so they are
// shown when the charger reports one rather than as three more zeroes.
const deviceLineVoltages = computed(() => {
@@ -1560,8 +1569,8 @@ onMounted(async () => {
<th class="py-1 text-right font-medium">{{ t("charging.modbus.voltage") }}</th>
<th class="py-1 text-right font-medium">{{ t("charging.modbus.current") }}</th>
<th class="py-1 text-right font-medium">{{ t("charging.modbus.activePower") }}</th>
<th class="py-1 text-right font-medium">{{ t("charging.modbus.reactivePower") }}</th>
<th class="py-1 text-right font-medium">{{ t("charging.modbus.apparentPower") }}</th>
<th v-if="devicePhasesHaveVA" class="py-1 text-right font-medium">{{ t("charging.modbus.reactivePower") }}</th>
<th v-if="devicePhasesHaveVA" class="py-1 text-right font-medium">{{ t("charging.modbus.apparentPower") }}</th>
</tr>
</thead>
<tbody class="data">
@@ -1570,8 +1579,8 @@ onMounted(async () => {
<td class="py-1 text-right text-strong">{{ p.volts }}</td>
<td class="py-1 text-right text-strong">{{ p.amps }}</td>
<td class="py-1 text-right text-strong">{{ p.watts }}</td>
<td class="py-1 text-right text-strong">{{ p.reactive }}</td>
<td class="py-1 text-right text-strong">{{ p.apparent }}</td>
<td v-if="devicePhasesHaveVA" class="py-1 text-right text-strong">{{ p.reactive }}</td>
<td v-if="devicePhasesHaveVA" class="py-1 text-right text-strong">{{ p.apparent }}</td>
</tr>
</tbody>
</table>