The charger's own card list gets a door, and says where it differs

0104 was already implemented and already in the action catalogue; nothing
routed to it, so the only way to see the device's list was as a side
effect of writing a card. It has an endpoint now, and the panel a button.

The two lists are compared where they meet: a card the charger holds and
the account has forgotten still opens it, and a card only the account
holds will not, and neither shows anywhere else. The comparison is drawn
only when they disagree, and the device's reading is dropped on a refresh
rather than measured against an account list from a later moment.

The new test asks all four card routes without a token: a capability the
plugin implements and the catalogue advertises is still unusable if
nothing routes to it, and no other test here would notice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tajniak81andClaude Opus 5 committed 2026-09-03 14:35:27 +02:00
1 parent 7176867eb3
commit 3064bff8ad
8 files changed
+165 -2

No files matched your search

@@ -648,7 +648,24 @@ func (s *Server) handleAnkerCardScan(w http.ResponseWriter, r *http.Request) {
s.ankerCardWrite(w, r, who, cfg, "rfid-card-scan", sn, map[string]any{"sn": sn})
}
// ankerCardGate is everything both writes need before they may run: a caller, a
// GET /api/integrations/anker-solix/chargers/{sn}/rfid-cards/charger — the list
// of cards the charger itself holds, asked of the device with 0104 rather than
// of the account.
//
// The two lists are written together and can still come apart: a card the
// account has forgotten still opens the charger until the device is told
// otherwise, and the account's copy is the only one every other view here
// draws. Asking the device is the only way to see the difference. It answers
// with UIDs and nothing else — the charger has no field for a card's name.
func (s *Server) handleAnkerChargerCards(w http.ResponseWriter, r *http.Request) {
who, sn, cfg, ok := s.ankerCardGate(w, r)
if !ok {
return
}
s.ankerCardWrite(w, r, who, cfg, "rfid-cards-charger", sn, map[string]any{"sn": sn})
}
// ankerCardGate is everything a card call needs before it may run: a caller, a
// serial, an integration that is on and has credentials, and a rate limit. A
// gate that is off answers 409 rather than the reads' 200-with-a-reason: a write
// that did not happen is not a state to render, it is a request that failed.
@@ -670,7 +687,7 @@ func (s *Server) ankerCardGate(w http.ResponseWriter, r *http.Request) (*callerI
return nil, "", nil, false
}
if !s.ctlRL.allow(who.ID + "|" + sn) {
writeError(w, http.StatusTooManyRequests, "too many card changes; please slow down")
writeError(w, http.StatusTooManyRequests, "too many card requests; please slow down")
return nil, "", nil, false
}
return who, sn, map[string]string{