Air Quality Index Fitbit App Released

Anyware, LLC Fitbit app for the display of relevant air quality data for ozone and 10 & 2.5 micron particles has been approved and published on the Fitbit Gallery.

This app helps you to know the air quality in your area before exercising outdoors. The app displays the current air quality index (AQI) using the official U.S. AQI, a color-coded index designed to communicate whether air quality is healthy or unhealthy for you. Data are provided by AirNow, which is a one-stop source for all air quality data. Using your location provided by your phone, the app finds the nearest reporting station and displays the AQI for ozone and particles 10 & 2.5 microns or less. Touch on a reported value to see the value description as provided by the Environmental Protection Agency.

To use this app, you must register and create an account at AirNow. AirNow will provide you a unique API key that is entered under the app settings on your phone. Enter the API key as provided by AirNow including all dashes. This app uses this API key to get the most relevant reported AQIs using your phone’s GPS and network connection. Instructions for obtaining an API key and configuring the app are found here.

The app only works in the U.S. and some locations in Canada and Mexico. The user needs to be within 250 miles of a reporting station used by AirNow.

Fitbit SunRise SunSet App Issue [Resolved August 20, 2022]

This published Fitbit app utilizes an API provided by to get the sunrise and sunset times for a given date and location. As of August 9. 2022, the sunrise-sunset API https call has an invalid certificate creating an issue. Fortunately a user notified me of this problem. Since the API certificate is invalid, returned data is throwing an error that wasn’t being properly handled in the code [Note: updated their certificate allowing the API to respond properly].

I am currently working on an update to the companion code to handle the unexpected error, which is discussed below. Even with this error handling, if the API certificate is not resolved, this will required a change in how the sunrise and sunset times are obtained. Currently my plan is to publish an app update by October 1, 2022 [Note: An app update to handle malformed JSON responses was published August 24, 2022, Version 1.1.0].

This issue with the unexpected error is found in the companion app (the code that runs on the Fitbit app on your phone). In the Fitbit architecture, messages are passed between your watch and the companion app (see this Fitbit guide for more information). The companion app provides the gateway to the Internet. The API call uses fetch() with the api calling string. The response should be a JSON string, which is parsed and the data sent back to the watch for display.

The solution to handle the unexpected error is simple. I added a “catch” statement to handle the error and pass an error indicator back to the watch. The companion code segment is show below. This code waits for a message from the watch to get sunrise and sunset data. The companion app builds the api string and then makes the api call using fetch(). The JSON string components are extracted and sent back to the watch for display. The catch sends back-100 lat and lon to indicate an error has occurred.

// Listen for messages from the device
// Message has the date we need, companion has everything else
messaging.peerSocket.onmessage = function(evt) {
  if ( {
    console.log(" " + JSON.stringify(;
    if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
      // build api string
      var apiString = "" + lat + "&lng=" + lng + "&date=" +;
      // go to the web to get sun rise/set data that is returned in a json string
        .then(function(response) {return response.json();})
        .then(function(json) {
        console.log("Got JSON response from server:" + JSON.stringify(json));
          getDate:, lat: lat, lng: lng, rise: json.results.sunrise, set: json.results.sunset});
      .catch(function(err) {
        console.error('Error during fetch ' + err.code);
          getDate:, lat: -100, lng: -100, rise: 0, set: 0}); // network data error occurred

When the watch receives a message with the error indicators, the watch displays that an error has occurred but continues to allow the user to change dates and try again.

Calculating Moon Phase

Recently I published a Fitbit app that calculates the Moon phase and then displays the proper phase image and illumination value. The user can select a different date or simply increment the date using buttons. Some feedback I received was that the app was fast and they wanted to understand why?

First, the app performs all calculations on the watch. No phone resources are need No GPS. No Internet. The calculations are based on a dateNow date object (this object is settable by the user though the interface that is not part of this discussion).

The calculation is based on an article I found by SubsySTEMs while performing an Internet search. Their paper broke the Moon phase calculation into a series of steps, which was easy to follow and translated directly to JavaScript. The output of their calculation is the number of days since the last New Moon. From there the Moon phase is determined. There are eight Moon phases (in order from New):

  • New
  • Waning Crescent
  • Third Quarter
  • Waning Gibbous
  • Full
  • Waxing Gibbous
  • First Quarter
  • Waxing Crescent

Using the days since the last New Moon you can find the phase but beware, each phase isn’t just 1/8 of 29.53 days. Shown below is the JavaScript that implements the attached paper.

function calMoonPhase() {
  // calculate moon phase for date
  let month = dateNow.getMonth() + 1;
  let year = dateNow.getFullYear();
  if(month < 3) {
    year = year - 1;
    month = month + 12;

  let a = Math.floor(year/100);
  let b = Math.floor(a/4);
  let c = 2 - a + b;
  let d = dateNow.getDate();
  let e = Math.floor(365.25*(year+4716));
  let f = Math.floor(30.6001 * (month + 1));
  let jd = c + d + e + f - 1524.5;
  let daysSinceNew = jd - 2451549.5;
  let newMoons = daysSinceNew / 29.53;

  <...Determine Moon phase...>
  <...Determine image to show...>
  <...Calculate illumination...>