The last message in the map, and it reboots the charger
0108 was the one thing in the MQTT inventory nobody had wired: the device power mode, whose single documented value restarts the charger. It is the only way to reboot a charger that is on neither a CSMS nor the local network — which is most of them — so the cloud transport sends it now, and "reset" reaches it too, since that is what the OCPP path has always called the same act. Nothing waits for a confirmation: the device that would send it is the device rebooting, so the command answers at once and says the charger drops off the cloud for about a minute. The gate is unchanged and now covers both spellings — an explicit confirm plus a password step-up, audited either way. Modbus still refuses, because no register does this, but its refusal now names both transports that can rather than only the CSMS. Both clients already had the reset button and its password prompt; they were hidden in every mode that reads the device, which is why the cloud never showed one. Modbus is now the only mode without it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2d333a63f
commit
4ff6242c8f
17 files changed
+159
-21
No files matched your search
@@ -71,6 +71,47 @@ func TestStatusRequestSendsItsClockWithoutAValueType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The restart carries the one value the map documents for the power-mode
|
||||
// command, opened and closed like every other command.
|
||||
func TestRestartFrameCarriesThePowerModeValue(t *testing.T) {
|
||||
got, err := encodeFrame(msgEVPowerMode, []cmdField{
|
||||
rawField(0xa1, 0x22),
|
||||
uintField(0xa2, powerModeRestart),
|
||||
timestampField(time.Unix(1756813256, 0)),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("encodeFrame: %v", err)
|
||||
}
|
||||
want := "ff09180003000f0108" + // header: marker, length 24, send pattern, type 0108
|
||||
"a10122" + // a1: the opener, no value type
|
||||
"a2020105" + // a2: ui 5 — restart
|
||||
"fe0503c8d7b668" // fe: var — the sender's clock
|
||||
if h := encodeHex(got); h[:len(want)] != want {
|
||||
t.Fatalf("frame = %s / want %s + checksum", h, want)
|
||||
}
|
||||
var sum byte
|
||||
for _, b := range got {
|
||||
sum ^= b
|
||||
}
|
||||
if sum != 0 {
|
||||
t.Errorf("checksum does not close the frame: %02x", sum)
|
||||
}
|
||||
}
|
||||
|
||||
// Both spellings reach the restart; nothing else does.
|
||||
func TestIsRestartTakesEitherName(t *testing.T) {
|
||||
for _, name := range []string{"restart", "reset"} {
|
||||
if !isRestart(name) {
|
||||
t.Errorf("%q should ask for a restart", name)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"reboot", "start", "stop", "trigger", ""} {
|
||||
if isRestart(name) {
|
||||
t.Errorf("%q should not ask for a restart", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A frame is only self-consistent if XORing every byte, checksum included,
|
||||
// comes to zero — which is exactly what the decoder checks.
|
||||
func TestEncodeFrameChecksumClosesToZero(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user