Giter Club home page Giter Club logo

ph4-walkingpad's Introduction

WalkingPad controller

Simple python script that can control KingSmith WalkingPad A1. Others report the similar models, such as R1 PRO work on the same principle.

The belt communicates via Bluetooth LE GATT. Only one device can be connected to the belt at a time, i.e., if original app is connected, the controller won't be able to connect.

Controller features

  • Switch mode: Standby / Manual / Automatic
  • Start belt, stop belt
  • Change belt speed (0.5 - 6.0), all options work, e.g. 1.2 not originally usable with the native interface (permits only 0.5 step)
  • Change preferences of the belt
    • Max speed
    • Start speed
    • start type (intelli)
    • Sensitivity in automatic mode
    • Display
    • Child lock
    • Units (miles/km)
    • Target (time, distance, calories, steps)
  • Ask for current state (speed, time, distance, steps)
  • Ask for last stored state in the WalkingPad

Demo

For the best understanding start jupyter-notebook and take a look at belt_control.ipynb

# Install jupyter-notebook
pip3 install jupyter

# Start jupyter-notebook in this repository
jupyter-notebook .

# open belt_control.ipynb

Library use-case

The main controller class is Controller in pad.py

Controller

Controller enables to control the belt via CLI shell.

Install the library:

pip install -U ph4-walkingpad

Start controller:

# Note: use module notation to run the script, no direct script invocation.
python -m ph4_walkingpad.main --stats 750 --json-file ~/walking.json

Or alternatively, if package was installed with pip:

ph4-walkingpad-ctl --stats 750 --json-file ~/walking.json

The command asks for periodic statistics fetching at 750 ms, storing records to ~/walking.json.

Output

---------------------------------------------------------------------------
    WalkingPad controller

---------------------------------------------------------------------------
$> help

Documented commands (use 'help -v' for verbose/'help <topic>' for details):
===========================================================================
alias      help     py  quit          set        speed   stop       
ask_stats  history  Q   run_pyscript  shell      start   switch_mode
edit       macro    q   run_script    shortcuts  status  tasks    

$> status
WalkingPadCurStatus(dist=0.0, time=0, steps=0, speed=0.0, state=5, mode=2, app_speed=0.06666666666666667, button=2, rest=0000)
$> start
$> speed 30
$> speed 15
$> status
WalkingPadCurStatus(dist=0.01, time=16, steps=18, speed=1.8, state=1, mode=1, app_speed=1.5, button=1, rest=0000)
$> status
WalkingPadCurStatus(dist=0.01, time=17, steps=20, speed=1.5, state=1, mode=1, app_speed=1.5, button=1, rest=0000)
$> speed 30
$> s
$> WalkingPadCurStatus(dist=0.98, time=670, steps=1195, speed=6.0, state=1, mode=1, app_speed=6.0, button=1, rest=0000), cal:  38.73, net:  30.89, total:  73.65, total net:  57.91      
$> stop
$> start
$> speed 30
$> status

Due to nature of the BluetoothLE callbacks being executed on the main thread we cannot use readline to read from the console, so the shell CLI does not support auto-complete, ctrl-r, up-arrow for the last command, etc. Readline does not have async support at the moment.

OSX Troubleshooting

This project uses Bleak Bluetooth library. It was reported that OSX 12+ changed Bluetooth scanning logic, so it is not possible to connect to a device without scanning Bluetooth first. Moreover, it blocks for the whole timeout interval.

Thus, when using on OSX 12+:

  • do not use -a parameter
  • if there are more WalkingPads scanned, use --filter and specify device address prefix
  • to modify scanning timeout value use --scan-timeout

Minimal required version of Bleak is 0.14.1

If the process is still crashing, it may be it does not have permissions to access Bluetooth. To fix it, add your Terminal app (in my case iTerm2.app) to System Preferences -> Security & Privacy -> Bluetooth.

Related resources: hbldh/bleak#635, hbldh/bleak#692

Profile

If the -p profile.json argument is passed, profile of the person is loaded from the file, so the controller can count burned calories. Units are in a metric system.

{
  "id": "user1",
  "male": true,
  "age": 25,
  "weight": 80,
  "height": 1.80,
  "token": "JWT-token", 
  "did": "ff:ff:ff:ff:ff:ff",
  "email": "[email protected]",
  "password": "service-login-password",
  "password_md5": "or md5hash of password, hexcoded, to avoid plaintext password in config"
}
  • did is optional field, associates your records with pad MAC address when uploading to the service
  • email and (password or password_md5) are optional. If filled, you can call login to generate a fresh JWT usable for service auth.

Note that once you use login command, other JWTs become invalid, e.g., on your phone. If you want to use the service on both devices, login with mobile phone while logging output with adb and capture JWT from logs (works only for Android phones).

Stats file

The following arguments enable data collection to a statistic file:

--stats 750 --json-file ~/walking.json

In order to guarantee file consistency the format is one JSON record per file, so it is easy to append to a file at any time without need to read and rewrite it with each update (helps to prevent a data loss in cause of a crash).

Example:

