RaspberryPI Bluetooth Heart Rate Display

While using my rowing machine I want to see my current heart rate and an indication for how long I am already rowing. I record the setup with my Coros watch, so this is only a display. For the last year I used an Android app for this, but at the end of last year this app got a redign and is not working for me anymore. I tried a few other Android apps, but either it needs an account, wants to record something or is not able to only show the heart rate. So I decided I have to build something myself.

I tried to show the heart rate of a Wahoo Tickr chest strap with a Raspberry PI. First using a Raspberry PI Zero W because I have one readily installed lying around, but the Zero loses connection to the chest strap after a few seconds and reconnects. Maybe I have a broken Zero W, which I didn't investigate, or the Zero W is not good enough. I used a Raspberry PI Zero 2 from that time on. Bluetooth is enabled by default with current Raspberry PI OS, so let us first use the tools that are there.

How to get the raw values of a BLE chest strap:

$ hciconfig
hci0:   Type: Primary  Bus: UART
        BD Address: B8:27:EB:06:F6:D9  ACL MTU: 1021:8  SCO MTU: 64:1
        UP RUNNING
        RX bytes:3610 acl:0 sco:0 events:256 errors:0
        TX bytes:33188 acl:0 sco:0 commands:256 errors:0
$ sudo hcitool lescan
EB:D4:07:40:52:A0 TICKR 3AB9
// others removed

// this is maybe not needed, I didn't debug further
$ sudo btmgmt le on

$ sudo bluetoothctl
// from here on we are in bluetoothctl shell
Agent registered
[CHG] Controller E4:5F:01:54:28:34 Pairable: yes
[bluetooth]# scan on
Discovery started
[CHG] Controller E4:5F:01:54:28:34 Discovering: yes
[bluetooth]# connect EB:D4:07:40:52:A0
Connection successful
[TICKR 3AB9]# gatt.select-attribute 00002a37-0000-1000-8000-00805f9b34fb
[TICKR 3AB9:/service0025/char0026]# gatt.read
Attempting to read /org/bluez/hci0/dev_EB_D4_07_40_52_A0/service0025/char0026
[TICKR 3AB9:/service0025/char0026]# gatt.notify on
[CHG] Attribute /org/bluez/hci0/dev_EB_D4_07_40_52_A0/service0025/char0026 Value:
  16 57 b8 02 bc 02                                .W....
[CHG] Attribute /org/bluez/hci0/dev_EB_D4_07_40_52_A0/service0025/char0026 Value:
  16 57 bc 02                                      .W..

The address of my Tickr is EB:D4:07:40:52:A0. The attribute 00002a37-0000-1000-8000-00805f9b34fb is the service id for the heart rate values. The lines at the end are the raw data from the chest strap. So the connection works and the values only need to be decoded.

The second thing that needs to work is a display connected with I2C. I used a 0.96" Inch I2c IIC Serial 128x64 Oled LCD LED White Display Module which I bought years ago. I2C needs to be activated via raspi-config.

The full Python code and install instructions: https://github.com/mfa/hr-ble-display

Some comments on the code:

The bluetooth part is async, but the I2C display is not. This is not optimal, because the update of the display pauses the async loop. A solution could be to update the display in a thread and send an event to update the display to this thread. For now the current solution is good enough until I actually have an issue while I am using it.

Another thing is the start of the script. I use systemd here with Restart=always because the bluetooth stack needs a bit to initialize and the chest strap some time to be found. This can take a bit, so I boot the Raspberry PI a few minutes before workout (and I may keep it running in the future and add other sensors to the PI).

On the display the BPM are shown in a bigger font size and the time since since the workout started in a smaller font in the lower right corner. The workout time is since the start or since the BPM was 0 the last time.

The whole setup in action: