Origin
Companion issue to eman/esphome-alpha-hwr#46, filed against this repo because the ESPHome component's identical opcode bug is a direct port of this client's enable_remote_mode()/disable_remote_mode().
Problem
enable_remote_mode() and disable_remote_mode() build their Class 3 GENIbus frame with the INFO operation specifier (0xC1) instead of SET (0x81) in the second APDU byte, so they likely never actually execute on the pump — INFO only asks the pump to describe a data item, it doesn't perform the command.
Note: unlike the ESPHome port, this client does not have the companion "switch reflects a local intent flag instead of pump state" bug — get_mode() already derives SetpointInfo.is_remote from the pump's own control_source readback (services/control.py:465-467, is_remote = (control_source == 2)), not from a locally-latched flag. So the fix needed here is narrower: just the opcode in the two methods below, plus verifying the control_source == 2 mapping is actually correct once the opcode is fixed.
Evidence
services/control.py:312-316 — enable_remote_mode(): apdu = bytes([0x03, 0xC1, 0x07])
services/control.py:341-345 — disable_remote_mode(): apdu = bytes([0x03, 0xC1, 0x06])
- Per GENIbus's APDU encoding (operation in bits 7-6, length in bits 5-0 of the second byte):
0xC1 = operation 3 (INFO) + length 1; the SET operation for a 1-byte command is 0x81 (operation 2 + length 1).
- Cross-repo bench confirmation: esphome-alpha-hwr#23 tested both against a real pump —
[0x03, 0xC1, 0x07] returns an 8-byte ack [03 01 AC] (class 3, ack OK, one data byte — an INFO descriptor, i.e. no execution), while [0x03, 0x81, 0x07] returns [03 00] (class 3, ack OK, zero data bytes — a clean execution ack), and only the latter actually starts/stops the pump when tested with the analogous START/STOP command IDs.
- Countervailing evidence worth checking before changing this:
resources/GRUNDFOS_APP_BLE_DATA_MAPPING.md (decompiled Grundfos GO app analysis) documents the real app's Class 3 commands as using OS_INFO (section 9, item 6, and the example frame at line 556-570), but sent from source address 0x0A ("Remote Master") rather than 0xF8, which this client always uses (_build_geni_packet(0xF8, 0xE7, apdu), e.g. services/control.py:314, 343). It's unconfirmed whether the real fix is the opcode, the source address, or both — see the discussion in esphome-alpha-hwr#46 for the full reasoning.
Suggested fix
Bench-verify against real hardware which change actually causes the pump's control_source (as read by get_mode()) to move between values, then apply it here: either changing the opcode to 0x81 (keeping source 0xF8), changing the source address to 0x0A (keeping opcode 0xC1), or both. Once fixed, confirm whether is_remote = (control_source == 2) (services/control.py:465-467) is actually the correct mapping — the ESPHome-side testing found control_source reading 0 in every sample taken so far, consistent with REMOTE/LOCAL never having actually executed.
References
Origin
Companion issue to eman/esphome-alpha-hwr#46, filed against this repo because the ESPHome component's identical opcode bug is a direct port of this client's
enable_remote_mode()/disable_remote_mode().Problem
enable_remote_mode()anddisable_remote_mode()build their Class 3 GENIbus frame with the INFO operation specifier (0xC1) instead of SET (0x81) in the second APDU byte, so they likely never actually execute on the pump — INFO only asks the pump to describe a data item, it doesn't perform the command.Note: unlike the ESPHome port, this client does not have the companion "switch reflects a local intent flag instead of pump state" bug —
get_mode()already derivesSetpointInfo.is_remotefrom the pump's owncontrol_sourcereadback (services/control.py:465-467,is_remote = (control_source == 2)), not from a locally-latched flag. So the fix needed here is narrower: just the opcode in the two methods below, plus verifying thecontrol_source == 2mapping is actually correct once the opcode is fixed.Evidence
services/control.py:312-316—enable_remote_mode():apdu = bytes([0x03, 0xC1, 0x07])services/control.py:341-345—disable_remote_mode():apdu = bytes([0x03, 0xC1, 0x06])0xC1= operation3(INFO) + length1; the SET operation for a 1-byte command is0x81(operation2+ length1).[0x03, 0xC1, 0x07]returns an 8-byte ack[03 01 AC](class 3, ack OK, one data byte — an INFO descriptor, i.e. no execution), while[0x03, 0x81, 0x07]returns[03 00](class 3, ack OK, zero data bytes — a clean execution ack), and only the latter actually starts/stops the pump when tested with the analogous START/STOP command IDs.resources/GRUNDFOS_APP_BLE_DATA_MAPPING.md(decompiled Grundfos GO app analysis) documents the real app's Class 3 commands as using OS_INFO (section 9, item 6, and the example frame at line 556-570), but sent from source address0x0A("Remote Master") rather than0xF8, which this client always uses (_build_geni_packet(0xF8, 0xE7, apdu), e.g.services/control.py:314, 343). It's unconfirmed whether the real fix is the opcode, the source address, or both — see the discussion in esphome-alpha-hwr#46 for the full reasoning.Suggested fix
Bench-verify against real hardware which change actually causes the pump's
control_source(as read byget_mode()) to move between values, then apply it here: either changing the opcode to0x81(keeping source0xF8), changing the source address to0x0A(keeping opcode0xC1), or both. Once fixed, confirm whetheris_remote = (control_source == 2)(services/control.py:465-467) is actually the correct mapping — the ESPHome-side testing foundcontrol_sourcereading0in every sample taken so far, consistent with REMOTE/LOCAL never having actually executed.References
#5)resources/GRUNDFOS_APP_BLE_DATA_MAPPING.md:216-224, 556-620