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 }}