Skip to content

Releases: mcdax/walkingpad-controller

v0.5.1

Choose a tag to compare

@mcdax mcdax released this 16 Aug 06:46

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

Choose a tag to compare

@mcdax mcdax released this 16 Aug 06:33

Adds Sperax P3 Max (wi-linktech WLT6200) protocol support: speed, start/stop/pause, incline 0-10, vibration, and status decoding (distance, duration, steps). Also tidies up the source distribution packaging.

v0.4.11

Choose a tag to compare

@mcdax mcdax released this 10 May 09:46

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

Choose a tag to compare

@mcdax mcdax released this 10 May 09:11

Fixed

  • WiLink: surface unilateral disconnects. WiLinkController now 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. The connected property also consults the live client state. Without this, _disconnect_callbacks were never fired on legacy devices and _connected stayed True forever after a real drop.
  • WalkingPadController.disconnect(): don't skip teardown on stale cache. Previously gated the entire disconnect on the cached _connected bool; 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_speed docstring trimmed of stale "the user will need to set the desired speed once the belt is running" comment — the code already issues set_target_speed after start().

Docs

  • README clarifies that the vendor pre-amble is sent for any device exposing the d18d2c10-… characteristic (not MC-21-specific).

v0.4.9

Choose a tag to compare

@mcdax mcdax released this 10 May 09:03

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).

  1. _paused deceleration race in _on_treadmill_data. The previous version cleared _paused on every Treadmill Data frame with speed > 0. Pause sets the flag at speed ≈ 2.5; the ~5–10 s deceleration to zero emits dozens of speed > 0 frames, each clearing the flag, so by the time speed reached 0 the _paused branch was dead and belt_state derived as STOPPED. The flag is now set/cleared exclusively by _on_machine_status in response to FM Status events; _on_treadmill_data only reads it.

  2. "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_event on 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 Status STOPPED_OR_PAUSED + PAUSE arrives as the first event. The ack waiter got woken (so pause() returned True), but _paused was never set, and belt_state derived 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

Choose a tag to compare

@mcdax mcdax released this 10 May 07:45

Added

  • BeltState.PAUSED (= 2) on the public enum. Distinguishes "session paused, will resume" from "session ended" — the device emits FM Status STOPPED_OR_PAUSED with the PAUSE param (0x02) when the user pauses (via pause() 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.

    FTMSController now 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
    

    _paused is 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

Choose a tag to compare

@mcdax mcdax released this 10 May 07:10

Added

  • WalkingPadController.pause() — exposes the existing FTMS pause path (STOP_OR_PAUSE opcode 0x08 with PAUSE = 0x02) on the unified controller. Pause leaves the session counters (time, distance, calories, steps) intact, so a subsequent start() 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() sends STOP = 0x01 which 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 to stop().

Refs walkingpad-controller#2.

v0.4.6

Choose a tag to compare

@mcdax mcdax released this 06 May 20:10

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

Choose a tag to compare

@mcdax mcdax released this 05 May 09:59

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

Choose a tag to compare

@mcdax mcdax released this 04 May 20:57

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.