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