From 905eb24cc1b37aa6ef44648d686f4d516766f382 Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:33:03 +0200 Subject: [PATCH] The service's own field names, said in the card's words Co-Authored-By: Claude Opus 5 --- Web App/web/src/i18n/da.json | 10 ++++ Web App/web/src/i18n/en.json | 10 ++++ Web App/web/src/i18n/pl.json | 10 ++++ Web App/web/src/views/Charging.vue | 93 ++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/Web App/web/src/i18n/da.json b/Web App/web/src/i18n/da.json index 770082c..61ca94e 100644 --- a/Web App/web/src/i18n/da.json +++ b/Web App/web/src/i18n/da.json @@ -273,6 +273,16 @@ "relatedBy": "Kan nås via", "timeZone": "Tidszone", "linked": "Tilknyttet", + "nickname": "Kaldenavn", + "productCode": "Produktkode", + "deviceType": "Enhedstype", + "charging": "Oplader", + "statusCode": "Statuskode", + "ocppLink": "OCPP-forbindelse", + "wifiOnline": "Wi-Fi forbundet", + "bleId": "Bluetooth-id", + "blePassword": "Bluetooth-parringskode", + "ownerId": "Ejer-id", "groups": { "device": "Enhed", "status": "Status", diff --git a/Web App/web/src/i18n/en.json b/Web App/web/src/i18n/en.json index 83bb68f..8cf4e3b 100644 --- a/Web App/web/src/i18n/en.json +++ b/Web App/web/src/i18n/en.json @@ -259,6 +259,16 @@ "relatedBy": "Reachable by", "timeZone": "Time zone", "linked": "Linked", + "nickname": "Nickname", + "productCode": "Product code", + "deviceType": "Device type", + "charging": "Charging", + "statusCode": "Status code", + "ocppLink": "OCPP connection", + "wifiOnline": "Wi-Fi connected", + "bleId": "Bluetooth id", + "blePassword": "Bluetooth pairing code", + "ownerId": "Owner id", "groups": { "device": "Device", "status": "Status", diff --git a/Web App/web/src/i18n/pl.json b/Web App/web/src/i18n/pl.json index 2f25551..fe0fdc1 100644 --- a/Web App/web/src/i18n/pl.json +++ b/Web App/web/src/i18n/pl.json @@ -275,6 +275,16 @@ "relatedBy": "Dostępna przez", "timeZone": "Strefa czasowa", "linked": "Powiązano", + "nickname": "Nazwa własna", + "productCode": "Kod produktu", + "deviceType": "Typ urządzenia", + "charging": "Ładowanie", + "statusCode": "Kod statusu", + "ocppLink": "Połączenie OCPP", + "wifiOnline": "Wi-Fi połączone", + "bleId": "Identyfikator Bluetooth", + "blePassword": "Kod parowania Bluetooth", + "ownerId": "Identyfikator właściciela", "groups": { "device": "Urządzenie", "status": "Status", diff --git a/Web App/web/src/views/Charging.vue b/Web App/web/src/views/Charging.vue index 5ebf96c..d653aea 100644 --- a/Web App/web/src/views/Charging.vue +++ b/Web App/web/src/views/Charging.vue @@ -880,6 +880,82 @@ function homeChargerSubtitle(c) { return [c.serial, c.model, c.siteName].filter(Boolean).join(" · "); } +// The service's own field names, given the names the rest of this card uses. +// Anker documents none of these, so only the fields whose meaning is plain from +// the value are named here — each says which group it belongs in and what to +// call it. A field whose meaning would be a guess stays out and keeps its own +// key in the box below, where the key is the only honest label it has. +const NAMED_ATTRS = { + alias_name: { group: "device", label: "nickname" }, + product_code: { group: "device", label: "productCode" }, + ms_device_type: { group: "device", label: "deviceType" }, + charge: { group: "status", label: "charging", bool: true }, + chargerStatus: { group: "status", label: "statusCode" }, + ocpp_connect_status: { group: "status", label: "ocppLink" }, + // The box on the wall is on two networks, and the service says more about + // both than the typed fields carry. + wifi_online: { group: "network", label: "wifiOnline", bool: true }, + bt_ble_id: { group: "network", label: "bleId", sameAs: "bleMac" }, + blue_password: { group: "network", label: "blePassword" }, + owner_user_id: { group: "account", label: "ownerId" }, +}; + +// Fields that repeat, in the service's own words, a row the card already draws: +// the same serial, the same firmware, the same Wi-Fi, the same picture. Shown +// twice they make the card longer without making it say more, so they are +// dropped instead. +const ECHOED_ATTRS = new Set([ + "deviceName", + "device_name", + "deviceSn", + "device_sn", + "device_sw_version", + "img_url", + "link_time", + "rssi", + "time_zone", + "wifi_mac", + "wifi_name", + "bt_ble_mac", +]); + +// relate_type arrives indexed — relate_type[0], relate_type[1] — and is the +// list the "Reachable by" row is built from. +function isEchoedAttr(key) { + return ECHOED_ATTRS.has(key) || key.startsWith("relate_type["); +} + +// Two values that are the same fact written two ways: 7C:E9:13:73:C2:38 is the +// address 7CE91373C238 with colons in it. +function sameAttrValue(a, b) { + const norm = (v) => String(v ?? "").toLowerCase().replace(/[^a-z0-9]/g, ""); + return norm(a) !== "" && norm(a) === norm(b); +} + +// A flag the service sends as true/false or 1/0, read out in words. Anything +// else is relayed as it arrived rather than forced into a yes. +function attrBoolLabel(v) { + const s = String(v).toLowerCase(); + if (s === "true" || s === "1") return t("common.yes"); + if (s === "false" || s === "0") return t("common.no"); + return v; +} + +// The named fields that belong in one group, for a charger whose service sent +// them. Unlike the rows above, a field missing here draws nothing: these are one +// service's fields, and a row of dashes on a charger from another service would +// say a field is absent when it was never a field at all. +function namedAttrRows(groupId, attrs, live) { + return Object.entries(NAMED_ATTRS) + .filter(([key, spec]) => spec.group === groupId && isSet(attrs[key]) && attrs[key] !== "") + .filter(([key, spec]) => !(spec.sameAs && sameAttrValue(attrs[key], live[spec.sameAs]))) + .map(([key, spec]) => ({ + key, + label: t(`charging.info.${spec.label}`), + value: spec.bool ? attrBoolLabel(attrs[key]) : attrs[key], + })); +} + // Everything the card can say about one charger, in groups: what the box is, // what it is doing, how it is connected, and how it sits on the account. Four // short lists under headings, one box each the way the readings card draws its @@ -925,14 +1001,20 @@ function chargerInfoGroups(c) { ["added", c.created ? formatDateTime(c.created) : ""], ]], ]; + const attrs = live.attrs || {}; return groups.map(([id, rows]) => ({ id, label: t(`charging.info.groups.${id}`), - rows: rows.map(([key, value]) => ({ - key, - label: t(`charging.info.${key}`), - value: value === "" || value == null ? "—" : value, - })), + rows: [ + ...rows.map(([key, value]) => ({ + key, + label: t(`charging.info.${key}`), + value: value === "" || value == null ? "—" : value, + })), + // The fields the service sent under its own names that this group has a + // name for, after the ones DriverVault stores itself. + ...namedAttrRows(id, attrs, live), + ], })); } @@ -995,6 +1077,7 @@ const chargerDetailViews = computed(() => { function chargerAttrRows(c) { const attrs = liveFor(c)?.attrs || {}; return Object.keys(attrs) + .filter((key) => !isEchoedAttr(key) && !NAMED_ATTRS[key]) .sort() .map((key) => ({ key, value: attrs[key] })); }