Firmware V3 already?

A project log for Custom Smartwatch

An ESP32 Based Open-Source Smartwatch

Matthew James BellafaireMatthew James Bellafaire 05/23/2022 at 03:060 Comments

Wow, to think it’s been over a year since I last updated the logs for this project. Life got busy and I started slacking on keeping up with the logs, but that doesn’t mean a lot of cool things didn’t happen. In my last log (over a year ago) I talked about the V2 firmware and all the great changes it had over V1. Ultimately though the V2 firmware fell victim to the same issue that made me abandon V1, namely extensibility. Simply put, the V2 firmware never enforced a specific structure to adding features, had too many global variables, and generally required more time debugging than developing which became a pain and slowed down progress to a standstill. At the beginning of the year I began work on the V3 firmware with extensibility in mind to try and make the project something that was fun and interesting to work on.

There were a lot of basic features that this watch has been lacking in terms of usability, this firmware aims to address those. In the previous firmware versions much of the interaction came in the form of tap gestures alone, this limited the interactivity options. The lack of structure in the previous firmware also got in the way of creating any highly-interactive UI since every action that occurred on screen was manually written, resulting in a lot of spaghetti code that made life difficult.

Before jumping into how the new firmware works, some of the current high-level features are:
    • Improvements to the reliability of Bluetooth connections
    • Swipe gestures for navigating pages
    • Simple structure for creating drawable objects on the screen, with improved touch handling
    • Ability to download and display app icons to represent phone notifications
    • and of course it still has a calculator (with a backspace key now!)

With all the high-level stuff out of the way lets get into how this new firmware works.       

Firmware Overview

The main loop of the firmware operates in mostly the same way the previous firmware versions have. The watch initializes hardware, enters a loop and draws elements to the screen provided some wake-up conditions are met, then goes back to sleep when those conditions are no longer fulfilled. On each wake-up the watch polls the notifications and time from the companion app and stores those to memory. The fundamental change with this update is the way objects are drawn to the screen. In order to make expansion easier with the watch all screen items are encapsulated in classes which inherit from the Drawable class.

class Drawable
    Drawable(int x, int y, int width, int height, GFXcanvas16 *buffer_ptr);
    Drawable(int x, int y, int width, int height, GFXcanvas16 *buffer_ptr, String type);
    virtual void draw();
    int getx();
    int gety();
    int getWidth();
    int getHeight();
    int setDims(int x, int y, int width, int height); 
    virtual boolean isTouched(int x, int y);
    void setTouchable(boolean val);
    void registerCallback(void (*cb)());
    virtual void onTouch(int x, int y);
    String toString(); 

    String _type = "Drawable";
    int _x;
    int _y;
    int _width;
    int _height;
    int _ptouchx, _ptouchy;
    void (*_callback)() = nullptr;
    boolean _touchable = false;
    boolean _touched = false; //holds the value resulting from the last isTouched() call. 
    GFXcanvas16 *_buffer_ptr;

 The key functions for this class are the onTouch()  and draw() functions which are both virtual, and therefore can be overridden by any child of the Drawable class. Additionally, since all classes are extensions of Drawable we can store references to the class as a Drawable pointer and know that each class will contain a draw() and a onTouch() function. These objects can be registered to the Drawable Manager which handles all the basic functions of drawing to the screen. This way, the drawFrame() function for this firmware is simplified down into:

Since all Drawables have locations and dimensions on the screen the Drawable manager can also handle the triggering of touch related functions. All a programmer has to do to add another visual element is create a simple class that extends the Drawable class and create a draw() function then register the element. Every visual element is nicely contained in its own small class that can be easily moved, reused, or included in other elements to create more complex behavior. All these benefits probably come as no surprise to anyone who knows anything about object orientation, but the amount of difficulty this has saved is massive. It also sets the ceiling higher in terms of features that can be added since difficult arrangements of visual elements can be broken down and debugged individually.

The main advantage to this structured approach is that each Drawable object can manage its own animation and redrawing itself and doesn’t require any interaction with any kind of global variables. The time, background, and application notifications all operate pretty much entirely on their own and animate whenever the draw() function is called.
For navigation the watch now uses swipe actions. The user is able to swipe on the screen in one of 4 directions to change pages or perform any number of actions. Overall this just allows for the simplification of the interface and removal of navigation buttons that occupy precious screen real estate.

Notification Icons

Adding notification icons is a feature in the watch that I’ve wanted for a very long time. On the android companion app I originally added the ability to transmit app icons around this time last year. There are a lot of steps involved between getting the image off the phone to actually displaying it on the watch and it took many attempts to get right. Transferring the notifications from the phone to the watch is done using base64, to do this I utilized the base64 library created by Densaugeo.

The app icons themselves are only a resolution of 32x32, but given that they’re transmitted over BLE it still takes a few seconds to complete even one icon transfer. This makes it impractical to read the images from the phone whenever they’re needed. In order to avoid this as much as possible the icons are saved into SPIFFS after they’re received. When an icon is requested the ESP32 first checks whether it has a local copy of the app icon before requesting it over BLE. On the home screen the notification icons themselves are used to show which apps have current notifications. Along with the theme of creating a more interactive user interface each app icon on the home screen is touchable. Touching the icon inflates a text window which shows the title of the notification. If the user wants they can then swipe left and go to the notification reader to get more information on the watch.

Spotify Control

When Spotify is currently playing on the device the app is sent over to the watch as a notification. Thus the Spotify app icon is present on the home screen. By tapping the Spotify icon the media control window appears, which can be used to change songs or pause/play. Mostly the same as the previous version, but a little bit more lively.


The calculator is still a part of the firmware, there’s not much to say about it other than that it can be accessed by swiping up on the screen. It also now has a backspace button. I think of all the features on this watch, this one gets the most use.

See you in the next log!