Releases: mcdax/walkingpad-controller
Release list
v0.5.1
Fixes protocol detection for Sperax-branded pads that speak FTMS. The name match for the Sperax vendor protocol is now limited to the P3 Max (SPERAX_P3MAX) instead of any SPERAX_ name, so devices like the Sperax RM-01 are correctly handled as FTMS again.
v0.5.0
v0.4.11
Fixed
- Use `bleak_retry_connector.establish_connection()` for FTMS connect. Replaces the hand-rolled `BleakClient(...).connect()` calls in both `FTMSController.connect()` and `_detect_protocol_from_probe()`. The retry-connector is HA-tuned: it handles ESPHome BT-proxy slot management, scanner re-discovery, and the `ESP_GATT_CONN_FAIL_ESTABLISH` failure mode KingSmith treadmills hit at marginal RSSI through proxies. Addresses the `BleakClient.connect() called without bleak-retry-connector` warning Bleak has been printing.
Added
- `bleak-retry-connector>=3.0.0` as a runtime dependency (already pre-installed inside Home Assistant; standalone users pull it from PyPI).
v0.4.10
Fixed
- WiLink: surface unilateral disconnects.
WiLinkControllernow hooks ph4_walkingpad's BleakClient disconnect callback, so a unilateral BLE drop (range loss, firmware reboot, phone-app stealing the link) propagates to upper layers. Theconnectedproperty also consults the live client state. Without this,_disconnect_callbackswere never fired on legacy devices and_connectedstayedTrueforever after a real drop. WalkingPadController.disconnect(): don't skip teardown on stale cache. Previously gated the entire disconnect on the cached_connectedbool; now also checks backend liveness so a real Bleak client can still be closed when the firmware unilaterally dropped the link (which flipped our cache to False before disconnect could run).set_speeddocstring trimmed of stale "the user will need to set the desired speed once the belt is running" comment — the code already issuesset_target_speedafterstart().
Docs
- README clarifies that the vendor pre-amble is sent for any device exposing the
d18d2c10-…characteristic (not MC-21-specific).
v0.4.9
Fixed
Two regressions where belt_state derived as STOPPED after a successful pause instead of PAUSED. Both were found by running tests/stress_dynamic.py against a real KS-HD-Z1D and verified by the pause_resume scenario invariants (16/16 OK after the fixes; ~50% before).
-
_pauseddeceleration race in_on_treadmill_data. The previous version cleared_pausedon every Treadmill Data frame withspeed > 0. Pause sets the flag at speed ≈ 2.5; the ~5–10 s deceleration to zero emits dozens ofspeed > 0frames, each clearing the flag, so by the time speed reached 0 the_pausedbranch was dead and belt_state derived as STOPPED. The flag is now set/cleared exclusively by_on_machine_statusin response to FM Status events;_on_treadmill_dataonly reads it. -
"First 2ADA event after subscribe" filter dropped genuine ack state. The defensive filter that drops the first 2ADA event (added because the firmware replays its prior state on CCCD-subscribe, which would flap
last_fm_eventon every reconnect) over-filtered when the user's first command after a fresh connect produced an ack as the first event — e.g.pause()right after a reconnect → FM StatusSTOPPED_OR_PAUSED + PAUSEarrives as the first event. The ack waiter got woken (sopause()returned True), but_pausedwas never set, andbelt_statederived as STOPPED. Heuristic: if the first event matches_status_ack_expected_opcode, treat as the real ack and apply state. Otherwise skip as before.
Refs walkingpad-controller#2.
v0.4.8
Added
-
BeltState.PAUSED(= 2) on the public enum. Distinguishes "session paused, will resume" from "session ended" — the device emits FM StatusSTOPPED_OR_PAUSEDwith the PAUSE param (0x02) when the user pauses (viapause()from 0.4.7, the phone app, or the physical remote), and counters (time, distance, calories, steps) survive across the pause. Previously the library reported `STOPPED` in both cases, so callers couldn't tell them apart.FTMSControllernow tracks an internal `_paused` flag set by the FM Status handler and consulted by the Treadmill Data parser, with this derivation:speed > 0 -> ACTIVE (also clears _paused) speed = 0 + paused -> PAUSED speed = 0 + ~paused -> STOPPED_pausedis cleared on STARTED_OR_RESUMED, STOPPED_OR_PAUSED+STOP, safety-key stop, every speed > 0 frame, and on disconnect.
Refs walkingpad-controller#2.
v0.4.7
Added
-
WalkingPadController.pause()— exposes the existing FTMS pause path (STOP_OR_PAUSEopcode0x08withPAUSE = 0x02) on the unified controller. Pause leaves the session counters (time, distance, calories, steps) intact, so a subsequentstart()resumes from where the user left off — same behaviour as the phone app's stop button and the physical remote.This matters for UI semantics: a "Stop" button in HA / a phone app should usually pause, not full-stop. The existing
stop()sendsSTOP = 0x01which fully ends the session and resets counters; that path is still available for the rare cases where a hard reset is wanted.WiLink protocol has no separate pause opcode; on those devices
pause()logs a warning and falls back tostop().
Refs walkingpad-controller#2.
v0.4.6
Fixed
- Connection stability on marginal-signal hardware. Removed
Training Status (0x2AD3)from the default connect-path subscriptions. On a KS-HD-Z1D at -83 dBm RSSI:- Before: 0/5 reps held a 20 s idle window — the link dropped within 2-3 s of `connect()` returning.
- After: 4/5 reps held the full 20 s.
- Command flow: unchanged (12/12 ok in stop_start scenario).
The bisection (in `tests/probe_setup_killer.py`) showed extra CCCD writes during connect destabilise the link on weak signals. Pure GATT reads and the load-bearing `REQUEST_CONTROL` write are kept (the latter is required — KingSmith firmware silently rejects subsequent `SET_TARGET_SPEED` / `STOP` if `REQUEST_CONTROL` never happened, even though it ignores its own response).
Training Status was informational only (PRE_WORKOUT / MANUAL / etc); the library does not gate any decision on it. Callers that want it can subscribe lazily.
v0.4.5
Fixed
- Stale 2ADA event on every reconnect. KingSmith firmware (KS-HD-Z1D verified, MC-21 likely the same) replays its current Fitness Machine Status the moment the CCCD is enabled — typically a leftover `STOPPED_OR_PAUSED` from before the client connected. The library now skips the very first 2ADA event after every subscribe, so `TreadmillStatus.last_fm_event` no longer flips to a misleading historical value within a second of every connect. The flag re-arms on disconnect, and pending command waiters still get woken if the skipped event matches the expected opcode (so command acknowledgement isn't lost).
v0.4.4
Added
Surface two new fields on TreadmillStatus so consumers can react to FTMS state-change events the library already subscribes to:
training_status— byte 1 of the 0x2AD3 notification (Bluetooth SIG FTMS standard enum). New `FTMSTrainingStatus` enum mirrors the 15 standard values (Idle / Warming Up / High-Intensity Interval / … / Manual Mode / Pre-Workout / Post-Workout).last_fm_event— opcode of the most recent 0x2ADA notification. Maps to `FitnessMachineStatusOpcode`.
The status callback now fires whenever either changes, so downstream consumers (e.g. Home Assistant) get pushed updates the moment the device reports a state transition.