Two years ago I did the Grand Traverse ski race. It is a 40 mile race between Crested Butte and Aspen through the Elk Mountains. I wouldn't exactly say I was racing. More just hoping to survive to the finish line. As part of the preperation for the race I followed an 80/20 Endurance 50 mile ultramarathon training plan.
This plan involves workouts that look something like:
5:00 in Zone 1
5:00 in Zone 2
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
5:00 in Zone 1
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
5:00 in Zone 1
5 x (0:30 in Zone 3/0:20 in Zone 4/0:10 in Zone 5)
10:00 in Zone 1
That is one of the more involved workouts. The specifics aren't important but what is important is there are 50 steps in there and some of the steps are as short as 10 seconds. So to follow along one needs some sort of system. I ended up doing the workouts just by bouncing between the Training Peaks app where the workout is stored, Strava where I could see my heart rate, and the iOS timer app. This would result in me staring at my phone the whole run which isn't very fun. So, near the end of my plan I went in search of a better system.
I thought the simplest solution would be to buy a Garmin smartwatch (or similar). They know about workouts like this and you can download the workout straight to the watch. But, I didn't really want to spend a few hundred dollars on a watch that would sit in a drawer after my five month traning plan was done. Also, from the people I've talked to the Garmin software is nothing amazing.
During my search for something better I stumbled upon a pretty cool solution. The Bangle.js. The Bangle.js is "the world's first open source hackable smartwatch." You write code in JavaScript for it and it only costs about $90. Perfect!
I finished my traning plan (and the race) before I had time to actually program the watch. It then sat in a drawer for two years before I got the idea to do the Grand Traverse again. So, recently I pulled out the old training plan and dusted off my watch.
Below is some information on the app I built to manage my training plan. I think the content is relevant to many people who want to program on the Bangle.js even if the app you want is for something else.
First some eye candy.
That is the main face of the watch while running my app. It has time left in the current stage, current heart rate, target heart rate range, and the name of the target heart rate zone. It isn't that amazing but getting there involved some twists and turns.
If you prefer reading code over prose here is a link to my app. There is much to improve but it works for now.
Basic development
The simplest way to program the Bangle.js is to do so in the web IDE. That is the way they tell you to program in the docs. But, I like programming in Emacs so a web IDE isn't going to fly.
Instead I connected to the watch using the espruino
cli. Connection is fairly
simple, you can do espruino -d Bangle --watch app.js
. -d Bangle
connects to any nearby bluetooth device with "Bangle" in the name.
--watch app.js
uploads my app and watches for changes. Using this
cli I was able to program in Emacs and on every save my code was
uploaded to the watch.
Bluetooth connection
Dealing with bluetooth is always a pain. From just being a consumer trying to get my headphones connected to my laptop to actually programming it on the watch I loathe interacting with bluetooth. The APIs for dealing with bluetooth on the Bangle.js are decent but bluetooth is just a finicky protocol no matter what.
I need bluetooth connectivity in my app to connect to an external heart rate monitor and to receive push notifications. The Bangle.js comes with a heart rate monitor under the watch face but it is pretty innacurate with any movement. So, I wanted to connect to a heart rate monitor I wear on my chest.
This is the main bit of bluetooth code in my app. Like most bluetooth code once you get it working it isn't that many lines but getting there takes some time. Also, it is all wrapped in an infinite retry because bleutooth connections never work on the first try and tend to drop after a while.
One issue with programming with bluetooth on the Bangle.js is that you can only connect to one peripheral and one "central" device at a time. So, I can connect to the debugger and to my heart rate monitor but nothing else. You'll see how this becomes a problem in the next section.
Getting the workout from my iPhone to the watch
I needed some way of loading my workout onto the watch. While developing I just had it hardcoded in the app. But, in the "production" version I settled on a nice Rube Goldberg contraption.
I first download the day's workout from the iOS Training Peaks app on my phone. This comes in a json file. I then use an iOS shortcuts flow that I wrote which let's me select the file, parse it, and send a push notification with the parsed data. My watch is paired with my phone so it receives the push notification and gets the workout from the body of the notification.
All in all, this seems to work fine. Getting the connection between my phone and the watch working and creating a small enough noticication so it wouldn't get clipped by iOS proved a bit challenging. I think the iOS push notification size limit is 4KB but I'm not really sure. I was bumping against whatever the limit was until I made the message as small as possible. This involved doing point and click programming in the iOS shortcuts app which was a chore but I was happy to have some way of writing a little program on my phone without having to resort to writing a full app.
The hardest part of this entire process is there is no good way to debug. As I said above you can only be connected to one peripheral and one "central" device at a time. My heart rate monitor counts as a peripheral. So, for the "central" connection I had to decide between being connected to the debugger or to my phone to receive a push notification. This resulted in numerous cycles of connecting to the deugger, uploading my code, disconnecting, fighting to connect my phone and the watch, sending the notification, having the app not work, and having no clue why.
I found a few solutions that may help future Bangle.js users.
- Write to a file instead of writing to stdout. You can write to a file quite easily so I would do that as a means to figure out what was going on.
- Similar to the first hint you can write all uncaught exceptions to a file. I was quite relieved when I found that burried down in the docs.
- Use the Light Blue app for connecting to the watch. Once the watch is paired the iPhone will connect to it again in the future. But, for whatever reason BLE devices aren't listed when trying to pair for the first time.
- There is more advice in the README.
After more time than I care to admit of carefully reading code and fixing bugs I still wasn't able to get my code working. But, the above tools finally gave me enough of a view into the errors that I could finish the app.
Final thoughts
I'm really happy with the Bangle.js. It is a super impressive bit of tech and I think it is almost entirely built by one guy, Gordon Williams. He is a very active coder and is very active on the forums. He's built quite an ecosystem.
If you're curious about hacking on a smart watch I highly reccomend the Bangle.js. I have the first version of the device but there is a newer version out with better specs. I hope to upgrade one of these days.
If you happen to already have a Bangle.js you can get my app from here. It has my heart rate zones hard-coded but I hope to fix that one of these days. You'll also need the ios integration app, my iOS shortcut and a workout loaded in Training Peaks. I haven't put any thought into whether or not this app will work for anyone besides me so buyer beware!