From db41f47d7c4a79c1e212c1d0246b354d4f40f66f Mon Sep 17 00:00:00 2001 From: GitHub Copilot Date: Tue, 18 Aug 2026 00:21:22 -0700 Subject: [PATCH] docs: the opening packets are reads, and nothing requires them Two claims in these docs are not supported by anything in this repository, and both are now contradicted by direct evidence: connection.md: "the device requires an application-layer handshake to 'unlock' control capabilities and high-frequency telemetry", and "without this, the pump may ignore control commands". device_info.md: "Class 7 reading requires the device to be Authenticated via the handshake sequence. If the device is not authenticated, Class 7 requests may return empty payloads or fail." Both entered in the initial documentation commit, hedged as "may ignore" and "may return ... or fail", which is not how an observation gets written down. bench_findings.md -- this repo's record of measured rather than inferred behaviour -- says nothing about a handshake at all. The mechanism could not have worked. The APDU's second byte is 0booLLLLLL, operation in the top two bits and payload length in the low six, so the 0x03 read as "SET operation, 3 data bytes" is a GET with a 3-byte payload. The three bytes after it are that payload, not a register address plus a value, which is where "register 0x9495, unlock code 0x96" came from. All four packets are reads: a Class 2 GET of unit_family/unit_type/unit_version, a Class 10 GET of Object 86 Sub 6, and two INFO queries. Reads cannot change device state. And the field evidence closes it. Ten connection cycles sending none of them -- two with the BLE bond cleared and re-paired, five across pump power cycles, three plain reconnects -- read all five Class 7 strings and reached full readiness every time. Nine accepted Class 3 START and STOP with the motor confirmed running. Across 1,019 frames there were no Class 2, Class 5 or Class 11 frames at all. Also corrected: the object and sub of the Class 10 read were documented reversed (it is Object 86, Sub 6, not "Sub 0x5600 / Object 0x0006"), and the two address bytes described as one 16-bit "Service ID" are a destination and a source -- the pump's replies swap them. The packets themselves are unchanged and this client still sends them. The docs now describe what they are, and the reimplementation guides tell a new client to skip them. See eman/esphome-alpha-hwr#174 for the decode, the captures, and the removal on that side. Decode and experiment by jfriend00. --- docs/protocol/connection.md | 71 +++++++++++++++---- docs/protocol/device_info.md | 25 +++++-- .../packet_traces/02_authentication.md | 57 +++++++++++---- docs/reimplementation/README.md | 20 ++++-- docs/reimplementation/checklist.md | 14 +++- docs/reimplementation/common_pitfalls.md | 21 ++++-- docs/reimplementation/layer_by_layer.md | 22 +++--- 7 files changed, 173 insertions(+), 57 deletions(-) diff --git a/docs/protocol/connection.md b/docs/protocol/connection.md index 0dcac08..94fc39c 100644 --- a/docs/protocol/connection.md +++ b/docs/protocol/connection.md @@ -1,6 +1,37 @@ -# Connection & Authentication - -To control the ALPHA HWR pump, a client must follow a specific connection sequence. Simply connecting via BLE is not enough; the device requires an application-layer handshake to "unlock" control capabilities and high-frequency telemetry. +# Connection + +To control the ALPHA HWR pump, a client bonds over BLE, subscribes to +notifications, and then reads and writes GENIbus frames. That is the whole +sequence. + +> **Correction, 2026-08-18.** This document used to say the device "requires an +> application-layer handshake to 'unlock' control capabilities and +> high-frequency telemetry", and described the four packets below as that +> handshake. **Neither claim is supported by any capture in this repository.** +> +> The packets decode as ordinary reads — two GETs and two INFO queries — under +> the GENIbus APDU rule that the second byte is `0booLLLLLL`, operation in the +> top two bits and payload length in the low six. `0x03` is a GET with a 3-byte +> payload, not a SET, and the "unlock code" was a length field misparsed. Reads +> cannot change device state, so an unlock was never something these bytes could +> perform. +> +> A separate client then ran ten connection cycles without sending any of them — +> including two with the BLE bond cleared and re-paired, and five across pump +> power cycles. All ten read every Class 7 device-info string and reached full +> readiness; nine accepted Class 3 START and STOP commands with the motor +> confirmed running. Across 1,019 captured frames there were no Class 2, Class 5 +> or Class 11 frames at all. +> +> `bench_findings.md`, this repository's record of measured rather than inferred +> behaviour, has never said anything about a handshake requirement. The claim +> entered in the initial documentation commit, hedged as "may ignore" and "may +> return ... or fail", which is not how an observation gets written down. +> +> The packets are documented below because this client still sends them and +> because knowing what they are has value. They are described as what they are. +> See esphome-alpha-hwr issue #174 for the decode, the captures, and the +> removal. ## 1. BLE Connection @@ -23,18 +54,21 @@ The device requires **Pairing/Bonding** for normal use. * **Level**: `Just Works` (No PIN usually required, though some models might prompt). * **Requirement**: Bonding is recommended. While some commands might work without it, telemetry, control, stable Schedule downloading (HCI layer), and consistent reconnection are much more reliable after bonding. -## 2. Authentication Handshake ("Unlock") - -After connecting and subscribing to notifications, the client **must** send a specific sequence of "Magic Packets". Without this, the pump may ignore control commands (Start/Stop/Set Mode) and will only stream basic or empty telemetry. +## 2. Opening Reads (optional) -This handshake appears to support both legacy GENI implementations and the newer Class 10 DataObject protocol. +After connecting and subscribing to notifications, this client sends four +frames. They are reads, their replies are not consumed, and a pump reaches full +readiness and accepts control commands without them. They are retained here as a +record of what this client does, not as a requirement. ### Sequence The client should send these packets in bursts to ensure the device receives them despite any radio interference or sleep states. -#### Step A: Legacy Magic Packet (Burst) -Send this packet 3 times with a small delay (~50ms) between writes. +#### Step A: Class 2 identity read +A GET of items 148, 149 and 150 — `unit_family`, `unit_type`, `unit_version`. +The ALPHA HWR answers `52 / 7 / 2`. This client sends it 3 times, ~50ms apart; +one send is sufficient, since the reply is the same every time. **Packet**: `27 07 E7 F8 02 03 94 95 96 EB 47` @@ -49,8 +83,12 @@ Send this packet 3 times with a small delay (~50ms) between writes. | 6-8 | `94 95 96` | Payload (Capabilities/Family Query) | | 9-10 | `EB 47` | CRC-16 | -#### Step B: Class 10 Unlock Packet (Burst) -Send this packet 5 times with a small delay (~50ms) between writes. This appears to be the primary unlock command for modern HWR firmware. +#### Step B: Class 10 operation-status read +A GET of Object 86, Sub 6 — the operation-status object, which answers with the +control mode, operation mode and current setpoint. This client sends it 5 times, +~50ms apart. It was described here as "the primary unlock command for modern HWR +firmware"; it is a read, and the same object is polled again in normal +operation. **Packet**: `27 07 E7 F8 0A 03 56 00 06 C5 5A` @@ -62,12 +100,15 @@ Send this packet 5 times with a small delay (~50ms) between writes. This appears | 3 | `0xF8` | Src | | 4 | `0x0A` | Class: 10 (DataObject) | | 5 | `0x03` | OpSpec: 0x03 (Length 3) | -| 6-7 | `56 00` | SubID (Operation/Unlock) | -| 8 | `0x06` | ObjID (Partial/Short) | +| 6 | `0x56` | Object 86 | +| 7-8 | `00 06` | Sub 6 | | 9-10 | `C5 5A` | CRC-16 | -#### Step C: Authorization Extend (Sequence) -Send these two packets once each, with a small delay (~100ms) in between. +#### Step C: Two INFO queries +INFO asks for a data item's scaling metadata; it reads nothing and changes +nothing, which is why "Authorization Extend" was never an accurate name. The +pump answers both with a one-byte INFO head meaning "unscaled". Sent once each, +~100ms apart. 1. **Packet 1**: `27 05 E7 F8 05 C1 4B C3 82` 2. **Packet 2**: `27 05 E7 F8 0B C1 0F D0 C3` diff --git a/docs/protocol/device_info.md b/docs/protocol/device_info.md index e19f5dc..82b55ba 100644 --- a/docs/protocol/device_info.md +++ b/docs/protocol/device_info.md @@ -28,7 +28,7 @@ The ALPHA HWR (Family 52, Type 7) supports reading detailed device identificatio ### Supported Parameters -The following string parameters can be read when the device is connected and authenticated: +The following string parameters can be read once the device is connected and subscribed: - **ID 1**: Product Name (e.g., `LPHA HWR`, cleaned up to `ALPHA HWR`) - **ID 9**: Serial Number Suffix (e.g., `0000479`) @@ -47,9 +47,24 @@ Request: 2707e7f8070109... (Read String ID 9) Response: 240ef8e70701093030303034373900... (Suffix: "0000479") ``` -### Authentication Requirement +### There is no authentication requirement -Class 7 reading requires the device to be **Authenticated** via the handshake sequence (Legacy Magic + Class 10 Magic + Auth Extend). If the device is not authenticated, Class 7 requests may return empty payloads or fail. +**Corrected 2026-08-18.** This section used to read: *"Class 7 reading requires +the device to be Authenticated via the handshake sequence (Legacy Magic + Class +10 Magic + Auth Extend). If the device is not authenticated, Class 7 requests +may return empty payloads or fail."* + +That is not true, and nothing in this repository ever supported it. Ten +connection cycles that sent none of those packets — including two with the BLE +bond cleared and re-paired, and five across pump power cycles — read all five +Class 7 strings every time, fifty reads with no failures and no empty payloads. +The independent ESPHome implementation likewise has no session check anywhere in +its Class 7 read path. + +The claim entered in this repository's initial documentation commit, hedged as +"may return ... or fail", and `bench_findings.md` — the record of measured +rather than inferred behaviour — says nothing about it. See +esphome-alpha-hwr issue #174. ## Available Information @@ -61,7 +76,7 @@ Class 7 reading requires the device to be **Authenticated** via the handshake se ### Via Class 7 Strings (Detailed Info) -Detailed information, including serial numbers and firmware versions, is available via **Class 7 ReadString** operations. This requires an active, authenticated connection. +Detailed information, including serial numbers and firmware versions, is available via **Class 7 ReadString** operations. This requires an active connection with notifications subscribed. ```python # Full device info - connects and reads Class 7 strings @@ -110,4 +125,4 @@ alpha-hwr info ## Related - **Class 2 Registers:** See `docs/protocol/telemetry.md` -- **Authentication Handshake:** Required for Class 7 string reading (see `docs/protocol/connection.md`) +- **Opening reads:** Not required for Class 7 string reading, despite what this document long claimed (see `docs/protocol/connection.md`) diff --git a/docs/protocol/packet_traces/02_authentication.md b/docs/protocol/packet_traces/02_authentication.md index 2a4c8f4..0f20442 100644 --- a/docs/protocol/packet_traces/02_authentication.md +++ b/docs/protocol/packet_traces/02_authentication.md @@ -4,7 +4,33 @@ This document shows a complete authentication sequence with byte-by-byte annotat ## Overview -After BLE connection, the pump requires a specific sequence of "magic packets" to unlock full functionality. This sequence must be sent **exactly** as specified. +> ## ⚠️ Corrected 2026-08-18 — this is not an authentication sequence +> +> This document used to open: *"After BLE connection, the pump requires a +> specific sequence of 'magic packets' to unlock full functionality. This +> sequence must be sent exactly as specified."* **Every part of that is wrong**, +> and the byte annotations below were wrong in the way that produced it. +> +> The APDU's second byte is `0booLLLLLL` — operation in the top two bits, +> payload length in the low six. So `0x03` is **GET with a 3-byte payload**, not +> "SET operation, 3 data bytes". The three bytes after it are the payload, not a +> register address plus a value, which is where "register 0x9495, unlock code +> 0x96" came from. All four packets are reads: two GETs and two INFO queries. +> Reads cannot unlock anything. +> +> Nor is the sequence required, or the repetition. Ten connection cycles sending +> none of it — two with the BLE bond cleared and re-paired, five across pump +> power cycles — reached full readiness, read every Class 7 string, and accepted +> Class 3 START and STOP with the motor confirmed running. +> +> The per-packet annotations below are corrected. The Python example further +> down still sends the packets, because this client still does; it is not doing +> what its comments say it is doing. See esphome-alpha-hwr issue #174 for the +> decode, the captures and the removal. + +After BLE connection, this client sends four frames before anything else. They +are reads, and the sequence is retained here as a record of what the client +does. ## Authentication Flow @@ -15,7 +41,7 @@ sequenceDiagram Client->>Pump: Legacy Magic (x3) - Client->>Pump: Class 10 Unlock (x5) + Client->>Pump: Class 10 operation-status read (x5) Client->>Pump: Extend 1 @@ -41,18 +67,19 @@ sequenceDiagram | 2 | `0xE7` | Service ID | GENI service | | 3 | `0xF8` | Source | Client address | | 4 | `0x02` | Class | Class 2 (Register-based operations) | -| 5 | `0x03` | OpSpec | SET operation, 3 data bytes | -| 6 | `0x94` | Register High | Register address 0x9495 high byte | -| 7 | `0x95` | Register Low | Register address 0x9495 low byte | -| 8 | `0x96` | Data | Unlock value | +| 5 | `0x03` | OpSpec | **GET**, 3-byte payload (`0b00` + length 3) | +| 6 | `0x94` | Data Item | Item 148, `unit_family` | +| 7 | `0x95` | Data Item | Item 149, `unit_type` | +| 8 | `0x96` | Data Item | Item 150, `unit_version` | | 9 | `0xEB` | CRC High | CRC-16-CCITT high byte | | 10 | `0x47` | CRC Low | CRC-16-CCITT low byte | ### Purpose -Unlocks legacy Class 2/3 commands (register-based operations). +Reads the pump's identity. An ALPHA HWR answers `52 / 7 / 2`. ### Repetition -Must be sent **exactly 3 times** in sequence. +This client sends it 3 times. One send is sufficient — the reply is identical +each time, and nothing consumes it in any case. ### Expected Response None (pump acknowledges silently). @@ -68,7 +95,7 @@ for _ in range(3): --- -## Packet 4-8: Class 10 Unlock (Send 5 Times) +## Packet 4-8: Class 10 operation-status read (sent 5 times) ### Hex Dump ``` @@ -84,15 +111,17 @@ for _ in range(3): | 2 | `0xE7` | Service ID | GENI service | | 3 | `0xF8` | Source | Client | | 4 | `0x0A` | Class | Class 10 (DataObject) | -| 5 | `0x03` | OpSpec | SET operation, 3 bytes follow | -| 6 | `0x56` | Sub ID High | Sub 0x5600 (control/unlock subsystem) | -| 7 | `0x00` | Sub ID Low | Sub 0x5600 low byte | -| 8 | `0x06` | Object ID | Object 0x0006 (unlock object) | +| 5 | `0x03` | OpSpec | **GET**, 3-byte payload (`0b00` + length 3) | +| 6 | `0x56` | Object | Object 86 | +| 7 | `0x00` | Sub ID High | Sub 6, high byte | +| 8 | `0x06` | Sub ID Low | Sub 6, low byte | | 9 | `0xC5` | CRC High | CRC-16-CCITT high byte | | 10 | `0x5A` | CRC Low | CRC-16-CCITT low byte | ### Purpose -Unlocks Class 10 commands (modern DataObject operations). Required for telemetry, control, and all advanced features. +Reads the operation-status object, which answers with the control mode, +operation mode and current setpoint. The same object is polled again in normal +operation, so this read is redundant rather than enabling. ### Repetition Must be sent **exactly 5 times** in sequence. diff --git a/docs/reimplementation/README.md b/docs/reimplementation/README.md index a9cd43b..ffba6d6 100644 --- a/docs/reimplementation/README.md +++ b/docs/reimplementation/README.md @@ -94,7 +94,7 @@ wrong again. For a minimal working implementation, you need: 1. **BLE Transport** - Connect to pump, send/receive data -2. **Authentication** - Send magic packets to unlock +2. ~~**Authentication**~~ - not required; see below 3. **Frame Encoding/Decoding** - Build and parse GENI frames 4. **Telemetry Reading** - Read pump measurements 5. **Control Commands** - Start/stop, set mode @@ -151,11 +151,19 @@ graph TD ### Concepts -#### 1. Authentication -The pump requires a specific sequence of "magic packets" to unlock: -- 3x Legacy Magic packets -- 5x Class 10 Unlock packets -- 2x Extend packets +#### 1. Opening reads (skip these) + +> **Corrected 2026-08-18.** These four frames are **not** an authentication +> handshake and are **not** required. They decode as GENIbus reads — two GETs +> and two INFO queries — and ten connection cycles omitting them entirely, +> including two with the BLE bond cleared and re-paired, reached full readiness +> and accepted control commands. If you are writing a new client, **skip this +> step.** See esphome-alpha-hwr issue #174. + +For the record, this client sends: +- 3x Class 2 identity read (`unit_family` / `unit_type` / `unit_version`) +- 5x Class 10 operation-status read (Object 86, Sub 6) +- 2x INFO queries (Class 5 item `0x4B`, Class 11 item `0x0F`) See [02_authentication.md](../protocol/packet_traces/02_authentication.md) for details. diff --git a/docs/reimplementation/checklist.md b/docs/reimplementation/checklist.md index 1d1fede..da0f419 100644 --- a/docs/reimplementation/checklist.md +++ b/docs/reimplementation/checklist.md @@ -13,10 +13,18 @@ Use this checklist to track your implementation progress. Features are organized - [ ] Bond/pair — an unbonded idle connection is dropped at ~1.8 s - [ ] Disconnect gracefully -### Authentication -- [ ] Send 3x Legacy Magic packets +### Opening reads (optional — skip if writing a new client) + +> **Corrected 2026-08-18.** These four frames are **not** an authentication +> handshake and are **not** required. They decode as GENIbus reads — two GETs +> and two INFO queries — and ten connection cycles omitting them entirely, +> including two with the BLE bond cleared and re-paired, reached full readiness +> and accepted control commands. If you are writing a new client, **skip this +> step.** See esphome-alpha-hwr issue #174. + +- [ ] Send 3x Class 2 identity read - Packet: `27 07 E7 F8 02 03 94 95 96 EB 47` -- [ ] Send 5x Class 10 Unlock packets +- [ ] Send 5x Class 10 operation-status read - Packet: `27 07 E7 F8 0A 03 56 00 06 C5 5A` - [ ] Send Extend 1 packet - Packet: `27 05 E7 F8 05 C1 4B C3 82` diff --git a/docs/reimplementation/common_pitfalls.md b/docs/reimplementation/common_pitfalls.md index b356d9e..4bd3a14 100644 --- a/docs/reimplementation/common_pitfalls.md +++ b/docs/reimplementation/common_pitfalls.md @@ -65,7 +65,7 @@ def check(frame_hex, expected): check("2705e7f805c14bc382", 0xC382) # Extend 1 check("2705e7f80bc10fd0c3", 0xD0C3) # Extend 2 check("2707e7f80203949596eb47", 0xEB47) # Legacy magic -check("2707e7f80a03560006c55a", 0xC55A) # Class 10 unlock +check("2707e7f80a03560006c55a", 0xC55A) # Class 10 operation-status read check("2705e7f8038106e587", 0xE587) # Class 3 START ``` @@ -106,12 +106,21 @@ pump. Do not reproduce it. **Cause:** Skipped or incorrect authentication sequence. -**Correct Sequence:** +**The sequence this client sends** — optional, and the "exactly N" was never +a real constraint: + +> **Corrected 2026-08-18.** These four frames are **not** an authentication +> handshake and are **not** required. They decode as GENIbus reads — two GETs +> and two INFO queries — and ten connection cycles omitting them entirely, +> including two with the BLE bond cleared and re-paired, reached full readiness +> and accepted control commands. If you are writing a new client, **skip this +> step.** See esphome-alpha-hwr issue #174. + 1. Connect to BLE device -2. Send **exactly 3** Legacy Magic packets: `27 07 E7 F8 02 03 94 95 96 EB 47` -3. Send **exactly 5** Class 10 Unlock packets: `27 07 E7 F8 0A 03 56 00 06 C5 5A` -4. Send **exactly 1** Extend 1 packet: `27 05 E7 F8 0B C1 0F D0 C3` -5. Send **exactly 1** Extend 2 packet: `27 05 E7 F8 05 C1 4B C3 82` +2. 3x Class 2 identity read: `27 07 E7 F8 02 03 94 95 96 EB 47` +3. 5x Class 10 operation-status read: `27 07 E7 F8 0A 03 56 00 06 C5 5A` +4. 1x INFO query, Class 11 item `0x0F`: `27 05 E7 F8 0B C1 0F D0 C3` +5. 1x INFO query, Class 5 item `0x4B`: `27 05 E7 F8 05 C1 4B C3 82` **Common Mistakes:** - Wrong number of repetitions diff --git a/docs/reimplementation/layer_by_layer.md b/docs/reimplementation/layer_by_layer.md index 10bbc82..89b67a6 100644 --- a/docs/reimplementation/layer_by_layer.md +++ b/docs/reimplementation/layer_by_layer.md @@ -561,17 +561,23 @@ assert len(frame["payload"]) == 4 --- -## Layer 4: Authentication +## Layer 4: Opening reads (optional — you can skip this layer) -**Goal**: Unlock pump with magic packet sequence. +> **Corrected 2026-08-18.** These four frames are **not** an authentication +> handshake and are **not** required. They decode as GENIbus reads — two GETs +> and two INFO queries — and ten connection cycles omitting them entirely, +> including two with the BLE bond cleared and re-paired, reached full readiness +> and accepted control commands. If you are writing a new client, **skip this +> step.** See esphome-alpha-hwr issue #174. -### 4.1 Authentication Sequence +**Goal**: match what the reference client sends, if you want to. -Send exactly these packets in order: -1. Legacy Magic × 3 -2. Class 10 Unlock × 5 -3. Extend 1 × 1 -4. Extend 2 × 1 +### 4.1 The sequence this client sends + +1. Class 2 identity read × 3 +2. Class 10 operation-status read × 5 +3. INFO query on Class 5 item `0x4B` × 1 +4. INFO query on Class 11 item `0x0F` × 1 **Packets** — these are captured constants the pump accepts. Earlier revisions of this page listed four *different* packets here, contradicting