Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 56 additions & 15 deletions docs/protocol/connection.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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`

Expand All @@ -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`

Expand All @@ -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`
Expand Down
25 changes: 20 additions & 5 deletions docs/protocol/device_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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`)
57 changes: 43 additions & 14 deletions docs/protocol/packet_traces/02_authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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).
Expand All @@ -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
```
Expand All @@ -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.
Expand Down
20 changes: 14 additions & 6 deletions docs/reimplementation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
14 changes: 11 additions & 3 deletions docs/reimplementation/checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
21 changes: 15 additions & 6 deletions docs/reimplementation/common_pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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
Expand Down
22 changes: 14 additions & 8 deletions docs/reimplementation/layer_by_layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading