walkingpad-controller 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/PKG-INFO +1 -1
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/pyproject.toml +1 -1
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/controller.py +30 -127
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/ftms.py +17 -103
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/tests/test_real_device.py +43 -20
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/.github/workflows/publish.yml +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/.gitignore +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/LICENSE +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/README.md +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/__init__.py +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/const.py +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/models.py +0 -0
- {walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/wilink.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: walkingpad-controller
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python library for controlling KingSmith WalkingPad treadmills over BLE (FTMS and legacy WiLink protocols)
|
|
5
5
|
Project-URL: Homepage, https://github.com/mcdax/walkingpad-controller
|
|
6
6
|
Project-URL: Repository, https://github.com/mcdax/walkingpad-controller
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "walkingpad-controller"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python library for controlling KingSmith WalkingPad treadmills over BLE (FTMS and legacy WiLink protocols)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/controller.py
RENAMED
|
@@ -13,8 +13,11 @@ Example usage:
|
|
|
13
13
|
controller = WalkingPadController(ble_device=device)
|
|
14
14
|
await controller.connect()
|
|
15
15
|
|
|
16
|
-
# Start at
|
|
17
|
-
await controller.start(
|
|
16
|
+
# Start the belt (runs at minimum speed)
|
|
17
|
+
await controller.start()
|
|
18
|
+
|
|
19
|
+
# Set desired speed via the speed slider / set_speed()
|
|
20
|
+
await controller.set_speed(3.0)
|
|
18
21
|
|
|
19
22
|
# Get status
|
|
20
23
|
print(controller.status)
|
|
@@ -40,7 +43,6 @@ from .const import (
|
|
|
40
43
|
MAX_CONNECT_RETRIES,
|
|
41
44
|
RETRY_DELAY_SECONDS,
|
|
42
45
|
WILINK_SERVICE_UUID,
|
|
43
|
-
BeltState,
|
|
44
46
|
OperatingMode,
|
|
45
47
|
ProtocolType,
|
|
46
48
|
)
|
|
@@ -76,15 +78,6 @@ class WalkingPadController:
|
|
|
76
78
|
self._status_callbacks: list[Callable[[TreadmillStatus], None]] = []
|
|
77
79
|
self._disconnect_callbacks: list[Callable[[], None]] = []
|
|
78
80
|
|
|
79
|
-
# Pending target speed for FTMS cold-start recovery.
|
|
80
|
-
# When a cold START_OR_RESUME causes a BLE drop, we store the desired
|
|
81
|
-
# speed here so it can be re-sent after reconnection.
|
|
82
|
-
self._pending_target_speed: float | None = None
|
|
83
|
-
self._pending_speed_task_active: bool = False
|
|
84
|
-
# Suppresses Layer 2 pending speed during an active start() call,
|
|
85
|
-
# since ftms.start() already handles the speed command internally.
|
|
86
|
-
self._start_in_progress: bool = False
|
|
87
|
-
|
|
88
81
|
# Eagerly detect protocol from BLE name
|
|
89
82
|
name_protocol = self._detect_protocol_from_name()
|
|
90
83
|
if name_protocol is not None:
|
|
@@ -172,73 +165,6 @@ class WalkingPadController:
|
|
|
172
165
|
except Exception:
|
|
173
166
|
_LOGGER.exception("Error in status callback")
|
|
174
167
|
|
|
175
|
-
# FTMS cold-start recovery: if the belt is running at min speed
|
|
176
|
-
# but we have a pending target speed, re-send it after a delay.
|
|
177
|
-
# IMPORTANT: We must NOT send SET_TARGET_SPEED too early after a
|
|
178
|
-
# reconnect — the BLE connection is fragile during motor startup.
|
|
179
|
-
# Also suppress during an active start() call to avoid duplicate
|
|
180
|
-
# speed commands (ftms.start() already handles this internally).
|
|
181
|
-
if (
|
|
182
|
-
self._pending_target_speed is not None
|
|
183
|
-
and self._ftms
|
|
184
|
-
and self._ftms.connected
|
|
185
|
-
and not self._start_in_progress
|
|
186
|
-
):
|
|
187
|
-
if status.speed > 0 and status.speed < self._pending_target_speed - 0.15:
|
|
188
|
-
# Only fire once — check if we already have a pending task
|
|
189
|
-
if not self._pending_speed_task_active:
|
|
190
|
-
pending = self._pending_target_speed
|
|
191
|
-
self._pending_target_speed = None
|
|
192
|
-
_LOGGER.info(
|
|
193
|
-
"Belt at %.1f km/h after reconnect, will apply "
|
|
194
|
-
"pending target %.1f km/h after stabilization delay",
|
|
195
|
-
status.speed,
|
|
196
|
-
pending,
|
|
197
|
-
)
|
|
198
|
-
self._pending_speed_task_active = True
|
|
199
|
-
asyncio.ensure_future(self._apply_pending_speed(pending))
|
|
200
|
-
elif abs(status.speed - self._pending_target_speed) <= 0.15:
|
|
201
|
-
_LOGGER.debug(
|
|
202
|
-
"Pending target speed %.1f reached, clearing",
|
|
203
|
-
self._pending_target_speed,
|
|
204
|
-
)
|
|
205
|
-
self._pending_target_speed = None
|
|
206
|
-
|
|
207
|
-
async def _apply_pending_speed(self, speed: float) -> None:
|
|
208
|
-
"""Apply a pending target speed after reconnection.
|
|
209
|
-
|
|
210
|
-
Waits for the BLE connection and motor to stabilize before sending
|
|
211
|
-
SET_TARGET_SPEED. This prevents the disconnect that occurs when
|
|
212
|
-
speed commands are sent too early after a cold start.
|
|
213
|
-
"""
|
|
214
|
-
stabilize_delay = 5.0
|
|
215
|
-
try:
|
|
216
|
-
_LOGGER.info(
|
|
217
|
-
"Waiting %.1fs for motor to stabilize before SET_TARGET_SPEED(%.1f)",
|
|
218
|
-
stabilize_delay,
|
|
219
|
-
speed,
|
|
220
|
-
)
|
|
221
|
-
await asyncio.sleep(stabilize_delay)
|
|
222
|
-
|
|
223
|
-
if self._ftms and self._ftms.connected:
|
|
224
|
-
_LOGGER.info("Re-sending SET_TARGET_SPEED(%.1f) after reconnect", speed)
|
|
225
|
-
result = await self._ftms.set_target_speed(speed)
|
|
226
|
-
if result:
|
|
227
|
-
_LOGGER.info("SET_TARGET_SPEED(%.1f) applied successfully", speed)
|
|
228
|
-
else:
|
|
229
|
-
_LOGGER.warning(
|
|
230
|
-
"SET_TARGET_SPEED(%.1f) failed after reconnect", speed
|
|
231
|
-
)
|
|
232
|
-
else:
|
|
233
|
-
_LOGGER.warning(
|
|
234
|
-
"Connection lost during stabilization, cannot apply speed %.1f",
|
|
235
|
-
speed,
|
|
236
|
-
)
|
|
237
|
-
except BleakError as err:
|
|
238
|
-
_LOGGER.warning("BLE error applying pending speed: %s", err)
|
|
239
|
-
finally:
|
|
240
|
-
self._pending_speed_task_active = False
|
|
241
|
-
|
|
242
168
|
def _on_disconnect(self) -> None:
|
|
243
169
|
"""Internal handler for disconnect events from either backend."""
|
|
244
170
|
_LOGGER.warning("Device disconnected")
|
|
@@ -379,49 +305,25 @@ class WalkingPadController:
|
|
|
379
305
|
|
|
380
306
|
# --- Commands ---
|
|
381
307
|
|
|
382
|
-
async def start(self
|
|
383
|
-
"""Start the treadmill.
|
|
384
|
-
|
|
385
|
-
For FTMS devices, this uses START_OR_RESUME (cold start) followed by
|
|
386
|
-
SET_TARGET_SPEED with retry logic. For WiLink devices, this sends the
|
|
387
|
-
standard start command.
|
|
308
|
+
async def start(self) -> bool:
|
|
309
|
+
"""Start the treadmill belt.
|
|
388
310
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
311
|
+
For FTMS devices, sends START_OR_RESUME and waits for the belt
|
|
312
|
+
to begin moving. Does NOT send SET_TARGET_SPEED — the user
|
|
313
|
+
must set speed explicitly via set_speed() (e.g. the HA speed
|
|
314
|
+
slider). Sending a speed command during motor spin-up crashes
|
|
315
|
+
the BLE connection on KingSmith firmware.
|
|
392
316
|
|
|
393
|
-
|
|
394
|
-
target_speed: Target speed in km/h. If None, uses min speed.
|
|
317
|
+
For WiLink devices, sends the standard start command.
|
|
395
318
|
|
|
396
319
|
Returns:
|
|
397
320
|
True if the belt is running. False if the connection was lost.
|
|
398
321
|
"""
|
|
399
322
|
if self._ftms:
|
|
400
|
-
|
|
401
|
-
if target_speed is not None and target_speed > self._ftms.min_speed:
|
|
402
|
-
self._pending_target_speed = target_speed
|
|
403
|
-
|
|
404
|
-
# Suppress Layer 2 pending speed during start() to avoid duplicate
|
|
405
|
-
# SET_TARGET_SPEED commands — ftms.start() handles it internally.
|
|
406
|
-
self._start_in_progress = True
|
|
407
|
-
try:
|
|
408
|
-
result = await self._ftms.start(target_speed=target_speed)
|
|
409
|
-
finally:
|
|
410
|
-
self._start_in_progress = False
|
|
411
|
-
|
|
412
|
-
if result and self._ftms.connected:
|
|
413
|
-
if target_speed and abs(self._ftms.status.speed - target_speed) > 0.15:
|
|
414
|
-
_LOGGER.info(
|
|
415
|
-
"start() completed but speed is %.1f (target %.1f), keeping pending",
|
|
416
|
-
self._ftms.status.speed,
|
|
417
|
-
target_speed,
|
|
418
|
-
)
|
|
419
|
-
else:
|
|
420
|
-
self._pending_target_speed = None
|
|
421
|
-
return result
|
|
323
|
+
return await self._ftms.start()
|
|
422
324
|
|
|
423
325
|
elif self._wilink:
|
|
424
|
-
return await self._wilink.start(
|
|
326
|
+
return await self._wilink.start()
|
|
425
327
|
|
|
426
328
|
_LOGGER.warning("No protocol backend available")
|
|
427
329
|
return False
|
|
@@ -432,8 +334,6 @@ class WalkingPadController:
|
|
|
432
334
|
Returns:
|
|
433
335
|
True if the command was sent successfully.
|
|
434
336
|
"""
|
|
435
|
-
self._pending_target_speed = None
|
|
436
|
-
|
|
437
337
|
if self._ftms:
|
|
438
338
|
return await self._ftms.stop()
|
|
439
339
|
elif self._wilink:
|
|
@@ -445,7 +345,9 @@ class WalkingPadController:
|
|
|
445
345
|
async def set_speed(self, speed_kmh: float) -> bool:
|
|
446
346
|
"""Set the treadmill speed.
|
|
447
347
|
|
|
448
|
-
If the belt is
|
|
348
|
+
If the belt is already running, sends SET_TARGET_SPEED directly.
|
|
349
|
+
If the belt is stopped, starts it first (the belt will run at
|
|
350
|
+
minimum speed until the user adjusts the speed slider).
|
|
449
351
|
|
|
450
352
|
Args:
|
|
451
353
|
speed_kmh: Target speed in km/h.
|
|
@@ -455,19 +357,20 @@ class WalkingPadController:
|
|
|
455
357
|
"""
|
|
456
358
|
if self._ftms:
|
|
457
359
|
if self._ftms.status.speed > 0:
|
|
360
|
+
# Belt already running — safe to send speed directly
|
|
458
361
|
return await self._ftms.set_target_speed(speed_kmh)
|
|
459
362
|
else:
|
|
460
|
-
# Belt is stopped
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return
|
|
363
|
+
# Belt is stopped — start it first. The user will need
|
|
364
|
+
# to set the desired speed once the belt is running.
|
|
365
|
+
_LOGGER.info(
|
|
366
|
+
"Belt is stopped — starting first, then setting speed %.1f",
|
|
367
|
+
speed_kmh,
|
|
368
|
+
)
|
|
369
|
+
started = await self._ftms.start()
|
|
370
|
+
if not started:
|
|
371
|
+
return False
|
|
372
|
+
# Belt is now running; send the speed command
|
|
373
|
+
return await self._ftms.set_target_speed(speed_kmh)
|
|
471
374
|
|
|
472
375
|
elif self._wilink:
|
|
473
376
|
return await self._wilink.set_target_speed(speed_kmh)
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/ftms.py
RENAMED
|
@@ -476,25 +476,18 @@ class FTMSController:
|
|
|
476
476
|
_LOGGER.warning("FTMS: Failed to acquire control")
|
|
477
477
|
return result
|
|
478
478
|
|
|
479
|
-
async def start(self
|
|
480
|
-
"""Start or resume the treadmill.
|
|
479
|
+
async def start(self) -> bool:
|
|
480
|
+
"""Start or resume the treadmill belt.
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
waits for the belt to actually start moving (speed > 0 reported via
|
|
488
|
-
treadmill data notifications) before sending any speed command.
|
|
489
|
-
|
|
490
|
-
Args:
|
|
491
|
-
target_speed: Target speed in km/h. If None, uses min speed.
|
|
482
|
+
Sends START_OR_RESUME and, on a cold start, waits for the belt to
|
|
483
|
+
report speed > 0. Does NOT send SET_TARGET_SPEED — the user sets
|
|
484
|
+
the speed explicitly via set_target_speed() (e.g. the HA speed
|
|
485
|
+
slider). Sending a speed command during motor spin-up crashes the
|
|
486
|
+
BLE connection on KingSmith firmware.
|
|
492
487
|
|
|
493
488
|
Returns:
|
|
494
489
|
True if the belt is running. False if the connection was lost.
|
|
495
490
|
"""
|
|
496
|
-
speed = target_speed if target_speed is not None else self.min_speed
|
|
497
|
-
|
|
498
491
|
if not self._has_control:
|
|
499
492
|
await self._request_control()
|
|
500
493
|
|
|
@@ -502,15 +495,12 @@ class FTMSController:
|
|
|
502
495
|
if cold_start:
|
|
503
496
|
_LOGGER.info("FTMS: START_OR_RESUME succeeded (cold start)")
|
|
504
497
|
else:
|
|
505
|
-
_LOGGER.debug("FTMS: START_OR_RESUME
|
|
498
|
+
_LOGGER.debug("FTMS: START_OR_RESUME not needed (belt already running)")
|
|
506
499
|
|
|
507
500
|
if not self.connected:
|
|
508
501
|
_LOGGER.warning("FTMS: Connection lost after START_OR_RESUME")
|
|
509
502
|
return False
|
|
510
503
|
|
|
511
|
-
# After a cold start, wait for the belt to actually start moving before
|
|
512
|
-
# sending SET_TARGET_SPEED. Sending speed commands during motor startup
|
|
513
|
-
# destabilizes the BLE connection on KingSmith devices (tested on KS-Z1D).
|
|
514
504
|
if cold_start:
|
|
515
505
|
belt_running = await self._wait_for_belt_moving(timeout=15.0)
|
|
516
506
|
if not belt_running:
|
|
@@ -519,76 +509,24 @@ class FTMSController:
|
|
|
519
509
|
return False
|
|
520
510
|
_LOGGER.warning("FTMS: Belt did not start moving within timeout")
|
|
521
511
|
return False
|
|
522
|
-
|
|
523
|
-
if not self.connected:
|
|
524
|
-
_LOGGER.warning("FTMS: Connection lost before SET_TARGET_SPEED")
|
|
525
|
-
return False
|
|
526
|
-
|
|
527
|
-
# Now it's safe to set the target speed. After a cold start the device
|
|
528
|
-
# may still need a moment to accept speed changes, so retry if needed.
|
|
529
|
-
max_attempts = 3 if cold_start else 1
|
|
530
|
-
for attempt in range(max_attempts):
|
|
531
|
-
if attempt > 0:
|
|
532
|
-
await asyncio.sleep(2.0)
|
|
533
|
-
if not self.connected:
|
|
534
|
-
return False
|
|
535
|
-
|
|
536
|
-
result = await self.set_target_speed(speed)
|
|
537
|
-
if not result:
|
|
538
|
-
_LOGGER.warning(
|
|
539
|
-
"FTMS: SET_TARGET_SPEED(%.1f) failed on attempt %d/%d",
|
|
540
|
-
speed,
|
|
541
|
-
attempt + 1,
|
|
542
|
-
max_attempts,
|
|
543
|
-
)
|
|
544
|
-
continue
|
|
545
|
-
|
|
546
|
-
# For min speed, no need to verify (belt already runs at min)
|
|
547
|
-
if speed <= self.min_speed + 0.05:
|
|
548
|
-
return True
|
|
549
|
-
|
|
550
|
-
# Verify the speed actually changed
|
|
551
|
-
verified = await self._verify_speed(speed, timeout=5.0)
|
|
552
|
-
if verified:
|
|
553
|
-
_LOGGER.info(
|
|
554
|
-
"FTMS: Speed verified at %.1f km/h (attempt %d/%d)",
|
|
555
|
-
speed,
|
|
556
|
-
attempt + 1,
|
|
557
|
-
max_attempts,
|
|
558
|
-
)
|
|
559
|
-
return True
|
|
560
|
-
|
|
561
512
|
_LOGGER.info(
|
|
562
|
-
"FTMS:
|
|
513
|
+
"FTMS: Cold start complete — belt running at %.1f km/h",
|
|
563
514
|
self._status.speed,
|
|
564
|
-
attempt + 1,
|
|
565
|
-
max_attempts,
|
|
566
|
-
speed,
|
|
567
515
|
)
|
|
568
516
|
|
|
569
|
-
|
|
570
|
-
"FTMS: Could not reach %.1f km/h after %d attempts (current: %.2f)",
|
|
571
|
-
speed,
|
|
572
|
-
max_attempts,
|
|
573
|
-
self._status.speed,
|
|
574
|
-
)
|
|
575
|
-
return True # Belt IS running, just not at desired speed
|
|
517
|
+
return True
|
|
576
518
|
|
|
577
|
-
async def _wait_for_belt_moving(
|
|
578
|
-
|
|
579
|
-
) -> bool:
|
|
580
|
-
"""Wait for the belt to start and stabilize before sending speed commands.
|
|
519
|
+
async def _wait_for_belt_moving(self, timeout: float = 15.0) -> bool:
|
|
520
|
+
"""Wait for the belt to report speed > 0 after a cold start.
|
|
581
521
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
2. An additional stabilization period after speed is first reported.
|
|
522
|
+
Polls treadmill-data notifications until the belt is physically
|
|
523
|
+
moving. No stabilisation delay is applied here — we deliberately
|
|
524
|
+
avoid sending any speed command while the connection is fragile.
|
|
586
525
|
|
|
587
526
|
Args:
|
|
588
527
|
timeout: Maximum time to wait for speed > 0 (seconds).
|
|
589
|
-
stabilize: Additional delay after speed detected (seconds).
|
|
590
528
|
|
|
591
|
-
Returns True if the belt
|
|
529
|
+
Returns True if the belt is moving, False on timeout/disconnect.
|
|
592
530
|
"""
|
|
593
531
|
_LOGGER.debug(
|
|
594
532
|
"FTMS: Waiting for belt to start moving (timeout=%.0fs)...", timeout
|
|
@@ -600,38 +538,14 @@ class FTMSController:
|
|
|
600
538
|
if self._status.speed > 0:
|
|
601
539
|
wait_elapsed = timeout - (deadline - time.time())
|
|
602
540
|
_LOGGER.info(
|
|
603
|
-
"FTMS: Belt moving at %.1f km/h (waited %.1fs),
|
|
604
|
-
"stabilizing %.1fs before speed command...",
|
|
541
|
+
"FTMS: Belt moving at %.1f km/h (waited %.1fs)",
|
|
605
542
|
self._status.speed,
|
|
606
543
|
wait_elapsed,
|
|
607
|
-
stabilize,
|
|
608
|
-
)
|
|
609
|
-
# Additional stabilization delay — the motor/firmware needs
|
|
610
|
-
# time to settle before it can handle SET_TARGET_SPEED
|
|
611
|
-
# without dropping the BLE connection.
|
|
612
|
-
await asyncio.sleep(stabilize)
|
|
613
|
-
if not self.connected:
|
|
614
|
-
return False
|
|
615
|
-
_LOGGER.info(
|
|
616
|
-
"FTMS: Stabilization complete (belt at %.1f km/h)",
|
|
617
|
-
self._status.speed,
|
|
618
544
|
)
|
|
619
545
|
return True
|
|
620
546
|
await asyncio.sleep(0.5)
|
|
621
547
|
return False
|
|
622
548
|
|
|
623
|
-
async def _verify_speed(self, target: float, timeout: float = 3.0) -> bool:
|
|
624
|
-
"""Wait for the reported speed to match the target."""
|
|
625
|
-
tolerance = max(self._capabilities.speed_range.increment, 0.15)
|
|
626
|
-
deadline = time.time() + timeout
|
|
627
|
-
while time.time() < deadline:
|
|
628
|
-
if not self.connected:
|
|
629
|
-
return False
|
|
630
|
-
if abs(self._status.speed - target) <= tolerance:
|
|
631
|
-
return True
|
|
632
|
-
await asyncio.sleep(0.5)
|
|
633
|
-
return False
|
|
634
|
-
|
|
635
549
|
async def stop(self) -> bool:
|
|
636
550
|
"""Stop the treadmill."""
|
|
637
551
|
if not self._has_control:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Test walkingpad-controller library against real KS-HD-Z1D device.
|
|
2
2
|
|
|
3
|
-
Tests
|
|
3
|
+
Tests the cold-start flow WITHOUT automatic SET_TARGET_SPEED:
|
|
4
|
+
scan -> connect -> start (no speed) -> observe belt at min speed ->
|
|
5
|
+
set_speed (user action) -> observe -> stop -> disconnect
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
8
|
import asyncio
|
|
@@ -18,8 +20,8 @@ logging.basicConfig(
|
|
|
18
20
|
_LOGGER = logging.getLogger("test")
|
|
19
21
|
|
|
20
22
|
DEVICE_NAME = "KS-HD-Z1D"
|
|
21
|
-
TARGET_SPEED = 2.0 # km/h
|
|
22
|
-
RUN_DURATION =
|
|
23
|
+
TARGET_SPEED = 2.0 # km/h — sent AFTER belt is running (simulates user slider)
|
|
24
|
+
RUN_DURATION = 20 # seconds to observe after setting speed
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
async def main():
|
|
@@ -90,27 +92,48 @@ async def main():
|
|
|
90
92
|
s.steps,
|
|
91
93
|
)
|
|
92
94
|
|
|
93
|
-
# --- Step 5: Start
|
|
94
|
-
_LOGGER.info("Starting
|
|
95
|
-
result = await controller.start(
|
|
95
|
+
# --- Step 5: Start (no target speed — belt runs at minimum) ---
|
|
96
|
+
_LOGGER.info("Starting belt (no target speed)...")
|
|
97
|
+
result = await controller.start()
|
|
96
98
|
_LOGGER.info("start() returned: %s", result)
|
|
97
99
|
|
|
98
100
|
if not controller.connected:
|
|
99
|
-
_LOGGER.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
_LOGGER.error("Connection lost during start! TEST FAILED.")
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
s = controller.status
|
|
105
|
+
_LOGGER.info(
|
|
106
|
+
"After start: speed=%.2f km/h (should be ~%.1f min speed)",
|
|
107
|
+
s.speed,
|
|
108
|
+
controller.min_speed,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# --- Step 6: Wait a moment, then set speed (simulates user slider) ---
|
|
112
|
+
_LOGGER.info(
|
|
113
|
+
"Belt running at min speed. Waiting 5s before setting speed to %.1f...",
|
|
114
|
+
TARGET_SPEED,
|
|
115
|
+
)
|
|
116
|
+
await asyncio.sleep(5)
|
|
117
|
+
|
|
118
|
+
if not controller.connected:
|
|
119
|
+
_LOGGER.error("Connection lost while waiting! TEST FAILED.")
|
|
120
|
+
return
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
_LOGGER.info("Setting speed to %.1f km/h (simulates user slider)...", TARGET_SPEED)
|
|
123
|
+
result = await controller.set_speed(TARGET_SPEED)
|
|
124
|
+
_LOGGER.info("set_speed() returned: %s", result)
|
|
125
|
+
|
|
126
|
+
if not controller.connected:
|
|
127
|
+
_LOGGER.error("Connection lost after set_speed! TEST FAILED.")
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
# --- Step 7: Run and observe ---
|
|
131
|
+
_LOGGER.info("Observing for %ds...", RUN_DURATION)
|
|
109
132
|
start_time = time.time()
|
|
110
133
|
while time.time() - start_time < RUN_DURATION:
|
|
111
134
|
if disconnected.is_set():
|
|
112
|
-
_LOGGER.
|
|
113
|
-
|
|
135
|
+
_LOGGER.error("Disconnected during run! TEST FAILED.")
|
|
136
|
+
return
|
|
114
137
|
await asyncio.sleep(1)
|
|
115
138
|
|
|
116
139
|
s = controller.status
|
|
@@ -123,7 +146,7 @@ async def main():
|
|
|
123
146
|
s.steps,
|
|
124
147
|
)
|
|
125
148
|
|
|
126
|
-
# --- Step
|
|
149
|
+
# --- Step 8: Stop ---
|
|
127
150
|
if controller.connected:
|
|
128
151
|
_LOGGER.info("Stopping belt...")
|
|
129
152
|
result = await controller.stop()
|
|
@@ -132,11 +155,11 @@ async def main():
|
|
|
132
155
|
s = controller.status
|
|
133
156
|
_LOGGER.info("After stop: speed=%.2f, belt=%s", s.speed, s.belt_state)
|
|
134
157
|
|
|
135
|
-
# --- Step
|
|
158
|
+
# --- Step 9: Disconnect ---
|
|
136
159
|
_LOGGER.info("Disconnecting...")
|
|
137
160
|
await controller.disconnect()
|
|
138
161
|
_LOGGER.info("Disconnected. Total status updates received: %d", status_count)
|
|
139
|
-
_LOGGER.info("TEST PASSED!")
|
|
162
|
+
_LOGGER.info("TEST PASSED — no BLE disconnect during cold start!")
|
|
140
163
|
|
|
141
164
|
|
|
142
165
|
if __name__ == "__main__":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/__init__.py
RENAMED
|
File without changes
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/const.py
RENAMED
|
File without changes
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/models.py
RENAMED
|
File without changes
|
{walkingpad_controller-0.2.0 → walkingpad_controller-0.3.0}/src/walkingpad_controller/wilink.py
RENAMED
|
File without changes
|