Announcing Fitbit OS SDK 4.1
chevron down

Announcing Fitbit OS SDK 4.1

Fitbit OS SDK 4.1

We're proud to announce the latest version of the software development kit for Fitbit OS.

SDK 4.1 adds a new API for calendar access, Always-on Display API, detecting a user's primary goal, and some rather useful enhancements to the Document API.

SDK 4.1 support requires the new Fitbit OS 4.1 firmware update which is now available for all in-market smartwatches.

With this release we deprecated SDK 3.1 as we previously described in our deprecation blog post. While existing published projects will continue to work for users, you should now switch to SDK 4.1 for updates, or new projects.

Note: Access to the Always-on Display API is currently invite-only. Read more below.

Calendars API

It's always exciting to announce new APIs, but this one is extra special and has amazing potential for both clocks and apps alike.

The Companion Calendars API provides developers with a read-only view of calendar events from all calendars on a user's mobile phone. We used this new API at Fitbit to create the Agenda app that launched with Fitbit OS 4.1.

The API provides access to multiple calendar data sources on the phone, including IMAP and Microsoft Exchange servers, and each data source can contain multiple calendars. Calendar events are retrieved by searching and iterating this promise based API.

Here's a basic example which retrieves all events, from all calendars, within the next 48 hours:

if (me.permissions.granted("access_calendar")) {
  calendars
    .searchSources()
    .then(sources => {
      console.log("All calendar data sources");
      return calendars.searchCalendars();
    })
    .then(results => {
      console.log("All calendars");
      const start = new Date();
      const end = new Date();

      // 48hr window
      start.setHours(0, 0, 0, 0);
      end.setHours(47, 59, 59, 999);
      const eventsQuery = {
        startDate: start,
        endDate: end
      };

      return calendars.searchEvents(eventsQuery);
    })
    .then(eventData => {
      // All events
      console.log("All events for the next 48 hours");
    })
    .catch(error => {
      // Inform the user about the error
      console.error(error);
      console.error(error.stack);
    });
}

Event data contains all of the properties you'd expect for a calendar event, including: title, description, location, start/end date, all day, status, organizer, attendees, alarms, and recurrence.

{
  "alarms": [
    {
      "trigger": {
        "offset": 900000,
        "related": "start"
      },
      "type": "alert"
    }
  ],
  "isAllDay": false,
  "attendees": [
    {
      "name": "Jeffrey Reiner",
      "status": "accepted"
    },
    {
      "name": "Peter Falk",
      "status": "accepted"
    }
  ],
  "calendarId": "22",
  "description": "This will be a great meeting!\n\n",
  "endDate": "2020-01-05T15:46:09.857Z",
  "id": "269",
  "location": "MARS-1-G-Rover-10",
  "organizer": {
    "name": "Jeffrey Reiner",
    "status": "accepted"
  },
  "sourceId": "22",
  "startDate": "2019-12-10T15:46:09.857Z",
  "status": "tentative",
  "title": "Fun times - part 2",
  "userStatus": "unknown"
}

We've added a small amount of sample data for use in the Fitbit simulator which will cover the common use cases, but for more advanced applications you'll want to use a real device with your real calendar.

For further information, check out the reference documentation, and the sample calendar clock.

Always-on Display API

You may have noticed the shiny new Always-on Display (AOD) clocks category in the Fitbit App Gallery and might be wondering how you can enable your own clocks for AOD?


AOD mode in Weather Land by reno

We have extended the Display API to include some new methods and properties to detect and respond to AOD.

Here's what the API looks like in action:

import clock from "clock";
import { display } from "display";

// does the device support AOD, and can I use it?
if (display.aodAvailable && me.permissions.granted("access_aod")) {
  // tell the system we support AOD
  display.aodAllowed = true;

  // respond to display change events
  display.addEventListener("change", () => {
    // Is AOD inactive and the display is on?
    if (!display.aodActive && display.on) {
      clock.granularity = "seconds";
      // Show elements & start sensors
      // someElement.style.display = "inline";
      // hrm.start();
    } else {
      clock.granularity = "minutes";
      // Hide elements & stop sensors
      // someElement.style.display = "none";
      // hrm.stop();
    }
  });
}

We aren’t quite ready to roll this out publicly just yet due to the need to adhere to strict hardware requirements. The primary reasons are that incorrectly implemented AOD apps and clocks can greatly reduce battery life and/or cause physical damage to the display, or to the display integrated circuit, and we want to ensure the best experience for our users.

With this in mind, we've been working closely with a small group of our amazing community developers to push the limits of our platform, testing this new API, and helping us to improve our tooling and processes around the gallery review process.

For now, AOD is gated behind a restricted permission that needs to be granted to a developer profile, and also granted for each clock or app (once reviewed). Without these permissions, apps and clocks cannot be uploaded to Fitbit Gallery App Manager, or used on physical devices. You can experiment with AOD using the simulator, but it will not work when sideloaded on a real device.

There are some very strict review guidelines about what an app or clock can and cannot do in AOD mode, here's some of the key ones to note:

  1. A maximum 20% On Pixel Ratio (OPR) can be used.

  2. All hardware sensors should be stopped.

  3. Clocks can only refresh the screen once per minute, apps once per second.

  4. Clocks should not display stats as numbers, to avoid misleading users with stale data.

  5. Only outlines or checkerboard images should be used in AOD, as dense pixel areas can cause flicker.

Improvements to the simulator mean we can now toggle AOD mode, display the OPR percentage, and highlight which sensors are currently active.


Fitbit OS Simulator toggling AOD mode using Drips by Lignite

All existing AOD clocks and apps in the gallery have been through a rigorous manual review process to ensure they adhere to these criteria, and much more.

While we're continuing to improve the review process and tooling, we will begin reaching out to developers of popular apps and clocks to grant them the ability to enable AOD. The number of invites will be limited at first, and based purely upon criteria such as number of downloads, number of active users, and ratings.

We will keep you informed via Twitter and the forum when there is any news regarding changes to the widespread availability of this API.

User Activity Primary Goal API

Developers can now determine which type of activity (steps, distance, calories, active minutes or floors) is the user's main fitness goal. The Primary Goal API is a simple addition to the Device User Activity API which returns the name of the goal as a string.

If you're developing a clock, you could use this API to prioritize or highlight the statistic which is most relevant to the user.

import { me as appbit } from "appbit";
import { primaryGoal } from "user-activity";

if (appbit.permissions.granted("access_activity")) {
  console.log(`User's primary activity goal is ${primaryGoal}`);
}

Document API Enhancements

This set of enhancements to the Document API includes a revolutionary change to the usefulness of CSS class names, plus a couple of other useful additions.

  1. The class attribute is now settable! That's right, you can finally dynamically update the class property for an element.
import document from "document";

const element = document.getElementById("some-element");

if (element.class === "show") {
  element.class = "hide";
} else {
  element.class = "show";
}

Note: The property is .class not .className.

  1. All container elements now have a children property, making it easy to iterate through the child nodes.
const element = document.getElementById("some-element");

element.children.forEach(child => {
  console.log(child.id);
})
  1. SVG <line> elements now have a JavaScript property to set their strokeWidth.
const line = document.getElementById("line-element");
line.style.strokeWidth = 5;

Until Next Time

Follow @fitbitdev on Twitter, and join our Fitbit Community Forum to keep in touch, 24/7. Curious to see the amazing work Fitbit Developers have done so far? Keep tabs on #Made4Fitbit on Twitter.