Skip to content

fix: authentication extension packet ordering and Bleak 3.x compatibility#25

Open
eman wants to merge 12 commits into
mainfrom
fix/bleak3-compat
Open

fix: authentication extension packet ordering and Bleak 3.x compatibility#25
eman wants to merge 12 commits into
mainfrom
fix/bleak3-compat

Conversation

@eman
Copy link
Copy Markdown
Owner

@eman eman commented May 15, 2026

Summary

Fixes the bugs surfaced by issue #24 (pump disconnecting after authentication on BLE firmware V06.00.01).

Fix 1: Extension packets sent in wrong order and in parallel

send_extension_packets() used asyncio.TaskGroup to fire both packets concurrently, with EXTEND_2 submitted before EXTEND_1. The packet trace spec requires EXTEND_1 → EXTEND_2 with a 50ms gap. Sending them in parallel causes premature disconnection. Fixed by sending sequentially with await and a 50ms sleep.

Fix 2: Bump minimum Bleak requirement to 3.0

Bleak 3.0 removed the adapter= keyword. Rather than maintain a version-detection shim, the minimum requirement is now bleak>=3.0.0. On Linux, an adapter name is passed via bluez={"adapter": ...}; on other platforms it is ignored. The _make_bleak_client() helper and its tests are removed.

Fix 3: Disconnection guard in read_once()

Added transport.is_connected() checks before the flow/pressure and temperature queries. If the pump disconnects mid-sequence the method now returns partial telemetry instead of raising a BleakError.

Fix 4: SetpointInfo.control_mode type (intControlMode | int)

The field was typed as bare int, causing AttributeError when callers accessed .name. Added a field_validator that coerces known values to the ControlMode IntEnum; unknown values remain as int. The service now passes the already-coerced self._current_mode to the constructor.

Fix 5: Double Pa→m conversion for pressure setpoints

ControlService.get_mode() converted pressure setpoints from Pascals to metres before storing in SetpointInfo, but get_display_value() and get_limits_display() then divided by 9806.65 again, producing values ~10,000× too small. Extended the service-side conversion to all pressure modes and removed the redundant division from both model methods.

Changes

File Change
core/authentication.py Sequential extension packets, correct order (fix 1)
client.py Bleak 3.x adapter handling inlined; compat shim removed (fix 2)
services/telemetry.py Disconnection guard in read_once() (fix 3)
models.py SetpointInfo.control_mode type + double-conversion fix (fixes 4, 5)
services/control.py Extend Pa→m conversion to all pressure modes (fix 5)
pyproject.toml Bump bleak>=3.0.0
scripts/verify_hardware.py Hardware regression-test script (new)

Hardware verification

Tested on a physical ALPHA HWR running BLE firmware 2811431V06.00.01.00001 (the firmware reported in #24) using Bleak 3.0.2 and Python 3.14.5:

  • Authentication completes without disconnection
  • control_mode.name resolves to TEMPERATURE_RANGE_CONTROL
  • get_display_value() returns 36.00 °C
  • Telemetry flow_m3h and head_m are populated

Closes #24

eman added 4 commits May 12, 2026 14:51
MkDocs does not recognise 'version' as a valid top-level config key.
Running with --strict aborted the build with:

  WARNING - Config value 'version': Unrecognised configuration name: version
  Aborted with 1 configuration warnings in 'strict' mode!

Removing the key fixes the GitHub Pages workflow.
- Add _make_bleak_client() helper in client.py to handle the deprecated
  adapter= kwarg: on Bleak 3+ / Linux uses bluez={'adapter': ...};
  on other platforms the adapter concept is BlueZ-only and is omitted;
  falls back to adapter= for Bleak < 3
- Add connection checks in TelemetryService.read_once() before the
  flow/pressure and temperature queries so a mid-sequence pump
  disconnect returns the partial telemetry already collected instead
  of raising BleakError (reported in issue #24)
- Document Bleak 3.x support in pyproject.toml dependency comment
…hen EXTEND_2)

The extension packets were being sent in a TaskGroup (parallel), with
EXTEND_2 submitted first despite the protocol spec requiring EXTEND_1
first. On strict firmware (BLE V06.00.01, issue #24) this caused the
session to not be fully established, resulting in the pump disconnecting
~500ms after the first data query.

Changes:
- Send EXTEND_1 before EXTEND_2 (matches 02_authentication.md packet
  trace which is the empirical source of truth)
- Use sequential awaits with a 50ms gap between packets instead of
  concurrent TaskGroup tasks - matches the documented reference
  implementation timing
- Fix the pseudocode reference comment at the bottom of the module
  which had the order reversed
Copilot AI review requested due to automatic review settings May 15, 2026 15:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a BLE firmware-specific authentication regression (issue #24) by enforcing correct extension packet sequencing/timing, adds compatibility for Bleak 3.x’s adapter= deprecation, and makes telemetry reads more resilient to mid-sequence disconnects.

Changes:

  • Send authentication extension packets sequentially (EXTEND_1 → 50ms → EXTEND_2) instead of concurrently/out-of-order.
  • Add _make_bleak_client() to select BleakClient kwargs compatible with Bleak 2.x vs 3.x (adapter= vs bluez={...} on Linux).
  • Return partial telemetry from TelemetryService.read_once() when a disconnect is detected mid-read.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/alpha_hwr/core/authentication.py Enforces correct extension packet ordering/timing; updates embedded pseudocode accordingly.
src/alpha_hwr/client.py Introduces Bleak client factory for Bleak 3.x adapter= deprecation handling.
src/alpha_hwr/services/telemetry.py Adds connection-state guards to return partial telemetry instead of raising after disconnects.
pyproject.toml Notes Bleak 3.x support in dependency comment.
Comments suppressed due to low confidence (1)

src/alpha_hwr/services/telemetry.py:284

  • New behavior: if the pump disconnects before the temperature query, read_once() returns the partial telemetry rather than attempting the query. Please add a unit test covering this path (transport.is_connected() -> False) to ensure the early-return behavior is preserved.
        # 3. Query Temperatures (always poll)
        if not self.transport.is_connected():
            logger.debug("Pump disconnected before temperature query")
            return self._telemetry

Comment thread src/alpha_hwr/client.py Outdated
Comment thread src/alpha_hwr/client.py
Comment thread src/alpha_hwr/core/authentication.py
Comment thread src/alpha_hwr/core/authentication.py
Comment thread src/alpha_hwr/services/telemetry.py
eman and others added 4 commits May 15, 2026 08:49
- _make_bleak_client: wrap _pkg_version() in try/except and fall back
  to inspecting BleakClient.__init__ signature when importlib.metadata
  is unavailable (editable/vendored installs)
- send_extension_packets: add delay parameter (default 0.05s) so
  fast_mode=True correctly skips the intra-stage sleep; update
  authenticate() to pass delay=0 in fast_mode
- authenticate() docstring: fix stage-3 description from 'packets in
  parallel' to sequential order, and clarify fast_mode skips all delays
- Add tests/unit/core/test_authentication.py: verify EXTEND_1 before
  EXTEND_2, sleep between packets, no sleep in fast_mode
- Add tests/unit/core/test_make_bleak_client.py: verify Bleak 2/3 and
  Linux/non-Linux branching, PackageNotFoundError signature fallback
- Add tests/unit/core/test_telemetry_disconnect_guard.py: verify
  read_once() returns partial telemetry without raising when transport
  disconnects after motor-state or flow/pressure query
- Change SetpointInfo.control_mode field type from int to ControlMode | int
  and add a field_validator that coerces known raw int values to the
  ControlMode enum. Users can now call .name on the result for recognised
  modes; unknown values fall back to plain int.

- Pass self._current_mode (already coerced) to SetpointInfo in
  get_mode() instead of the raw local int.

- Fix double Pa->m conversion: control.py already converted
  CONSTANT_PRESSURE and PROPORTIONAL_PRESSURE from Pascals to metres
  before storing in SetpointInfo, but get_display_value() and
  get_limits_display() then divided by 9806.65 again for all pressure
  modes, producing values ~10,000x too small. Extended the service-side
  conversion to cover all pressure modes for consistency, and removed
  the redundant division from both model methods.

- Add scripts/verify_hardware.py: hardware regression-test script that
  connects via BLE, reads device info (firmware version), telemetry, and
  control mode, then prints a PASS/FAIL summary. Verified on device
  running BLE firmware V06.00.01.

Verified on hardware: firmware 2811431V06.00.01.00001, control mode
TEMPERATURE_RANGE_CONTROL reads correctly at 36.00 degC.
Drop support for bleak <3.x. The _make_bleak_client() version-detection
helper is replaced with a direct two-line conditional at the call site:
on Linux with an adapter specified, use bluez={'adapter': ...}; otherwise
use BleakClient(address) with no extra kwargs. Remove the inspect, and
importlib.metadata imports that were only needed for the shim.

Update pyproject.toml minimum bleak requirement from >=0.19.0 to >=3.0.0.

Remove test_make_bleak_client.py (tested the now-deleted helper).

Also remove 'strict firmware' language from send_extension_packets()
docstring. The sequential ordering with a 50ms gap is simply the correct
protocol behaviour, not a workaround for a particular firmware variant.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Comment thread scripts/verify_hardware.py Outdated
Comment thread scripts/verify_hardware.py Outdated
Comment thread src/alpha_hwr/models.py Outdated
Comment thread tests/unit/core/test_telemetry_disconnect_guard.py
Comment thread tests/unit/core/test_telemetry_disconnect_guard.py Outdated
Comment thread pyproject.toml
GitHub Copilot and others added 4 commits May 15, 2026 10:15
Ruff format / mypy:
- Fix mypy call-overload errors in _coerce_control_mode: replace
  int(v: object) calls with an isinstance guard, eliminating the
  need for type: ignore comments entirely.
- Reflow long lines in models.py and verify_hardware.py so ruff
  format --check passes.

PR review threads:
- Patch asyncio.sleep in test_telemetry_disconnect_guard.py so the
  five unit tests run in ~0ms instead of incurring real 50ms waits.
  Also shorten one assertion message that exceeded the 80-char limit.
- Handle PackageNotFoundError from importlib.metadata.version() in
  verify_hardware.py (_pkg_ver helper falls back to 'unknown').
- Remove stale reference to _make_bleak_client in script docstring.
- Add CHANGELOG.md Unreleased section documenting all fixes from this
  branch, including the breaking bleak>=3.0.0 minimum requirement.
- Add assert guards to narrow service attributes from `| None` to
  their concrete types after context manager entry
- Replace hasattr guard with isinstance(ControlMode) for proper type
  narrowing of control_mode.name access
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reading data fails on Alpha HWR

2 participants