⚡ Frank Villaro-Dixon's website

Hacking a walking pad into an IOT device

My WFH setup consists of a walking pad. However, it doesn’t track distance over time and resets on every day. Let’s put it on the cloud

weekend-project

Motivation

I’ve owned a standing desk for a long time now, and used it quite regularly. However, I’ve found that the body is not really compelled to keep standing: there is no direct motivation to do so.

Moreover, when you work from home you sometimes start snacking and gaining weight.

I’ve have the idea of having an under-the-desk treadmill/walking pad. Linus Torvalds, for example, uses one. It was on the back of my head for some time, until a colleague made me jealous by buying one for himself.

So that was the reason to buy one too. They’re quite cheap (~100 €)1 and work rather well.

Since more than a month now, my work from home setup has consisted of a standing desk plus a walking pad.

The problem though, is that the built-in distance tracker resets every time you turn the walking pad off. I really wanted to keep track of my daily distance, so I could track my progress and my walking goals. Thus started the goal of IOT-fying this treadmill

What didn’t work out

The first goal was to be able to measure the distance walked on the walking pad.

There were many solutions: the easiest one would be to estimate the distance based on the power-on time of the motor. However, the speed can be changed and thus it wouldn’t be that much accurate.

After disassembling the machine, I thought about interfacing with the display PCB. My walking pad (an “LF-X2D”) has two PCBs connected through a ribbon cable. The main one houses the PSU and the motor controller, while the smaller “logic” one displays data and receives the commands from the remote control.

The 2 logic boards

I started scoping the cables, and it turns out that the 5-strand cable is made of:

  • VCC (~10V)
  • COMM (serial)
  • 8V-ish
  • 3.3V-ish
  • GND

Using the trusty Saleae logic analyser, I started to reverse engineer the communication protocol:

Scoping the serial line

It turns out that the serial port is a rather standard 4800 8N1 serial channel. The payload is always grouped into packs of 9 bytes, which are always 3 groups of 3 identical bytes, for example:

  • 0xB1 0xB1 0xB1 0xAE 0xAE 0xAE 0xCF 0xCF 0xCF

After some time sniffing, pausing the treadmill, upping the speed, etc. I just gave up, mainly for the following reasons: first of all, that meant that the MCU I would use would need to interpret these serial frames, and I wasn’t really motivated to do that. But most importantly, I saw that the PCB that controls the motor is only providing power to the motor: there is no feedback loop to the power PCB nor to the control PCB. That means that the control PCB cannot know precisely the speed of the motor (i.e it can slow down with the weight, friction of the pad, etc..). It was a bit dumb to do all this work and have semi accurate measures.

So I opted instead to bypass these PCBs and just measure the speed of the pulley instead. If you’d like to have a look a the frames, you can check out the repo here

Hardware solution

In order to measure of the movement of the walking belt, I opted for a simple optical limit switch connected to the motor’s pulley. By measuring the impulsions, the controller knows the distance traveled at all times. By subtracting the impulsions received from the others, it obtains a difference, or distance walked.

Using the amazing FreeCAD software, a little stand for the sensor was designed. I’m a bit ashamed of how badly I added the inserts into the piece (for my defense: I mis-measured the holes and there were way too small to start with).

You can download the freecad files here.

The encoder was then connected to an ESP8266. It was a first time using this model for me, but I have to agree that it’s quite useful: the ESP8266 embedds a WIFI + TCP/IP(v4) stack, so it’s really easy to call webservices with just some lines of code.

If you’re interested, the hacked together source code for the ESP8266 module is available here.

Cloud infrastructure

At this point in time, we have a device that can send HTTP requests, via wifi, to an internet service. The next step is to write a simple backend that can gather and process these requests.

The backend won’t be that much complicated: it’s only two CRUD-ish tasks. Because I quite like it, I wrote it in rust. I have to agree that it was a very pleasant experience. It is connected to a postgresql database to persist the results.

The backend implements the following routes:

  • POST walking.k3s.fr/api/devices in order to register a new device
  • GET walking.k3s.fr/api/devices/:device_uuid to get a device summary (notably its last ping to the backend)
  • POST walking.k3s.fr/api/distance/:device_uuid to submit a new distance walked
  • GET walking.k3s.fr/api/distance/:device_uuid to get all the distance walked as a time series

The ESP8266 devices send the two following requests:

  • A ping at boot and then every minute that is used to know when the device is up and running (and also if something went wrong and is no longer working).
  • A distance report every $x$ meters walked. In my case every 76 meters (because it’s exactly 2000 sensor interrupts)

The ping timestamp is not stored in the postgresql database (it’s not that useful IMHO). Because I didn’t want to rely on an external cache DB like redis, I just stored it inside a simple hashmap. It will be lost when the application restarts, but I guess I can live with that.

When the walking pad reports the distance to the API, it is not committed directly to the DB. That was chosen for the following reasons:

  • We can bin the measurements and aggregate them every 10 minutes or so, as we don’t need more precise resolution
  • As the service is open and free, it prevents somewhat from rogue devices commiting the distance every second, and thus creating many records at once

Instead, every $x$ minutes, a backend task writes to the DB all the distances sum reported by the devices.

If you’re interested the repo for the backend is here.

The service was quite easy to deploy in my home Kubernetes infra (running on k3s), it consists of:

  • A backend in rust
  • The Postgresql DB
  • The frontend written in Vue.js.

Frontend

REST is nice, but a simple visualisation frontend is even nicer for us humans. It was written in Vue.js and is basically a simple wrapper around the REST endoints. If you’re interested, the repo is here.

It allows for two features:

  • Register a new device (if for example you want to cloudify your walking pad too; it’s free!).
  • Show the stats of a walking pad

When registering a new walking pad, the backend gives you two token:

  • The public UUID of the walking pad, that you can share to other people so they can see their stats
  • The push token to be used when sending the metrics of the distance walked, to be kept secret

The statistics page is rather simple, but gets to the point:

the frontend with stats per day

Conclusion

All in all, it was a rather nice project. Most of it was done in a weekend, but as always, all the little details, the writing of this blogpost, and the polishing of it takes almost more time than the initial 80% of the work.

Hopefully, having these statistics encourages me to walk from home 10 km daily.

If you want to do the same

If you’d like to do the same, it’s rather easy.

First of all, you need an ESP8266. It needs to be powered somehow (I used the display PCB), and wire the interrupt sensor to GPIO15 (D8).

In order to build the firmware, you need a device ID + token. These can be generated on the walking-pad tracker website:

The creds are generated from the website

Then you’ll want to clone the firmware repo, and upload it to your MCU. You have to specify the following build variables to PlatformIO:

  • WIFI_SSID="my_ssid"
  • WIFI_PASSWORD="hunter2"
  • DEVICE_UUID="0198c924-f7aa-7a63-b043-811ebd308f19"
  • DEVICE_AUTH_TOKEN="8c8bb061-f586-4137-b28b-cb4c868f0dde"

you can use the PLATFORMIO_BUILD_FLAGS environment value like so:

export PLATFORMIO_BUILD_FLAGS='-DWIFI_SSID="my_ssid" -DWIFI_PASSWORD…'

And then build + upload the firmware to the MCU:

pio run

(or use the vscode integration plugin).

Finally, wrap everything up, launch your walking pad, and check the stats on the website by entering the DEVICE_ID:

Enter the device token into the website

And look at your walking stats :-)

Look at the stats for the new device


  1. Although I don’t expect them to last 5+ years.. we’ll see ↩︎