Anker health: count the chargers the panel lists, not the ones one endpoint admits to
The probe still asked get_user_bind_and_not_in_station_evchargers and read its userBindEvChargersCount, so it reported "0 EV charger(s) bound to account" for an account whose two chargers the panel was listing directly underneath — the same blind spot the capability was just moved off, left behind in the health check. It now takes the same inventory the chargers capability returns and counts that. Authenticated with nothing on the account is degraded rather than ok, following Greencell's rule: the half we address answers, and the empty half is the account or the country that picks the regional server, so the message says so instead of reporting a healthy connection to nothing. A count reached with some view missing says how many views stayed silent, because the number is then a floor rather than a total. The web panel colours degraded amber, as it already did for Greencell. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e138fad3f4
commit
a809980d8b
@@ -238,17 +238,33 @@ func (p *Plugin) Init(_ context.Context, config map[string]string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthCheck logs in (if needed) and lists the account's EV chargers.
|
// HealthCheck logs in (if needed) and counts the account's EV chargers. It takes
|
||||||
|
// the same inventory the chargers capability returns rather than the standalone
|
||||||
|
// list alone: that list omits every charger that belongs to a system, so probing
|
||||||
|
// it reported "0 chargers" for an account whose chargers the panel was, at the
|
||||||
|
// same moment, listing.
|
||||||
|
//
|
||||||
|
// Authenticated but holding no charger is degraded, not down — the half we
|
||||||
|
// address works, and the missing half is the account (or the country, which
|
||||||
|
// picks the regional server).
|
||||||
func (p *Plugin) HealthCheck(ctx context.Context) plugins.Health {
|
func (p *Plugin) HealthCheck(ctx context.Context) plugins.Health {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
body, err := p.apiRequest(ctx, epStandaloneChargers, map[string]any{})
|
doc, err := p.chargerInventory(ctx)
|
||||||
lat := time.Since(start).Milliseconds()
|
lat := time.Since(start).Milliseconds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins.Health{Status: plugins.StatusDown, LatencyMs: lat, Detail: shorten(err.Error())}
|
return plugins.Health{Status: plugins.StatusDown, LatencyMs: lat, Detail: shorten(err.Error())}
|
||||||
}
|
}
|
||||||
detail := "authenticated; EV chargers reachable"
|
if doc.Count == 0 {
|
||||||
if n, ok := countChargers(body); ok {
|
detail := "authenticated; no EV charger on the account — if it does own one, check the country setting, which picks the Anker server"
|
||||||
detail = fmt.Sprintf("authenticated; %d EV charger(s) bound to account", n)
|
if len(doc.Warnings) > 0 {
|
||||||
|
detail = "authenticated; no EV charger found: " + strings.Join(doc.Warnings, "; ")
|
||||||
|
}
|
||||||
|
return plugins.Health{Status: plugins.StatusDegraded, LatencyMs: lat, Detail: shorten(detail)}
|
||||||
|
}
|
||||||
|
detail := fmt.Sprintf("authenticated; %d EV charger(s) on the account", doc.Count)
|
||||||
|
if len(doc.Warnings) > 0 {
|
||||||
|
// Some view failed; the count still stands, but say it is a floor.
|
||||||
|
detail += fmt.Sprintf(" (%d of the cloud's views did not answer)", len(doc.Warnings))
|
||||||
}
|
}
|
||||||
return plugins.Health{Status: plugins.StatusOK, LatencyMs: lat, Detail: detail}
|
return plugins.Health{Status: plugins.StatusOK, LatencyMs: lat, Detail: detail}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,10 +190,32 @@ func (p *Plugin) siteList(ctx context.Context) ([]siteRef, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// accountChargers lists every EV charger on the account by merging the views
|
// chargersDoc is what the chargers capability answers, and what the health
|
||||||
|
// check counts. boundCount is the standalone view's own tally of the account's
|
||||||
|
// chargers, which is not the same number as len(chargers) — it misses whatever
|
||||||
|
// only the site and bound-device views can see, so it is reported alongside
|
||||||
|
// rather than instead.
|
||||||
|
type chargersDoc struct {
|
||||||
|
Chargers []accountCharger `json:"chargers"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
BoundCount *int `json:"boundCount,omitempty"`
|
||||||
|
Warnings []string `json:"warnings,omitempty"`
|
||||||
|
Detail string `json:"detail,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// accountChargers is the chargers capability: the inventory as a JSON document.
|
||||||
|
func (p *Plugin) accountChargers(ctx context.Context) (json.RawMessage, error) {
|
||||||
|
doc, err := p.chargerInventory(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargerInventory lists every EV charger on the account by merging the views
|
||||||
// described at the top of this file. A view that fails is recorded as a warning
|
// described at the top of this file. A view that fails is recorded as a warning
|
||||||
// and the others still answer; only losing all of them is an error.
|
// and the others still answer; only losing all of them is an error.
|
||||||
func (p *Plugin) accountChargers(ctx context.Context) (json.RawMessage, error) {
|
func (p *Plugin) chargerInventory(ctx context.Context) (chargersDoc, error) {
|
||||||
inv := newInventory()
|
inv := newInventory()
|
||||||
var warnings []string
|
var warnings []string
|
||||||
views, failed := 0, 0
|
views, failed := 0, 0
|
||||||
@@ -242,23 +264,20 @@ func (p *Plugin) accountChargers(ctx context.Context) (json.RawMessage, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if failed == views {
|
if failed == views {
|
||||||
return nil, fmt.Errorf("anker-solix: chargers: every cloud view failed: %s", strings.Join(warnings, "; "))
|
return chargersDoc{}, fmt.Errorf("anker-solix: chargers: every cloud view failed: %s", strings.Join(warnings, "; "))
|
||||||
}
|
}
|
||||||
|
|
||||||
out := inv.list()
|
out := inv.list()
|
||||||
doc := map[string]any{"chargers": out, "count": len(out)}
|
doc := chargersDoc{Chargers: out, Count: len(out), Warnings: warnings}
|
||||||
if boundCount >= 0 {
|
if boundCount >= 0 {
|
||||||
doc["boundCount"] = boundCount
|
doc.BoundCount = &boundCount
|
||||||
}
|
|
||||||
if len(warnings) > 0 {
|
|
||||||
doc["warnings"] = warnings
|
|
||||||
}
|
}
|
||||||
if len(out) == 0 {
|
if len(out) == 0 {
|
||||||
// An account that owns chargers but exposes none through any view is
|
// An account that owns chargers but exposes none through any view is
|
||||||
// almost always pointed at the wrong regional server.
|
// almost always pointed at the wrong regional server.
|
||||||
doc["detail"] = "No EV charger was returned by any view. If the account does own one, check the country setting — it selects the Anker server, and the wrong one logs in but shows nothing."
|
doc.Detail = "No EV charger was returned by any view. If the account does own one, check the country setting — it selects the Anker server, and the wrong one logs in but shows nothing."
|
||||||
}
|
}
|
||||||
return json.Marshal(doc)
|
return doc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setChargerStatus records a raw operating-state code and its name; the first
|
// setChargerStatus records a raw operating-state code and its name; the first
|
||||||
|
|||||||
@@ -1474,7 +1474,9 @@ onBeforeUnmount(() => {
|
|||||||
<p
|
<p
|
||||||
v-if="ankerHealth"
|
v-if="ankerHealth"
|
||||||
class="mt-2 text-sm"
|
class="mt-2 text-sm"
|
||||||
:class="ankerHealth.status === 'ok' ? 'text-success' : 'text-danger'"
|
:class="ankerHealth.status === 'ok'
|
||||||
|
? 'text-success'
|
||||||
|
: ankerHealth.status === 'degraded' ? 'text-warning' : 'text-danger'"
|
||||||
>
|
>
|
||||||
{{ ankerHealth.detail || ankerHealth.status }}
|
{{ ankerHealth.detail || ankerHealth.status }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user