Creating a Fitbit watch face

December 26th, 2019

One of the reasons why I purchased a Fitbit Versa 2 is because I wanted to create my own apps and watchfaces. At first, I thought that watch face development would be a lot like front-end development, but it didn't turn out quite that way. Fitbit uses JavaScript and CSS, so there are some similarities, but instead of HTML it uses SVG for layout, which presents its own challenges. I decided for my first watch face I wanted to make a word clock. Instead of updating the time each minute, it updates the lit words on the watch face every five minutes.

Fitbit watch face

Instead of using Fitbit's web-based editor, Fitbit Studio, I prefer to use the CLI and use Webstorm. There are some basic instructions for setting up the CLI at https://dev.fitbit.com/build/guides/command-line-interface/. I start a new project with npx create-fitbit-app. Another way of getting started, and having a good scaffold to start from, is by cloning one of Fitbit's sample projects, like https://github.com/Fitbit/sdk-moment.

Here are a few layout challenges I faced. First, Fitbit doesn't offer a monospaced font, so I couldn't just type in the words I needed with some extra letters around them. I decided that I would instead make a grid and put a letter in each grid space. However, there also isn't an easy way to make a grid with SVG elements. What I ended up doing is making a bunch of individual text boxes and putting them into a grid with CSS.

/resources/index.gui

<svg class="background">
    <text id="row1col1" class="row1 col1">I</text>
    <text id="row1col2" class="row1 col2">T</text>
    <text id="row1col3" class="row1 col3">X</text>
    <text id="row1col4" class="row1 col4">I</text>
    <text id="row1col5" class="row1 col5">S</text>
    <text id="row1col6" class="row1 col6">A</text>
...
</svg>

As you can see, I have a unique ID for each text block that has the row and column numbers, and classes that separately correspond to rows and classes. In this way, I can use the IDs to individually "light up" each letter, and the classes for positioning.

/resources/style.css

.row1 {
    y: 20;
}
.row2 {
    y: 47;
}
.row3 {
    y: 74;
}
...

.col1 {
    x: 10;
}
.col2 {
    x: 35;
}
.col3 {
    x: 60;
}
...

With this SVG and CSS code, I get a nice, evenly spaced grid of letters that I'll use to spell out the time.

Now, one of the most frustrating things about Fitbit development is that you can't add and remove classes using JavaScript. It seems like it should be possible, but according to Fitbit's dev team, it's not. Because of this, I have to do silly things like this to light up the letters.

/app/index.js

    let textcolor = "white";
    document.getElementById('row1col1').style.fill = textcolor;
    document.getElementById('row1col2').style.fill = textcolor;
    document.getElementById('row1col4').style.fill = textcolor;
    document.getElementById('row1col5').style.fill = textcolor;

This little bit of code lights up the "IT IS" that always preceeds the time.

The time calculation is done here:

    let today = evt.date;
    let hours = today.getHours();
    hours = hours % 12 || 12;
    let mins = today.getMinutes();
    minsToWords(mins);
    if (mins >= 35) {
        hours = (hours + 1) % 12 || 12;
    }
    hoursToWords(hours);

This calculates the current time, gets the number for the hour, or 12, and the current number of minutes in the hour. If it's past the "half hour" mark, we add another hour to the calculation so we can say "x minutes to y".

The minsToWords and hoursToWords functions take the supplied values and light up the corresponding letters.

function hoursToWords(hours) {
    switch (hours) {
        case 1:
            document.getElementById('row10col9').style.fill = textcolor;
            document.getElementById('row10col10').style.fill = textcolor;
            document.getElementById('row10col11').style.fill = textcolor;
            break;
        case 2:
            document.getElementById('row8col10').style.fill = textcolor;
            document.getElementById('row8col11').style.fill = textcolor;
            document.getElementById('row8col12').style.fill = textcolor;
            break;
...

Because we're only updating every five minutes, minsToWords does an additional check to see which five-minute mark we're between.

function minsToWords(mins) {
    if (mins >= 0 && mins < 5) {
        document.getElementById('row11col7').style.fill = textcolor;
        document.getElementById('row11col8').style.fill = textcolor;
        document.getElementById('row11col9').style.fill = textcolor;
        document.getElementById('row11col10').style.fill = textcolor;
        document.getElementById('row11col11').style.fill = textcolor;
        document.getElementById('row11col12').style.fill = textcolor;
    } else if (mins >= 5 && mins < 10) {
        document.getElementById('row3col7').style.fill = textcolor;
        document.getElementById('row3col8').style.fill = textcolor;
        document.getElementById('row3col9').style.fill = textcolor;
        document.getElementById('row3col10').style.fill = textcolor;
        minutes();
        past();
...

Mostly, there's a lot of boring style.fill nonsense going on in the JavaScript to change the letter colors from dark to light, but it works! There's also a refresh function that runs before each update to ensure that no previously-lit letters remain lit after the time changes.

I've added the code to Github at https://github.com/captainpainway/fitbit-word-clock-watch-face. You'll see that there are settings and a companion app that I didn't add here because they were a little complicated and I still don't understand how they work 100%. Hopefully once I figure that out a little bit better I can post a follow-up for that.