{"time": 554, "dist": 79, "steps": 977, "speed": 60, "app_speed": 180, "belt_state": 1, "controller_button": 0, "manual_mode": 1, "raw": "f8a2013c0100022a00004f0003d1b4000000e3fd", "rec_time": 1615644982.5917802, "pid": "ph4r05", "ccal": 23.343, "ccal_net": 18.616, "ccal_sum": 58.267, "ccal_net_sum": 45.644}
{"time": 554, "dist": 79, "steps": 978, "speed": 60, "app_speed": 180, "belt_state": 1, "controller_button": 0, "manual_mode": 1, "raw": "f8a2013c0100022a00004f0003d2b4000000e4fd", "rec_time": 1615644983.345463, "pid": "ph4r05", "ccal": 23.343, "ccal_net": 18.616, "ccal_sum": 58.267, "ccal_net_sum": 45.644}
{"time": 555, "dist": 79, "steps": 980, "speed": 60, "app_speed": 180, "belt_state": 1, "controller_button": 0, "manual_mode": 1, "raw": "f8a2013c0100022b00004f0003d4b4000000e7fd", "rec_time": 1615644984.0991402, "pid": "ph4r05", "ccal": 23.476, "ccal_net": 18.722, "ccal_sum": 58.4, "ccal_net_sum": 45.749}
{"time": 556, "dist": 79, "steps": 981, "speed": 60, "app_speed": 180, "belt_state": 1, "controller_button": 0, "manual_mode": 1, "raw": "f8a2013c0100022c00004f0003d5b4000000e9fd", "rec_time": 1615644984.864169, "pid": "ph4r05", "ccal": 23.608, "ccal_net": 18.828, "ccal_sum": 58.533, "ccal_net_sum": 45.855}
{"time": 557, "dist": 80, "steps": 982, "speed": 60, "app_speed": 180, "belt_state": 1, "controller_button": 0, "manual_mode": 1, "raw": "f8a2013c0100022d0000500003d6b4000000ecfd", "rec_time": 1615644985.606997, "pid": "ph4r05", "ccal": 23.741, "ccal_net": 18.933, "ccal_sum": 58.665, "ccal_net_sum": 45.961}

The benefit of having detailed data is an option to analyze data from the whole run, e.g., how step size varies over the time during one session, collect preferred speeds, etc...

Also, if the original app fails to fetch the final state from the Belt, having continuous data stream is helpful to avoid data loss.

Reversing Belt API

Easy way - Android logs

I used the easiest way I found - the original Android application is quite generously logging all Bluetooth requests and responses; and network requests and responses (JWT included).

After few trial/error attempts I managed to reverse binary packet protocol format. See pad.py for protocol internals.

You can query from the belt a status message (app does so each 750 ms, approx). The status contains speed, distance, steps, and very simple CRC code (sum of the payload). Interestingly, calories are not part of the status message and cannot be queried either.

For obtaining logs just plug Android phone via USB, enable development mode on the phone, enable ADB connection and run:

adb logcat

(Or use AndroidStudio)

You then can see the app communication with the belt in real-time. When using the app, it logs also requests so you can figure out how commands for e.g., speed change look like.

Medium - Bluetooth logs

Should vendor remove the logging from the app and you are unable to find APK in archives with the logging, you can always enable Bluetooth logs in the Phone development settings.

This approach is not that straightforward as from logs as you cannot see belt responses in real-time. The Bluetooth log can be obtained from the device via adb and opened in Wireshark.

You may need to do own journal with times and commands you issued so you can experiment with the belt (e.g., change speeds), the commands get logged to the Bluetooth log. Then after the experiment, download the Bluetooth log and map your log entries to the packets from the log.

This is substantially difficult compared to the easy way - message logs.

Hard way - Flutter disassembly

The original application is implemented in Flutter, so direct application reversing is quite painful process. Flutter compiles the source language (TypeScript I guess) to a binary form. It runs on top of a Flutter virtual machine, thus compiled binary has only one primary entry point, a dispatch function. Disassembly does not yield anything sensible, it requires special tools. Also, decompilation tools require the Flutter version to precisely match the version used to compile the application.

For those willing to spend time on this: 1, 2, 3, 4.

Hack way - BLE sniffer

Manual sniffer capture:

./nrf_sniffer_ble.sh --extcap-interface /dev/cu.usbserial-0001 --capture --fifo /tmp/fi 

Alternatives

I was using the WalkingPad app to reverse engineer packet formats:

Other reported apps may be less obfuscated and easier to analyze (didn't test):

Protocol basics

Protocol internals are implemented in pad.py.

  • Belt communicates over BT LE GATT messages.
  • Controlling app sends a simple binary message to the belt for control and status fetch (request)
  • App sends periodically status requests (~ 750 ms), belt responds with a binary message containing: current belt state, manual mode indicator, belt running time in seconds, distance in 10 meters (1km = 100 units), number of steps, last set speed, last button pressed on controller (calories are not reported by the belt)
  • Large numbers, such as distance, steps and time are encoded in 3 bytes in the following form: [x0, x1, x2], where integer form is x = x0*65536 + x1*256 + x0 (big endian on 3 bytes)
  • Packet contains a simple checksum. If the checksum is invalid, belt ignores the command. Let cmd be the whole received payload, checksum is computed as: cmd[-2] = sum(cmd[1:-2]) % 256. For more, check WalkingPadCurStatus
  • Belt stores the last run status in memory. On query from the app the belt returns it in a different status message form, check WalkingPadLastStatus. Another request from the app clears the last run status.
  • It seems that the belt stores the last run status only for a limited time and does not survive power cut, thus this might be the reason why users are reporting apps are not fetching the statistics completely from the belt. Final stats are fetched after the belt is stopped, thus if app is not running when belt stops (e.g., auto stop, or by controller), app sometimes does not make the status fetch in time and the run status is lost.

Example of a status message m:

f8a2010f01000fd10000ab0012ae3c0000003afd

When logged by the application, it is printed out as array if bytes:

[248, 162, 1, 15, 1, 0, 15, 209, 0, 0, 171, 0, 18, 174, 60, 0, 0, 0, 58, 253]
  • [248, 162] or f8a2 is a fixed prefix, probably the message ID.
  • m[2] == 1 is a belt state
  • m[3] == 15 is a belt speed * 10, here 1.5 kmph
  • m[4] == 1 is a flag signalizing manual mode (vs automatic = 0)
  • m[5:8] == [0, 15, 209] is encoded time in seconds, here 4049s = 67 min, 29s
  • m[8:11] == [0, 0, 171] is distance in 10 meters, here 171 = 1.71 km
  • m[11:14] == [0, 18, 174] is number of steps, here 4782
  • m[14] == 60 is the last set app speed, 60 units, 6 kmph
  • m[15] unknown
  • m[16] last controller button pressed
  • m[17] == 58 is the checksum
  • m[18] == 253 is a fixed suffix

Meaning of some fields are not known (15) or the value space was not explored. m[15] could be for example heart rate for those models measuring it.

Related work

Another reverse engineer of the protocol (under GPL, tldr): https://github.com/DorianRudolph/QWalkingPad/blob/master/Protocol.h

Thanks

Thanks to all contributors and to the community.

This project was awarded by the Google Open Source Peer Bonus in Feb 2022.

Donate

Thanks for considering donation if you find this project useful:

Bitcoin

1DBr1tfuqv6xphg5rzNTPxqiUbqbRHrM2E

(No Lightning for now, hopefully soon)

Monero

87KDQUP7yVKd7inmX2WXuaQUBrxeGN9X9AuQwfaUkJ3KQXSRe6KbhnLRvWNK4mx2SeBwcFdHYgS71fzYFS5mtNf7Dn8SdpJ

PayPal

PayPal link

ph4-walkingpad's People

Contributors

ph4r05 avatar romses avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ph4-walkingpad's Issues

Errors and Malfunctionality when using prompts like ask_beep, start, stop etc.

I have a Dynamax Treadmill (R1 "clone"), which is unfortunately not possible to be connected to the KSFit App, which i wanted to use to synch to my AppleHealth. Either way, i want to retrieve my steps by your solution. Unfortunately i get errors, for example when i use ask_beep. Moreover the start, stop and status is not working. If i type in status for example it just outputs "None" even when the Pad is on and running. But setting the speed for example i working flawlessly. Hope you can help me out! :)

