0103 writes a card, 0104 asks for the list, 0108 a2=7 opens the reader
Caught on the charger's own command topic while a card was removed and added back in the Anker app. Enrolling at the charger is three MQTT messages and no REST call: open the reader, take the UID the tap publishes, write it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
62b0a29598
commit
4c73d4ac05
@@ -122,3 +122,33 @@ func TestCardReadWithoutACard(t *testing.T) {
|
|||||||
t.Errorf("rfidCardRead = %v, want it absent", values["rfidCardRead"])
|
t.Errorf("rfidCardRead = %v, want it absent", values["rfidCardRead"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The two writes the Anker app sent, caught on the charger's own command topic:
|
||||||
|
// the same card removed and then added back, the action the only difference.
|
||||||
|
func TestDecodeCardWriteCommands(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name, hex string
|
||||||
|
action float64
|
||||||
|
}{
|
||||||
|
{"delete", "ff091f0003000f0103a10122a2020102a3020101a4080404dfe672151a901f", 2},
|
||||||
|
{"add", "ff091f0003000f0103a10122a2020101a3020101a4080404dfe672151a901c", 1},
|
||||||
|
} {
|
||||||
|
data, err := hex.DecodeString(tc.hex)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
msgType, values, err := decodeFrame(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: decodeFrame: %v", tc.name, err)
|
||||||
|
}
|
||||||
|
if msgType != msgEVCardWrite {
|
||||||
|
t.Fatalf("%s: msgType = %q, want %q", tc.name, msgType, msgEVCardWrite)
|
||||||
|
}
|
||||||
|
if got, _ := values["cardWriteAction"].(float64); got != tc.action {
|
||||||
|
t.Errorf("%s: cardWriteAction = %v, want %v", tc.name, values["cardWriteAction"], tc.action)
|
||||||
|
}
|
||||||
|
if got := values["cardWritten"]; got != "04DFE672151A90" {
|
||||||
|
t.Errorf("%s: cardWritten = %v, want the UID", tc.name, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,20 @@ const (
|
|||||||
// from the account: the same address get_ocpp_info reports, and the source
|
// from the account: the same address get_ocpp_info reports, and the source
|
||||||
// number that view leaves as a bare integer.
|
// number that view leaves as a bare integer.
|
||||||
msgEVOcppInfo = "0911"
|
msgEVOcppInfo = "0911"
|
||||||
|
// The three commands the app sends about cards, all captured from its own
|
||||||
|
// traffic on the charger's command topic. The pattern is the connector's
|
||||||
|
// everywhere: a command 01xx is answered by the data frame 09xx.
|
||||||
|
//
|
||||||
|
// 0103 write one card: a2 = 1 add, 2 delete; a4 = the UID -> 0903, then 0904
|
||||||
|
// 0104 ask for the list -> 0904
|
||||||
|
// 0111 ask which OCPP backend -> 0911
|
||||||
|
//
|
||||||
|
// The fourth is not its own message: opening the reader is the power-mode
|
||||||
|
// command with powerModeReadCard, which is answered by 0908.
|
||||||
|
msgEVCardWrite = "0103"
|
||||||
|
msgEVCardListReq = "0104"
|
||||||
|
msgEVOcppInfoReq = "0111"
|
||||||
|
|
||||||
// The reader, reporting a card held against it. Captured during an
|
// The reader, reporting a card held against it. Captured during an
|
||||||
// enrol-at-the-charger: the frame arrives the moment the card is tapped,
|
// enrol-at-the-charger: the frame arrives the moment the card is tapped,
|
||||||
// carrying the UID, and the charger publishes its updated card list a second
|
// carrying the UID, and the charger publishes its updated card list a second
|
||||||
@@ -99,7 +113,14 @@ const (
|
|||||||
// closes with nothing tapped, which is what makes the UID field the event.
|
// closes with nothing tapped, which is what makes the UID field the event.
|
||||||
msgEVCardRead = "0908"
|
msgEVCardRead = "0908"
|
||||||
|
|
||||||
// powerModeRestart is the only value the power-mode command is known to take.
|
// powerModeReadCard opens the card reader for a tap: the app sends the
|
||||||
|
// power-mode command with this value and the charger answers 0908 — with the
|
||||||
|
// UID if a card was held against it inside the twenty seconds, and without one
|
||||||
|
// if the window closed empty. Captured from the app on an A5191; the same 7
|
||||||
|
// comes back in the answer.
|
||||||
|
powerModeReadCard uint8 = 7
|
||||||
|
|
||||||
|
// powerModeRestart is the only other value the power-mode command is known to take.
|
||||||
// The map documents 5 and nothing else, so nothing else is sent.
|
// The map documents 5 and nothing else, so nothing else is sent.
|
||||||
powerModeRestart uint8 = 5
|
powerModeRestart uint8 = 5
|
||||||
)
|
)
|
||||||
@@ -232,6 +253,13 @@ var evCardRead = map[byte]mqttField{
|
|||||||
0xa3: {name: "rfidCardRead"},
|
0xa3: {name: "rfidCardRead"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// evCardWrite names the write the app sends. a3 was 1 on both the add and the
|
||||||
|
// delete, so it keeps its own key rather than a guessed name.
|
||||||
|
var evCardWrite = map[byte]mqttField{
|
||||||
|
0xa2: {name: "cardWriteAction"}, // 1 add, 2 delete
|
||||||
|
0xa4: {name: "cardWritten"},
|
||||||
|
}
|
||||||
|
|
||||||
// evOcppInfo names the charger's own view of its OCPP backend.
|
// evOcppInfo names the charger's own view of its OCPP backend.
|
||||||
var evOcppInfo = map[byte]mqttField{
|
var evOcppInfo = map[byte]mqttField{
|
||||||
0xa2: {name: "ocppBackendName"},
|
0xa2: {name: "ocppBackendName"},
|
||||||
@@ -244,9 +272,10 @@ var evOcppInfo = map[byte]mqttField{
|
|||||||
// settings report: evMessages is what stamps the timestamp a control command
|
// settings report: evMessages is what stamps the timestamp a control command
|
||||||
// waits on, and a card list is not an acknowledgement of anything.
|
// waits on, and a card list is not an acknowledgement of anything.
|
||||||
var evInfoMessages = map[string]map[byte]mqttField{
|
var evInfoMessages = map[string]map[byte]mqttField{
|
||||||
msgEVCards: evCards,
|
msgEVCards: evCards,
|
||||||
msgEVOcppInfo: evOcppInfo,
|
msgEVOcppInfo: evOcppInfo,
|
||||||
msgEVCardRead: evCardRead,
|
msgEVCardRead: evCardRead,
|
||||||
|
msgEVCardWrite: evCardWrite,
|
||||||
}
|
}
|
||||||
|
|
||||||
// evMessages selects a field map by message type. A type absent from here is one
|
// evMessages selects a field map by message type. A type absent from here is one
|
||||||
|
|||||||
Reference in New Issue
Block a user