• Home
  • Blog
    • Programming
    • Art
    • Recipes
    • Home Automation
    • Life
    • Friday Faves
    • Books
    • Writing
    • Games
    • Web Accessibility
    • Advent of Code
  • Projects
  • GLSL Shader Art
  • Glitch Art

  • Github
  • Bluesky
  • Mastodon
  • Duolingo
  • LinkedIn
  • RSS
  • Bridgy Fed

mary.codes

Web development, art, and tinkering with fun projects. JavaScript, Python, WebGL, GLSL, Rust, Lua, and whatever else I find interesting at the moment.

Creating a Fitbit watch face

Mary KnizeBlog iconDec 26th, 2019
4 min read

Programming

Creating my first Fitbit Versa 2 watch face.

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.


Other Programming posts

How I'm organizing my work in 2024

Finding the Design Engineer within the Full-Stack Developer

Displaying the current Git branch in my Linux terminal prompt

Generating a 3D map with OpenStreetMap and A-Frame

Translating OpenStreetMap data to HTML5 Canvas with Rust and WebAssembly

Latest posts

I started painting again

100 days of Japanese on Duolingo

How I'm organizing my work in 2024

Watch the documentary 'Good Night Oppy' if you're feeling science-y

Sculpted robo-goldfish, a rainbow office building, and a look inside click farms