From ad785ee9f869c650cd21a42025b5a6252429432c Mon Sep 17 00:00:00 2001 From: tajniak81 <13187254+tajniak81@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:43:23 +0200 Subject: [PATCH] The fields the cloud sends, kept all the way to the card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normalizing a provider's charger list threw most of the answer away: firmware, the site id, how the charger is registered, the charge power and the cloud's own OCPP reading all arrived from Anker and none of them got past providerCharger, which carried seven fields and dropped the rest. The information card could not show what it was never handed. It carries them now, and the card lays them out: firmware beside the model, site and site id where the charger lives, "Registered as" for standalone / in a system / bound, and — when the service knows — state, charge power and OCPP status. Charge power is relayed exactly as worded upstream, since the unit is theirs and putting one on it here would be inventing it. Settings' own list gains the firmware in its subtitle. A field no view supplied still leaves no row, so an account whose chargers stand outside a system reads shorter rather than emptier. Co-Authored-By: Claude Opus 5 --- API Server/internal/api/chargerproviders.go | 36 +++++++++++++++++---- 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 | 33 ++++++++++++++++--- Web App/web/src/views/Settings.vue | 2 +- 6 files changed, 89 insertions(+), 12 deletions(-) diff --git a/API Server/internal/api/chargerproviders.go b/API Server/internal/api/chargerproviders.go index 972e4fe..a82cd76 100644 --- a/API Server/internal/api/chargerproviders.go +++ b/API Server/internal/api/chargerproviders.go @@ -69,10 +69,24 @@ type providerCharger struct { Name string `json:"name"` Vendor string `json:"vendor,omitempty"` Model string `json:"model,omitempty"` + Firmware string `json:"firmware,omitempty"` + SiteID string `json:"siteId,omitempty"` SiteName string `json:"siteName,omitempty"` Status string `json:"status,omitempty"` // the service's own word for its state Online *bool `json:"online,omitempty"` + // How the charger is registered on the account — standalone, inside a + // system (site), or merely bound to it. A charger can be several at once, + // and which ones it is decides how much the service says about it. + Sources []string `json:"sources,omitempty"` + + // What it is doing right now, when the service knows: the charge power as + // the service words it (the unit is upstream's, so it is relayed verbatim) + // and the charger's OCPP connector state as the service sees it. + Power string `json:"power,omitempty"` + OcppStatus *int `json:"ocppStatus,omitempty"` + OcppStatusDesc string `json:"ocppStatusDesc,omitempty"` + // LinkedChargerID is set when this one is already in DriverVault, so the UI // never offers to import the same charger twice. LinkedChargerID string `json:"linkedChargerId,omitempty"` @@ -105,12 +119,18 @@ func (ankerChargerSource) gate(ctx context.Context, s *Server, who *callerIdenti func (ankerChargerSource) chargers(raw json.RawMessage) []providerCharger { var env struct { Chargers []struct { - SN string `json:"sn"` - Name string `json:"name"` - Model string `json:"model"` - SiteName string `json:"siteName"` - StatusDesc string `json:"statusDesc"` - Online *bool `json:"online"` + SN string `json:"sn"` + Name string `json:"name"` + Model string `json:"model"` + Firmware string `json:"firmware"` + SiteID string `json:"siteId"` + SiteName string `json:"siteName"` + Sources []string `json:"sources"` + StatusDesc string `json:"statusDesc"` + Online *bool `json:"online"` + Power string `json:"power"` + OcppStatus *int `json:"ocppStatus"` + OcppStatusDesc string `json:"ocppStatusDesc"` } `json:"chargers"` } if json.Unmarshal(raw, &env) != nil { @@ -123,7 +143,9 @@ func (ankerChargerSource) chargers(raw json.RawMessage) []providerCharger { } out = append(out, providerCharger{ ID: c.SN, Name: c.Name, Vendor: "Anker Solix", Model: c.Model, - SiteName: c.SiteName, Status: c.StatusDesc, Online: c.Online, + Firmware: c.Firmware, SiteID: c.SiteID, SiteName: c.SiteName, + Sources: c.Sources, Status: c.StatusDesc, Online: c.Online, + Power: c.Power, OcppStatus: c.OcppStatus, OcppStatusDesc: c.OcppStatusDesc, }) } return out diff --git a/Web App/web/src/i18n/da.json b/Web App/web/src/i18n/da.json index 1de0b5a..44b16a8 100644 --- a/Web App/web/src/i18n/da.json +++ b/Web App/web/src/i18n/da.json @@ -100,12 +100,22 @@ "empty": "Intet importeret endnu — en lader, du importerer, viser alt, den melder, her.", "vendor": "Producent", "model": "Model", + "firmware": "Firmware", "serial": "Serienummer", "site": "Placering", + "siteId": "Placerings-id", + "sources": "Registreret som", + "sourceNames": { + "standalone": "Selvstændig", + "site": "I et system", + "bound": "Knyttet til kontoen" + }, "power": "Effekt", "connector": "Stik", "providerId": "Id hos tjenesten", "state": "Tilstand", + "chargePower": "Ladeeffekt", + "ocpp": "OCPP-status", "added": "Tilføjet", "online": "Online", "offline": "Offline", diff --git a/Web App/web/src/i18n/en.json b/Web App/web/src/i18n/en.json index 9cdf5fb..495976a 100644 --- a/Web App/web/src/i18n/en.json +++ b/Web App/web/src/i18n/en.json @@ -86,12 +86,22 @@ "empty": "Nothing imported yet — a charger you import shows everything it reports here.", "vendor": "Vendor", "model": "Model", + "firmware": "Firmware", "serial": "Serial", "site": "Site", + "siteId": "Site id", + "sources": "Registered as", + "sourceNames": { + "standalone": "Standalone", + "site": "In a system", + "bound": "Bound to the account" + }, "power": "Power", "connector": "Connector", "providerId": "Service id", "state": "State", + "chargePower": "Charge power", + "ocpp": "OCPP status", "added": "Added", "online": "Online", "offline": "Offline", diff --git a/Web App/web/src/i18n/pl.json b/Web App/web/src/i18n/pl.json index 326cc7f..c05d6c0 100644 --- a/Web App/web/src/i18n/pl.json +++ b/Web App/web/src/i18n/pl.json @@ -102,12 +102,22 @@ "empty": "Nic jeszcze nie zaimportowano — zaimportowana ładowarka pokaże tutaj wszystko, co zgłasza.", "vendor": "Producent", "model": "Model", + "firmware": "Oprogramowanie", "serial": "Numer seryjny", "site": "Lokalizacja", + "siteId": "Identyfikator lokalizacji", + "sources": "Zarejestrowana jako", + "sourceNames": { + "standalone": "Samodzielna", + "site": "W systemie", + "bound": "Powiązana z kontem" + }, "power": "Moc", "connector": "Złącze", "providerId": "Identyfikator w usłudze", "state": "Stan", + "chargePower": "Moc ładowania", + "ocpp": "Status OCPP", "added": "Dodano", "online": "Online", "offline": "Offline", diff --git a/Web App/web/src/views/Charging.vue b/Web App/web/src/views/Charging.vue index d40f98e..234a9e5 100644 --- a/Web App/web/src/views/Charging.vue +++ b/Web App/web/src/views/Charging.vue @@ -129,7 +129,7 @@ async function loadChargerProviders() { // held beside the records rather than in them. Asking costs a round trip to // each connected service, so it happens when the home tab is first opened — // the moment the question is being asked — and on demand after that. -const chargerLive = ref({}); // provider charger id → { online, status } +const chargerLive = ref({}); // provider charger id → the service's own record const chargerLiveLoading = ref(false); const chargerLiveLoaded = ref(false); @@ -143,7 +143,7 @@ async function loadChargerLive(force = false) { connected.map(async (p) => { try { const res = await api.listProviderChargers(p.id); - for (const c of res?.chargers || []) live[c.id] = { online: c.online, status: c.status }; + for (const c of res?.chargers || []) live[c.id] = c; } catch { // A service that will not answer leaves its chargers unknown rather // than offline — this page cannot tell those two apart. @@ -163,6 +163,23 @@ function liveFor(c) { return chargerLive.value[c.providerChargerId] || chargerLive.value[c.serial] || null; } +// How the charger is registered on the account, in the service's own terms. +function sourcesLabel(sources) { + if (!sources?.length) return ""; + return sources.map((src) => { + const key = `charging.info.sourceNames.${src}`; + const label = t(key); + return label === key ? src : label; + }).join(" · "); +} + +// The OCPP connector state as the *service* sees it — the cloud's own reading, +// not our CSMS's. It words it when it can and numbers it when it cannot. +function ocppStatusLabel(live) { + if (live?.ocppStatusDesc) return live.ocppStatusDesc; + return live?.ocppStatus == null ? "" : String(live.ocppStatus); +} + // The cloud's own slug for what the charger is doing (charging, standby, …), // translated. The vocabulary is the integration's, so the wording lives with it // in Settings rather than being said twice. @@ -217,15 +234,23 @@ function homeChargerSubtitle(c) { // the card is what is known. The provider's own id for the charger is the serial // for both services we speak to, so it appears only when it is something else. function chargerInfoRows(c) { + const live = liveFor(c) || {}; const rows = [ ["vendor", c.vendor], ["model", c.model], + ["firmware", live.firmware], ["serial", c.serial], - ["site", c.siteName], + ["site", c.siteName || live.siteName], + ["siteId", live.siteId], + ["sources", sourcesLabel(live.sources)], ["power", c.powerKw ? `${c.powerKw} kW` : ""], ["connector", c.connector], + ["state", chargerStateLabel(live.status)], + // Relayed as the service words it — the unit is upstream's, so putting one + // on it here would be inventing it. + ["chargePower", live.power], + ["ocpp", ocppStatusLabel(live)], ["providerId", c.providerChargerId === c.serial ? "" : c.providerChargerId], - ["state", chargerStateLabel(liveFor(c)?.status)], ["added", c.created ? formatDateTime(c.created) : ""], ]; return rows diff --git a/Web App/web/src/views/Settings.vue b/Web App/web/src/views/Settings.vue index 36320a7..6a10b87 100644 --- a/Web App/web/src/views/Settings.vue +++ b/Web App/web/src/views/Settings.vue @@ -1551,7 +1551,7 @@ onBeforeUnmount(() => { {{ c.name || t("settings.integrations.chargerUnnamed") }}

- {{ c.sn }} · {{ c.model }} · {{ c.siteName }} + {{ c.sn }} · {{ c.model }} · {{ c.firmware }} · {{ c.siteName }}