I am using the script on a Raspberry Pi 4.

WalkingPad controller


$> status
None
$> help

Documented commands (use 'help -v' for verbose/'help ' for details):

alias edit macro q s speed switch_mode
ask_beep help profile quit set start tasks
ask_last history py run_pyscript shell status upload
ask_stats login Q run_script shortcuts stop

Undocumented commands:

margins

$> ask_beep
$> 2023-02-05 14:58:11 raspberrypi asyncio[5679] ERROR Future exception was never retrieved
future: <Future finished exception=TypeError("object of type 'NoneType' has no len()") created at /usr/lib/python3.9/asyncio/base_events.py:424>
source_traceback: Object created at (most recent call last):
File "/usr/lib/python3.9/threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/cmd_helper.py", line 45, in looper
loop.run_forever()
File "/usr/lib/python3.9/asyncio/base_events.py", line 596, in run_forever
self._run_once()
File "/usr/lib/python3.9/asyncio/base_events.py", line 1890, in _run_once
handle._run()
File "/usr/lib/python3.9/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/main.py", line 171, in stats_fetcher
await self.ctler.ask_stats()
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 345, in ask_stats
return await self.send_cmd(cmd)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 316, in send_cmd
return await self.send_cmd_raw(cmd)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 321, in send_cmd_raw
r = await self.client.write_gatt_char(self.char_fe02, cmd)
File "/usr/local/lib/python3.9/dist-packages/bleak/init.py", line 593, in write_gatt_char
await self._backend.write_gatt_char(char_specifier, data, response)
File "/usr/local/lib/python3.9/dist-packages/bleak/backends/bluezdbus/client.py", line 788, in write_gatt_char
reply = await self._bus.call(
File "/usr/local/lib/python3.9/dist-packages/dbus_fast/aio/message_bus.py", line 360, in call
future = self._loop.create_future()
File "/usr/lib/python3.9/asyncio/base_events.py", line 424, in create_future
return futures.Future(loop=self)
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/dbus_fast/aio/message_bus.py", line 86, in write_callback
if self.offset >= len(self.buf):
TypeError: object of type 'NoneType' has no len()
2023-02-05 14:58:11 raspberrypi asyncio[5679] ERROR Future exception was never retrieved
future: <Future finished exception=TypeError("object of type 'NoneType' has no len()") created at /usr/lib/python3.9/asyncio/base_events.py:424>
source_traceback: Object created at (most recent call last):
File "/usr/lib/python3.9/threading.py", line 912, in _bootstrap
self._bootstrap_inner()
File "/usr/lib/python3.9/threading.py", line 954, in _bootstrap_inner
self.run()
File "/usr/lib/python3.9/threading.py", line 892, in run
self._target(*self._args, **self._kwargs)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/cmd_helper.py", line 45, in looper
loop.run_forever()
File "/usr/lib/python3.9/asyncio/base_events.py", line 596, in run_forever
self._run_once()
File "/usr/lib/python3.9/asyncio/base_events.py", line 1890, in _run_once
handle._run()
File "/usr/lib/python3.9/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/main.py", line 438, in ask_beep
await self.ctler.cmd_162_3_7()
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 353, in cmd_162_3_7
return await self.send_cmd(cmd)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 316, in send_cmd
return await self.send_cmd_raw(cmd)
File "/home/alcom/.local/lib/python3.9/site-packages/ph4_walkingpad/pad.py", line 321, in send_cmd_raw
r = await self.client.write_gatt_char(self.char_fe02, cmd)
File "/usr/local/lib/python3.9/dist-packages/bleak/init.py", line 593, in write_gatt_char
await self._backend.write_gatt_char(char_specifier, data, response)
File "/usr/local/lib/python3.9/dist-packages/bleak/backends/bluezdbus/client.py", line 788, in write_gatt_char
reply = await self._bus.call(
File "/usr/local/lib/python3.9/dist-packages/dbus_fast/aio/message_bus.py", line 360, in call
future = self._loop.create_future()
File "/usr/lib/python3.9/asyncio/base_events.py", line 424, in create_future
return futures.Future(loop=self)
Traceback (most recent call last):
File "/usr/local/lib/python3.9/dist-packages/dbus_fast/aio/message_bus.py", line 86, in write_callback
if self.offset >= len(self.buf):
TypeError: object of type 'NoneType' has no len()

Cannot connect - address unknow?

Running on OpenSuse Tumbleweed

I have connected to the WalkingPad. I run status, start which is followed by a beep. When I type speed 30 nothing happens - as well with speed 15. status returns
WalkingPadCurStatus(dist=0.0, time=0, steps=0, speed=0.0, state=0, mode=1, app_speed=3.0, button=1, rest=0000)

I press CTRL-Z ==>

$> ^[[A^[[A^CTraceback (most recent call last):
  File "/usr/lib64/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 639, in <module>
    main()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 632, in main
    loop.run_until_complete(br.main())
  File "/usr/lib64/python3.8/asyncio/base_events.py", line 603, in run_until_complete
    self.run_forever()
  File "/usr/lib64/python3.8/asyncio/base_events.py", line 570, in run_forever
    self._run_once()
  File "/usr/lib64/python3.8/asyncio/base_events.py", line 1823, in _run_once
    event_list = self._selector.select(timeout)
  File "/usr/lib64/python3.8/selectors.py", line 468, in select
    fd_event_list = self._selector.poll(timeout, max_ev)
  File "/home/lw/.local/lib/python3.8/site-packages/cmd2/cmd2.py", line 2004, in sigint_handler
    raise KeyboardInterrupt("Got a keyboard interrupt")
KeyboardInterrupt: Got a keyboard interrupt

which is no problem. But when I try to restart I fail:

❯ python3.8 -m ph4_walkingpad.main                                                                                                                                                                                                          ─╯
2022-02-03 19:48:51 ws3 __main__[7565] ERROR Exception in the main entry point: No address given to connect to
Traceback (most recent call last):
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 374, in main
    await self.work()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 94, in work
    await self.connect(address)
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 74, in connect
    await self.ctler.run()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/pad.py", line 357, in run
    await self.connect(address)
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/pad.py", line 272, in connect
    raise ValueError('No address given to connect to')
ValueError: No address given to connect to
Traceback (most recent call last):
  File "/usr/lib64/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 639, in <module>
    main()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 632, in main
    loop.run_until_complete(br.main())
  File "/usr/lib64/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 378, in main
    await self.disconnect()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/main.py", line 63, in disconnect
    await self.ctler.disconnect()
  File "/home/lw/.local/lib/python3.8/site-packages/ph4_walkingpad/pad.py", line 267, in disconnect
    await self.client.disconnect()
AttributeError: 'NoneType' object has no attribute 'disconnect'

==> Any idea what to check or what to do?

Bildschirmfoto von 2022-02-03 20-01-43

Does connect, beeps, yet actions have no impact (start, speed, etc)

Hi,

just stumble upon your amazing project via https://huserben.github.io/Automating-Walkingpad/ and with the hope of continuing my home automation process and tinkering (safely) with VR physical activity.

Anyway I manage to connect, ysing ph4-walkingpad-ctl --stats 750 --json-file ~/walking.json, as least as I assume from the beeping sound coming from the device, but unfortunately commands do not seem to work.

I tried with --debug which gives me the error INFO Error in ask stats: Task <Task pending name='Task-5' coro=<WalkingPadControl.stats_fetcher(). See part of the log below :

2022-08-17 15:26:31 fabien-CORSAIR-ONE-i160 ph4_walkingpad.pad[739255] INFO Service enumeration done
2022-08-17 15:26:33 fabien-CORSAIR-ONE-i160 bleak.backends.bluezdbus.client[739255] DEBUG Write Characteristic 0000fe02-0000-1000-8000-00805f9b34fb | /org/bluez/hci0/dev_57_4C_4E_11_79_0C/service0013/char0017: bytearray(b'\xf7\xa5`JM\x93q)\xc9\xfd')
2022-08-17 15:26:33 fabien-CORSAIR-ONE-i160 bleak.backends.bluezdbus.manager[739255] DEBUG received D-Bus signal: org.freedesktop.DBus.Properties.PropertiesChanged (/org/bluez/hci0/dev_57_4C_4E_11_79_0C/service0013/char0014): ['org.bluez.GattCharacteristic1', {'Value': <dbus_next.signature.Variant ('ay', b'\xf8\xa5MP1\x12\n\xb4\n\x00\x01\x00\x00!s\x18\x00\x00\xfa\xfd')>}, []]
2022-08-17 15:26:33 fabien-CORSAIR-ONE-i160 ph4_walkingpad.pad[739255] DEBUG Msg: f8, a5, 4d, 50, 31, 12, 0a, b4, 0a, 00, 01, 00, 00, 21, 73, 18, 00, 00, fa, fd
2022-08-17 15:26:34 fabien-CORSAIR-ONE-i160 bleak.backends.bluezdbus.client[739255] DEBUG Write Characteristic 0000fe02-0000-1000-8000-00805f9b34fb | /org/bluez/hci0/dev_57_4C_4E_11_79_0C/service0013/char0017: bytearray(b'\xf7\xa2\x03\x07\xac\xfd')
2022-08-17 15:26:34 fabien-CORSAIR-ONE-i160 bleak.backends.bluezdbus.manager[739255] DEBUG received D-Bus signal: org.freedesktop.DBus.Properties.PropertiesChanged (/org/bluez/hci0/dev_57_4C_4E_11_79_0C/service0013/char0014): ['org.bluez.GattCharacteristic1', {'Value': <dbus_next.signature.Variant ('ay', b'\xf8\xa2\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\xaa\xfd')>}, []]
2022-08-17 15:26:34 fabien-CORSAIR-ONE-i160 ph4_walkingpad.pad[739255] DEBUG Msg: f8, a2, 00, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 07, 00, 00, 00, aa, fd
WalkingPadCurStatus(dist=0.0, time=0, steps=0, speed=0.0, state=0, mode=1, app_speed=0.23333333333333334, button=1, rest=0000)
2022-08-17 15:26:34 fabien-CORSAIR-ONE-i160 ph4_walkingpad.pad[739255] DEBUG Status: WalkingPadCurStatus(dist=0.0, time=0, steps=0, speed=0.0, state=0, mode=1, app_speed=0.23333333333333334, button=1, rest=0000)
2022-08-17 15:26:35 fabien-CORSAIR-ONE-i160 asyncio[739255] DEBUG Using selector: EpollSelector
2022-08-17 15:26:35 fabien-CORSAIR-ONE-i160 ph4_walkingpad.cmd_helper[739255] DEBUG Starting looper for loop <_UnixSelectorEventLoop running=False closed=False debug=False>
2022-08-17 15:26:35 fabien-CORSAIR-ONE-i160 ph4_walkingpad.main[739255] INFO Starting stats fetching
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    WalkingPad controller

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2022-08-17 15:26:35 fabien-CORSAIR-ONE-i160 ph4_walkingpad.main[739255] INFO Error in ask stats: Task <Task pending name='Task-5' coro=<WalkingPadControl.stats_fetcher() running at /home/fabien/.local/lib/python3.10/site-packages/ph4_walkingpad/main.py:171> cb=[_chain_future.<locals>._call_set_state() at /usr/lib/python3.10/asyncio/futures.py:391]> got Future <Future pending created at /usr/lib/python3.10/asyncio/base_events.py:429> attached to a different loop

I'm not sure what model I have as I never used the app (privacy concerns) and only the remote. If that helps I could install it and check.

Otherwise here are the details :

image

It's another brand but seems like the same device.

Errors when running script

Thank you for great software! Using this with walking pad DTM100i and arch linux. Never used the phone app.
The program connects to the pad and writes stats but I cannot control the pad with it.
I have these errors reported about every half a minute but randomly. The pad was in standby mode when i started the script and then I tried to control it with start and speed 3but I had to use the remote to actually start it.

A message handler raised an exception: bytearray index out of range.
Traceback (most recent call last):
  File "/.local/lib/python3.10/site-packages/dbus_next/message_bus.py", line 668, in _process_message
    result = handler(msg)
  File "/.local/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 1018, in _parse_msg
    self._notification_callbacks[message.path](
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 232, in notif_handler
    m = WalkingPadCurStatus.from_data(data)
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 157, in from_data
    m.load_from(cmd)
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 138, in load_from
    self.belt_state = cmd[2]
IndexError: bytearray index out of range

and

A message handler raised an exception: bytearray index out of range.
Traceback (most recent call last):
  File "/.local/lib/python3.10/site-packages/dbus_next/message_bus.py", line 668, in _process_message
    result = handler(msg)
  File "/.local/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 1018, in _parse_msg
    self._notification_callbacks[message.path](
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 232, in notif_handler
    m = WalkingPadCurStatus.from_data(data)
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 157, in from_data
    m.load_from(cmd)
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 143, in load_from
    self.steps = WalkingPad.byte2int(cmd[11:])
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 115, in byte2int
    return sum([(val[i] << (8 * (width - 1 - i))) for i in range(width)])
  File "/.local/lib/python3.10/site-packages/ph4_walkingpad/pad.py", line 115, in <listcomp>
    return sum([(val[i] << (8 * (width - 1 - i))) for i in range(width)])
IndexError: bytearray index out of range

am I missing something?

Exception raised after connection

It appears that I'm successfully connecting to the device, however immediately that I get a status output and the 'menu' appears, I then get the below exception.

Behavior appears to be the same regardless of whether I manually specify the MAC or not. Python is version 3.12.0

`(.venv) PS D:\Documents\Python\treadmil> ph4-walkingpad-ctl --stats 750 --json-file walking2.json -a 57:4C:4E:2B:25:FC
WalkingPadCurStatus(dist=0.0, time=0, steps=0, speed=0.0, state=0, mode=1, app_speed=0.23333333333333334, button=1, rest=0000)

WalkingPad controller

2024-03-24 17:08:18 JamesPC ph4_walkingpad.main[18276] ERROR Exception in the main entry point:
Traceback (most recent call last):
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4_walkingpad\main.py", line 375, in main
await self.work()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4_walkingpad\main.py", line 104, in work
res = await self.entry()
^^^^^^^^^^^^^^^^^^
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4_walkingpad\main.py", line 188, in entry
await self.acmdloop()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4acmd2_init_.py", line 153, in acmdloop
await self.acmdloop()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4acmd2_init
.py", line 181, in _acmdloop
self.start_controller()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4acmd2_init
.py", line 51, in _start_controller
self.start_reader()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4acmd2_init
.py", line 80, in start_reader
self.reset_reader()
File "D:\Documents\Python\treadmil.venv\Lib\site-packages\ph4acmd2_init
.py", line 86, in reset_reader
self.loop.add_reader(self.stdin.fileno(), self.reader)
File "C:\Program Files\Python312\Lib\asyncio\events.py", line 534, in add_reader
raise NotImplementedError
NotImplementedError`

Discovery of WalkingPad Fails but Specifiying Address Works

Hi

Firstly, thanks to the author of this project!

In summary I can control my WalkingPad if I specify its address on the command line from a Pi but not by allowing the script to discover the WalkingPad on its own. I have a large mount of BlueTooth devices, which could be the cause of the issue. In case others have this problem I thought I'd share my findings.

TLDR: To get my WalkingPad to work I had to issue the command: python -m ph4_walkingpad.main -a 57:4C:4D:20:04:BD

I have a KingSmith WalkingPad A1 and a Raspberry Pi 3+ and a 4B. Both Pis are running aarch64 Bullseye Raspbian Pi OS. The WalkingPad Iphone app connects to the WalkingPad ok. I've used pip install -U ph4-walkingpad" to install ph4-walkingpad, which completes ok. Both Pis are next to the WalkingPad and if I run bluetoothctl both Pis can 'see' the WalkingPad ok. With no bluetooth devices connected to the WalkingPad and the WalkingPad powered on and 'awake' I receive the same error message on the Pis when I run ph4-walkingpad-ctl --stats 750 --json-file ~/walking.json:

022-12-30 13:03:28 GymPi asyncio[1944] WARNING Executing <Task pending name='Task-1' coro=<WalkingPadControl.main() running at /usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py:375> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_read_done(9, handle=<Handle BaseS...events.py:259>)(), <TaskWakeupMethWrapper object at 0x7f9d336190>()] created at /usr/lib/python3.9/asyncio/base_events.py:424> cb=[_run_until_complete_cb() at /usr/lib/python3.9/asyncio/base_events.py:184] created at /usr/lib/python3.9/asyncio/base_events.py:621> took 0.212 seconds
2022-12-30 13:03:31 GymPi __main__[1944] ERROR Exception in the main entry point: No address given to connect to
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 375, in main
    await self.work()
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 95, in work
    await self.connect(address)
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 75, in connect
    await self.ctler.run()
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/pad.py", line 388, in run
    await self.connect(address)
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/pad.py", line 303, in connect
    raise ValueError('No address given to connect to' )
ValueError: No address given to connect to

I've run the command in debug mode and the output is shown below:

2022-12-30 13:27:28 GymPi asyncio[1063] DEBUG Using selector: EpollSelector
2022-12-30 13:27:28 GymPi ph4_walkingpad.cmd_helper[1063] DEBUG Starting looper for loop <_UnixSelectorEventLoop running=False closed=False debug=False>
2022-12-30 13:27:28 GymPi ph4_walkingpad.pad[1063] INFO Scanning for peripherals...
2022-12-30 13:27:28 GymPi ph4_walkingpad.pad[1063] DEBUG Scanning kwargs: {}
2022-12-30 13:27:28 GymPi asyncio[1063] WARNING Executing <Task pending name='Task-1' coro=<WalkingPadControl.main() running at /usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py:375> wait_for=<Future pending cb=[BaseSelectorEventLoop._sock_read_done(9, handle=<Handle BaseS...events.py:259>)(), <TaskWakeupMethWrapper object at 0x7f7bbea370>()] created at /usr/lib/python3.9/asyncio/base_events.py:424> cb=[_run_until_complete_cb() at /usr/lib/python3.9/asyncio/base_events.py:184] created at /usr/lib/python3.9/asyncio/base_events.py:621> took 0.521 seconds
2022-12-30 13:27:28 GymPi bleak.backends.bluezdbus.manager[1063] DEBUG initial properties: {'/org/bluez': {'org.freedesktop.DBus.Introspectable': {}, 'org.bluez.AgentManager1': {}, 'org.bluez.ProfileManager1': {}, 'org.bluez.HealthManager1': {}}, '/org/bluez/hci0': {'org.freedesktop.DBus.Introspectable': {}, 'org.bluez.Adapter1': {'Address': 'B8:27:EB:CA:2B:29', 'AddressType': 'public', 'Name': 'GymPi', 'Alias': 'GymPi', 'Class': 0, 'Powered': True, 'Discoverable': False, 'DiscoverableTimeout': 180, 'Pairable': False, 'PairableTimeout': 0, 'Discovering': False, 'UUIDs': ['00001801-0000-1000-8000-00805f9b34fb', '00001800-0000-1000-8000-00805f9b34fb', '00001200-0000-1000-8000-00805f9b34fb', '0000110c-0000-1000-8000-00805f9b34fb', '0000110e-0000-1000-8000-00805f9b34fb', '0000180a-0000-1000-8000-00805f9b34fb'], 'Modalias': 'usb:v1D6Bp0246d0537', 'Roles': ['central', 'peripheral']}, 'org.freedesktop.DBus.Properties': {}, 'org.bluez.GattManager1': {}, 'org.bluez.LEAdvertisingManager1': {'ActiveInstances': 0, 'SupportedInstances': 5, 'SupportedIncludes': ['tx-power', 'appearance', 'local-name']}, 'org.bluez.Media1': {}, 'org.bluez.NetworkServer1': {}}}
2022-12-30 13:27:29 GymPi bleak.backends.bluezdbus.manager[1063] DEBUG received D-Bus signal: org.freedesktop.DBus.Properties.PropertiesChanged (/org/bluez/hci0): ['org.bluez.Adapter1', {'Discovering': <dbus_fast.signature.Variant ('b', True)>}, []]

2022-12-30 13:27:29 GymPi bleak.backends.bluezdbus.manager[1063] DEBUG received D-Bus signal: org.freedesktop.DBus.ObjectManager.InterfacesAdded (/): ['/org/bluez/hci0/dev_57_4C_4D_20_04_BD', {'org.freedesktop.DBus.Introspectable': {}, 'org.bluez.Device1': {'Address': <dbus_fast.signature.Variant ('s', 57:4C:4D:20:04:BD)>, 'AddressType': <dbus_fast.signature.Variant ('s', public)>, 'Name': <dbus_fast.signature.Variant ('s', Dynamax)>, 'Alias': <dbus_fast.signature.Variant ('s', Dynamax)>, 'Paired': <dbus_fast.signature.Variant ('b', False)>, 'Trusted': <dbus_fast.signature.Variant ('b', False)>, 'Blocked': <dbus_fast.signature.Variant ('b', False)>, 'LegacyPairing': <dbus_fast.signature.Variant ('b', False)>, 'RSSI': <dbus_fast.signature.Variant ('n', -51)>, 'Connected': <dbus_fast.signature.Variant ('b', False)>, 'UUIDs': <dbus_fast.signature.Variant ('as', ['0000fe00-0000-1000-8000-00805f9b34fb'])>, 'Adapter': <dbus_fast.signature.Variant ('o', /org/bluez/hci0)>, 'ManufacturerData': <dbus_fast.signature.Variant ('a{qv}', {96: <dbus_fast.signature.Variant ('ay', bytearray(b'WLM \x04\xbd'))>})>, 'ServicesResolved': <dbus_fast.signature.Variant ('b', False)>}, 'org.freedesktop.DBus.Properties': {}}]

(Other bluetooh devices removed)
2022-12-30 13:27:32 GymPi bleak.backends.bluezdbus.manager[1063] DEBUG received D-Bus signal: org.freedesktop.DBus.Properties.PropertiesChanged (/org/bluez/hci0/dev_57_4C_4D_20_04_BD): ['org.bluez.Device1', {}, ['RSSI']]

(Other bluetooh devices removed)
2022-12-30 13:27:32 GymPi bleak.backends.bluezdbus.manager[1063] DEBUG received D-Bus signal: org.freedesktop.DBus.Properties.PropertiesChanged (/org/bluez/hci0): ['org.bluez.Adapter1', {'Discovering': <dbus_fast.signature.Variant ('b', False)>}, []]
2022-12-30 13:27:32 GymPi ph4_walkingpad.pad[1063] INFO Device: [ 0], 57:4C:4D:20:04:BD, Dynamax, ['0000fe00-0000-1000-8000-00805f9b34fb']

2022-12-30 13:27:32 GymPi __main__[1063] ERROR Exception in the main entry point: No address given to connect to
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 375, in main
    await self.work()
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 95, in work
    await self.connect(address)
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/main.py", line 75, in connect
    await self.ctler.run()
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/pad.py", line 388, in run
    await self.connect(address)
  File "/usr/local/lib/python3.9/dist-packages/ph4_walkingpad/pad.py", line 303, in connect
    raise ValueError('No address given to connect to' )
ValueError: No address given to connect to
2022-12-30 13:27:32 GymPi __main__[1063] DEBUG Disconnecting coroutine

Thanks

Bert

question

Hello,
First of all thank you very much for sharing this, it seems to be working with my R1 PRO walkingpad.
Have a few question if you have the time to address them.

  1. How do you grab the JWT token from the app?
  2. I've been looking for docs for the walkingpad API, how did you manage to find it? Reversed engineered it? :)
  3. How can i donate ? 👍

Thanks again!
Vlad

ValueError: No address given to connect to

I am doing something wrong?

test@host:~/ph4-walkingpad $  ph4-walkingpad-ctl --stats 750 --json-file ~/walking.json -p profile.json
Traceback (most recent call last):
  File "/home/issa/.local/lib/python3.7/site-packages/ph4_walkingpad/main.py", line 374, in main
    await self.work()
  File "/home/issa/.local/lib/python3.7/site-packages/ph4_walkingpad/main.py", line 94, in work
    await self.connect(address)
  File "/home/issa/.local/lib/python3.7/site-packages/ph4_walkingpad/main.py", line 74, in connect
    await self.ctler.run()
  File "/home/issa/.local/lib/python3.7/site-packages/ph4_walkingpad/pad.py", line 359, in run
    await self.connect(address)
  File "/home/issa/.local/lib/python3.7/site-packages/ph4_walkingpad/pad.py", line 274, in connect
    raise ValueError('No address given to connect to')
ValueError: No address given to connect to
test@host:~/ph4-walkingpad $ cat profile.json
{
  "id": "xjfrac2lhj",
  "male": true,
  "age": 25,
  "weight": 70,
  "height": 1.80,
  "token": "tyJ5eXAiOoJKH1RiLCJh"
  "did": "57:4C:4D:4D:1B:1B",
  "email": "[email protected]",
  "password": "superpassword",
}

Token, did, email and pass are fake.

No longer works in OSX Monterey

After upgrading from Catalina to Monterey, it seems that scan is no longer working, as no devices are listed anymore:

$ python3 -m ph4_walkingpad.main -s --debug
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro asyncio[49062] DEBUG Using selector: KqueueSelector
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro ph4_walkingpad.cmd[49062] DEBUG Starting looper for loop <_UnixSelectorEventLoop running=False closed=False debug=False>
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro ph4_walkingpad.pad[49062] INFO Scanning for peripherals...
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro bleak.backends.corebluetooth.CentralManagerDelegate[49062] DEBUG centralManagerDidUpdateState_
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro bleak.backends.corebluetooth.CentralManagerDelegate[49062] DEBUG Bluetooth powered on
2021-11-02 10:12:15 pedros-macbookpro16.home.pombei.ro bleak.backends.corebluetooth.CentralManagerDelegate[49062] DEBUG 'isScanning' changed
2021-11-02 10:12:20 pedros-macbookpro16.home.pombei.ro bleak.backends.corebluetooth.CentralManagerDelegate[49062] DEBUG 'isScanning' changed

NOTE: This is probably related: hbldh/bleak#635

Problem running from console on raspberry pi

Hi thanks for the script it works great through jupyter notebook on my PC.

However, I would like to run this on my raspberry pi with Home Assistant. When I try to run it in terminal I get it to connect to WalkingPad but I'm unable to start it or do anything beyond that. It gives this error:

  ph4-walkingpad-master python3
Python 3.9.5 (default, May 12 2021, 20:44:22) 
[GCC 10.3.1 20210424] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import logging
>>> import coloredlogs
>>> import asyncio 
>>> 
>>> from ph4_walkingpad import pad
>>> from ph4_walkingpad.pad import Scanner, WalkingPad, WalkingPadCurStatus, WalkingPadLastStatus, Controller
>>> from ph4_walkingpad.utils import setup_logging
>>> 
>>> log = setup_logging()
>>> pad.logger = log
>>> #coloredlogs.install(level=logging.INFO)
>>> ctler = Controller()
>>> async def run():
...     await ctler.run('57:4C:4E:1F:6C:12')
... 
>>> asyncio.run(run())
/config/ph4-walkingpad-master/ph4_walkingpad/pad.py:332: FutureWarning: is_connected has been changed to a property. Calling it as an async method will be removed in a future version
  x = await client.is_connected()
Connected: True
[Service] 0000180a-0000-1000-8000-00805f9b34fb: Device Information
        [Characteristic] 00002a25-0000-1000-8000-00805f9b34fb: (Handle: 9) (read) | Name: Serial Number String, Value: b'Serial Number\x00' 
        [Characteristic] 00002a26-0000-1000-8000-00805f9b34fb: (Handle: 11) (read) | Name: Firmware Revision String, Value: b'M30_V187.2.0\x00' 
        [Characteristic] 00002a28-0000-1000-8000-00805f9b34fb: (Handle: 13) (read) | Name: Software Revision String, Value: b'Hardware Revision\x00' 
        [Characteristic] 00002a24-0000-1000-8000-00805f9b34fb: (Handle: 15) (read) | Name: Model Number String, Value: b'WLT8266M\x00' 
        [Characteristic] 00002a29-0000-1000-8000-00805f9b34fb: (Handle: 17) (read) | Name: Manufacturer Name String, Value: b'Software Revision\x00' 
[Service] 0000fe00-0000-1000-8000-00805f9b34fb: Vendor specific
        [Characteristic] 0000fe01-0000-1000-8000-00805f9b34fb: (Handle: 20) (read,notify) | Name: Vendor specific, Value: None 
                [Descriptor] 00002902-0000-1000-8000-00805f9b34fb: (Handle: 22) | Value: b'\x00\x00' 
        [Characteristic] 0000fe02-0000-1000-8000-00805f9b34fb: (Handle: 23) (write-without-response) | Name: Vendor specific, Value: None 
[Service] 00010203-0405-0607-0809-0a0b0c0d1912: Unknown
        [Characteristic] 00010203-0405-0607-0809-0a0b0c0d2b12: (Handle: 26) (read,write-without-response) | Name: Unknown, Value: b'\x00' 
                [Descriptor] 00002901-0000-1000-8000-00805f9b34fb: (Handle: 28) | Value: b'OTA' 
Enabling notification for 0000fe01-0000-1000-8000-00805f9b34fb
Service enumeration done
>>> async def start():
...     await ctler.switch_mode(WalkingPad.MODE_MANUAL)
...     await asyncio.sleep(1.0)
...     await ctler.start_belt()
... 
>>> asyncio.run(start())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "<stdin>", line 2, in start
  File "/config/ph4-walkingpad-master/ph4_walkingpad/pad.py", line 267, in switch_mode
    return await self.send_cmd(cmd)
  File "/config/ph4-walkingpad-master/ph4_walkingpad/pad.py", line 257, in send_cmd
    return await self.send_cmd_raw(cmd)
  File "/config/ph4-walkingpad-master/ph4_walkingpad/pad.py", line 262, in send_cmd_raw
    r = await self.client.write_gatt_char(self.char_fe02, cmd)
  File "/usr/lib/python3.9/site-packages/bleak/backends/bluezdbus/client.py", line 787, in write_gatt_char
    reply = await self._bus.call(
  File "/usr/lib/python3.9/site-packages/dbus_next/aio/message_bus.py", line 303, in call
    self._call(msg, reply_handler)
  File "/usr/lib/python3.9/site-packages/dbus_next/message_bus.py", line 588, in _call
    self.send(msg)
  File "/usr/lib/python3.9/site-packages/dbus_next/aio/message_bus.py", line 326, in send
    self._writer.schedule_write(msg, future)
  File "/usr/lib/python3.9/site-packages/dbus_next/aio/message_bus.py", line 85, in schedule_write
    self.loop.add_writer(self.fd, self.write_callback)
  File "/usr/lib/python3.9/asyncio/selector_events.py", line 346, in add_writer
    self._add_writer(fd, callback, *args)
  File "/usr/lib/python3.9/asyncio/selector_events.py", line 295, in _add_writer
    self._check_closed()
  File "/usr/lib/python3.9/asyncio/base_events.py", line 510, in _check_closed
    raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed

--stats argument ignored, 25 lines added to JSON file per second

The --stats parameter says it takes an argument for how often stats are collected:

  --stats STATS         Enable periodic stats collecting, interval in ms

Thus, one would expect a command such as ph4-walkingpad-ctl --stats 10000 --json-file ~/walk.json to add a new line to ~/walk.json. However, that does not happen – 25 new lines are added every second, regardless of the argument passed to --stats:

$ jq .time < ~/walk.json  | uniq -c
     15 149
     25 150
     25 151
     25 152
     25 153
     25 154
     25 155
     25 156
     25 157
(…)

This is ph4-walkingpad v0.0.7 (installed via pip) running on Fedora 37 towards a WalkingPad A1 Pro.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.