package ankersolix import ( "encoding/hex" "testing" ) // The card-list frame, captured from a live A5191 on firmware 1.0.6.1. The nine // UIDs in it are the nine the account's own card list answered with, which is // what named this frame in the first place. const capturedCardFrame = "ff09540003010f090400a10132a2020109a3080404dfe672151a90a4050457c4df2a" + "a505046745bb4ea6050477dfd24ea70504d7b1c44ea80504778acd4ea905048a3cbc3f" + "aa0504da7cb93fab05044754dc2a00" func TestDecodeCardListFrame(t *testing.T) { data, err := hex.DecodeString(capturedCardFrame) if err != nil { t.Fatal(err) } msgType, values, err := decodeFrame(data) if err != nil { t.Fatalf("decodeFrame: %v", err) } if msgType != msgEVCards { t.Fatalf("msgType = %q, want %q", msgType, msgEVCards) } if n, _ := values["rfidCardCount"].(float64); n != 9 { t.Errorf("rfidCardCount = %v, want 9", values["rfidCardCount"]) } cards, ok := values["rfidCards"].([]string) if !ok { t.Fatalf("rfidCards = %T, want []string", values["rfidCards"]) } // In the charger's own order: newest first, which is the reverse of the way // the account lists them. want := []string{ "04DFE672151A90", "57C4DF2A", "6745BB4E", "77DFD24E", "D7B1C44E", "778ACD4E", "8A3CBC3F", "DA7CB93F", "4754DC2A", } if len(cards) != len(want) { t.Fatalf("got %d cards, want %d: %v", len(cards), len(want), cards) } for i, w := range want { if cards[i] != w { t.Errorf("card %d = %q, want %q", i, cards[i], w) } } } // A UID is an identifier, not a quantity: read as a big-endian number, 57C4DF2A // becomes 1472519978 and matches nothing the account ever prints. func TestCardUIDsAreHexNotNumbers(t *testing.T) { v, ok := decodeValue(typeBytes, []byte{0x57, 0xc4, 0xdf, 0x2a}, mqttField{}) if !ok || v != "57C4DF2A" { t.Fatalf("decodeValue = %v (%T), want \"57C4DF2A\"", v, v) } } // The charger's own view of its OCPP backend, from the same capture. func TestDecodeOcppInfoFrame(t *testing.T) { data, err := hex.DecodeString("ff096a0003010f091100a10132a20600416e6b6572a331007773733a2f2f" + "6368617267696e672d6f63707070726f78792d65752d70726f642e616e6b65722e636f6d2f6f6370706a" + "a4020100a51200415432444b325a31463438333030313134a60100a70100a80100a7") if err != nil { t.Fatal(err) } msgType, values, err := decodeFrame(data) if err != nil { t.Fatalf("decodeFrame: %v", err) } if msgType != msgEVOcppInfo { t.Fatalf("msgType = %q, want %q", msgType, msgEVOcppInfo) } if got := values["ocppBackendName"]; got != "Anker" { t.Errorf("ocppBackendName = %v, want Anker", got) } if got := values["ocppBackendUrl"]; got != "wss://charging-ocppproxy-eu-prod.anker.com/ocppj" { t.Errorf("ocppBackendUrl = %v", got) } if got, _ := values["ocppSource"].(float64); got != 0 { t.Errorf("ocppSource = %v, want 0 — the number the account's OCPP view leaves bare", values["ocppSource"]) } if got := values["ocppChargePointId"]; got != "AT2DK2Z1F48300114" { t.Errorf("ocppChargePointId = %v", got) } } // The tap itself, captured while a card was enrolled at the charger: the reader // reports the UID the moment the card touches it. func TestDecodeCardReadFrame(t *testing.T) { data, err := hex.DecodeString("ff091b0003010f090800a10132a20107a3080404dfe672151a90a8") if err != nil { t.Fatal(err) } msgType, values, err := decodeFrame(data) if err != nil { t.Fatalf("decodeFrame: %v", err) } if msgType != msgEVCardRead { t.Fatalf("msgType = %q, want %q", msgType, msgEVCardRead) } if got := values["rfidCardRead"]; got != "04DFE672151A90" { t.Errorf("rfidCardRead = %v, want 04DFE672151A90", got) } } // The same frame when the window closed with nothing tapped: no UID, and the // absence is the answer. func TestCardReadWithoutACard(t *testing.T) { data, err := hex.DecodeString("ff09110003010f090801a10132a20107dc") if err != nil { t.Fatal(err) } msgType, values, err := decodeFrame(data) if err != nil { t.Fatalf("decodeFrame: %v", err) } if msgType != msgEVCardRead { t.Fatalf("msgType = %q, want %q", msgType, msgEVCardRead) } if _, ok := values["rfidCardRead"]; ok { 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) } } } // The frames this connector sends to enrol a card, against the ones the Anker // app was captured sending. They are compared byte for byte: neither carries a // timestamp, so there is nothing in them that can differ between two senders, // and anything that does differ is a difference the charger would see. func TestCardCommandsMatchTheApp(t *testing.T) { open, err := encodeFrame(msgEVPowerMode, []cmdField{ rawField(0xa1, 0x22), uintField(0xa2, powerModeReadCard), }) if err != nil { t.Fatal(err) } if got, want := hex.EncodeToString(open), "ff09110003000f0108a10122a2020107c6"; got != want { t.Errorf("reader-open frame:\n got %s\nwant %s (the app's own)", got, want) } list, err := encodeFrame(msgEVCardListReq, []cmdField{rawField(0xa1, 0x22)}) if err != nil { t.Fatal(err) } if got, want := hex.EncodeToString(list), "ff090d0003000f0104a1012270"; got != want { t.Errorf("list-request frame:\n got %s\nwant %s (the app's own)", got, want) } uid, _ := hex.DecodeString("04DFE672151A90") for _, tc := range []struct { name string action uint8 want string }{ {"add", cardWriteAdd, "ff091f0003000f0103a10122a2020101a3020101a4080404dfe672151a901c"}, {"delete", cardWriteRemove, "ff091f0003000f0103a10122a2020102a3020101a4080404dfe672151a901f"}, } { frame, err := encodeFrame(msgEVCardWrite, []cmdField{ rawField(0xa1, 0x22), uintField(0xa2, tc.action), uintField(0xa3, 1), bytesField(0xa4, uid), }) if err != nil { t.Fatalf("%s: %v", tc.name, err) } if got := hex.EncodeToString(frame); got != tc.want { t.Errorf("%s frame:\n got %s\nwant %s (the app's own)", tc.name, got, tc.want) } } }