TOTP tokens on my wrist with the smartest dumb watch.

A Casio F-91W showing my Google TOTP code

I recently took delivery of a new replacement logic board for the ubiquitous classic Casio F-91W from Sensor Watch. The F-91W needs no introduction. It’s probably the most popular quartz watch in the world with something like 90 million total units sold. The Sensor Watch board replaces the F-91W’s original quartz movement with a new ARM Cortex M0+ powered brain. It uses the original LCD display, pushers and piezo-buzzer. The board is programmable and the Sensor Watch project provides a clean and easy to modify set of watchfaces and “complications” (little utility apps). There’s no Bluetooth radio to connect to other devices, but the combination of a lightweight tried-and-true utility watch case, with months long battery life and features you can rebuild at home is surprisingly powerful. In about an hour I was able to: replace the logic board, configure my 2FA secrets for my Google and Github accounts so I could get my most frequently used OTP codes right on my wrist and write a whole new ratemeter watchface for use as a rowing strokemeter or cadence meter! It’s a delight to hack on, and there’s even a wasm based emulator that makes testing on your computer easy and means you can play with my personal build right on this webpage →

Press MODE once to get to the 2FA token face. ALARM now cycles between Google and Github tokens. Don’t worry, I’ve replaced my real TOTP secrets with dummy values. Press MODE again to get to my new ratemeter watchface. Now start pressing ALARM periodically to measure the rate per minute of whatever you’re tracking. The remaining watchfaces in this build cycle through a world clock, a sunrise/sunset calculator, a moon phase indicator, a live read out from the temperature sensor in the watch, 24h setting picker, and time/date set mode. There are a bunch of other cool watchfaces in the Sensor Watch movement source tree including a pulsometer and orrery.

The process of upgrading the F-91W module has been well documented on John Graham-Cumming’s blog - I also ordered one of those cool orange watches to transfer my board into soon!

Here’s some info on how to get your TOTP secrets into the build and how I built the watchface:

TOTP watchface

This watchface generates time based one time passwords (two factor auth codes) allowing you to sign in securely to many popular websites (e.g. Google, Github). Time-based one-time password (TOTP) is a computer algorithm that generates a one-time password (OTP) that uses the current time as a source of uniqueness.

Press the Alarm button to cycle between your configured websites / TOTP secrets.

The watchface supports multiple websites / TOTP secrets, which need to be extracted from TOTP QR codes and added to the source code for the watchface as follows:

  1. Obtain a TOTP secret or QR code from the website you want to generate codes for.
  2. If you have just the QR code, Stefan Sundin’s web site will allow you to extract the secret - it will be an alphanumeric string around 32 characters long, which is the TOTP secret encoded in Base32.
  3. To add the secret to the watchface code, you need to convert it to hexadecimal bytes. This cryptii.com page will allow you to do that conversion. Note you’ll have to enter your TOTP secret in uppercase.
  4. Finally, you’ll need to take the hexadecimal bytes and add them to the TOTP watchface source code and recompile movement:

Edit totp_face.c

You may want to remove the demo keys. Assuming you want to add a key to the end of the list:

static const uint8_t num_keys = 2;

Add one to the number on this line.

static uint8_t keys[] = {
   // Add the hex bytes for your key
};

Add the hexadecimal bytes from step 3 to the end of this array, comma separated and each one preceeded by 0x. Don’t forget to add a comma after the previous final byte.

static const uint8_t key_sizes[] = {

Add the size of your secret (the number of hex bytes you just added) to the end of this array.

static const uint32_t timesteps[] = {

Add another 30 entry to the end of this array.

static const char labels[][2] = {

Add a label for your secret… E.g. if it’s for your Google account you might want to add { 'g', 'o' } as a friendly label.

That’s it - enjoy the convenience of TOTP codes on your wrist!

Writing a new watchface – ratemeter

You can find all the code for this watchface in this pull request I submitted to the main project.

Writing this feature was surprisingly simple - the implementation is pretty much all in this one main loop function.

bool ratemeter_face_loop(movement_event_t event,
                         movement_settings_t *settings,
                         void *context) {
    (void) settings;
    ratemeter_state_t *ratemeter_state = (ratemeter_state_t *)context;
    char buf[14];

This function needs to handle events for any button presses you want to handle as well as each tick of the clock.

    switch (event.event_type) {

The tick frequency is something your watchface can request if you want to time intervals or handle an animation or similar.

movement provides a utility function called watch_display_string which does its very best to render an alphanumeric string across the various 7+ segment elements on the Casio display. There are lots of foibles trying to map arbitrary strings onto this limited surface, but it’s all very clearly explained in the docs.

So, each of the states we care about in turn:

When the watchface is activated display “RA” in the day indicators.

        case EVENT_ACTIVATE:
            watch_display_string("ra          ", 0);
            break;

When the MODE button is pressed, move on to the next watchface.

        case EVENT_MODE_BUTTON_UP:
            movement_move_to_next_face();
            break;

When the LIGHT button is pressed, turn on the light!

        case EVENT_LIGHT_BUTTON_DOWN:
            movement_illuminate_led();
            break;

Now the real business… When the ALARM button is pressed:

  1. update the computed rate to display based on the interval between this and the previous button press.
  2. reset the tick counter (part of the bespoke state of this watchface which I defined).
  3. request a fast tick frequency (this constant is defined as one sixteenth of a second).
        case EVENT_ALARM_BUTTON_DOWN:
            if (ratemeter_state->ticks != 0) {
                ratemeter_state->rate =
                    (int16_t)(60.0 / 
                        ((float)ratemeter_state->ticks /
                         (float)RATEMETER_FACE_FREQUENCY));
            }
            ratemeter_state->ticks = 0;
            movement_request_tick_frequency(RATEMETER_FACE_FREQUENCY);
            break;

And finally, on every tick… Update the display to show the current rate or “Hi” if the rate is faster than 500 per minute and “Lo” below once per minute. Plus, increment the tick counter!

        case EVENT_TICK:
            if (ratemeter_state->rate == 0) {
                watch_display_string("ra          ", 0);
            } else {
                if (ratemeter_state->rate > 500) {
                    watch_display_string("ra      Hi", 0);
                } else if (ratemeter_state->rate < 1) {
                    watch_display_string("ra      Lo", 0);
                } else {
                    sprintf(buf, "ra  %-3d pn", ratemeter_state->rate);
                    watch_display_string(buf, 0);
                }
            }
            ratemeter_state->ticks++;
            break;

That’s it - this was both easier and more fun than I expected.

If you enjoyed this, you might like to get your own Sensor Watch from Oddly Specific Objects - I’m not affiliated with them, I just think what Joey has made here is really cool!