The service's own field names, said in the card's words

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81
2026-09-03 09:33:03 +02:00
co-authored by Claude Opus 5
parent 0657decbd1
commit 905eb24cc1
4 changed files with 118 additions and 5 deletions
+10
View File
@@ -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",
+10
View File
@@ -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",
+10
View File
@@ -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",
+88 -5
View File
@@ -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] }));
}