Skip to content
Colin Wren
Twitter

Breathing easy with Awair and Electron

JavaScript, Technology6 min read

person sitting on bench
Photo by Sid Leigh on Unsplash

I recently bought an Awair, a nifty little box that reads the temperature, humidity, carbon dioxide, chemical and dust content in the room it is placed in.

I’d bought it as the girlfriend and myself had some issues with stuffiness in our bedroom and would wake up wheezing so I wanted to find out what was going on with our air quality.

It really helped us, the first night we turned it on it identified the problem, our CO2 levels were about six times that of what they should be at night time due to the windows being shut to keep the room quiet and the door was shut.

If your air quality takes a dive due to a particular reason, say you’ve just sprayed some harsh chemicals in the room, it will alert you both via a beep on the device and an alert on your phone (although my girlfriend gets freaked out when I know that she’s been dusting due to the alert I get).

Awair makes it easier to understand the air quality by showing the different sensor readings on a scale which I’ve now come to know at a glance if the room needs the window opened or the heating turned on to make things better.

Additionally Awair takes all the sensor readings and their positions on the scales and creates a score, which as long as it’s above 80 generally means things are ok.

The one thing I liked about the Awair, aside from the fact that it allowed me to breathe, is that it has an API so you can build your own app to analyse your data, so that’s what I did.

I bank with Monzo and found a really great toolbar app called BankBar by John Easton which allows you to see your balance and manage your account with Mac OSX’s toolbar which was built in Electron so I wanted to build something similar (read: I copied the idea).

awair toolbar
A demo of my toolbar app

Building an app with the Awair API

Awair’s API used to be private, with a series of libraries that reverse engineered the calls from the iOS app, but they’ve since opened things up and you can register for an API key via their developer portal.

The Awair API can be used via an access token for personal use or you can register an OAuth2 app if you want others to log in and access their data. The app I was building needed to use OAuth2 as I wanted people to be able to download it, log in and see their data.

OAuth2 Flows with Electron

The standard OAuth2 flow is as follows:

  • You send the user to a page that they can log into, this page has your app’s credentials and a redirect URL it will send the user to once they’ve authenticated
  • The redirect URL is sent an authorisation code which is used by your app when requesting an access token or refresh token
  • You then make a call the API with your authorisation code to get an access token which you use to authorise your app’s requests for the user’s data
  • You include the access token in a header or as a param when making calls to the API, if the access token is expired you request a refresh token to continue being able to access the user’s data via the API

Modern operating systems on both desktop and mobile have the ability to associate protocols with applications, this means that calls to awairbar:// will be associated with my app AwairBar.

Electron allows you to set your app to be the default handler for a protocol using the app.setAsDefaultProtocolClient() function (you don’t need to include the :// part here).

You then use app.on('open-url', (event, url)) which will receive the URL use with the protocol. For redirects from OAuth2 apps you can use (provided that the data it’s sending is in the request parameters) this to get the response from the authorisation code request.

Access and refresh tokens

Awair has a really good guide on handling the OAuth2 lifecycle but the basics of it are once you’ve got your authorisation code from the redirect you can fire that off along with your app’s client id, client secret and the type of token you’re requesting.

This will return an object containing the access token which you’ll then use to make the subsequent API calls.

Getting data from the Awair API

The Awair API documentation is pretty thorough, containing the usual OpenAPI functionality so it’s easy to pick up and consume.

The main endpoints I needed for my app were:

http://developer-apis.awair.is/v1/users/self/devices which returns a list of all the Awair devices associated with the user’s account. You’ll need to get this list for getting readings from devices as those endpoints require both the deviceTypeand deviceId properties.

http://developer-apis.awair.is/v1/users/self/devices/:device_type/:device_id/air-data/latest which returns the latest readings from the device at time of calling it. This returns an object with the time of the reading, the Awair score for the reading as well as raw values from the different sensors and the raw values place on a scale of 1–5 (1 being good).

Building a toolbar app with electron

As I mentioned before I was ‘inspired’ by BankBar to build a toolbar app as I think it’s a nice way of having the data accessible, out of the way and the life cycle is easier to manage.

Luckily for me building a toolbar (or Trayto use the electron term) app is pretty straight forward.

Creating a Tray app

To build a tray app you just create a new Tray instance, which takes a path to the tray app’s icon (on Mac OS X you can provide icon.png but if you’ve got icon@2x.png in the same directory it’ll use that for retina displays).

The tray will only show an icon on the toolbar so you’ll need to combine that with a Menu and use tray.setContextMenu() to have that menu associated with the tray.

Menus are pretty simple to create thanks to Menu.buildFromTemplate() which takes an array of objects, each object acting as a menu item that can also have it’s own submenus.

Each menu item you define has a label and you can define a click() function to handle clicking that menu item. There are also certain roles you can have menu items play which manage those actions without the need for extra code.

Both the tray’s image and title can be updated on the fly using tray.setTitle() and tray.setImage() respectively. As long as you have access to the tray instance you can call these at any time during your app’s lifecycle.

Persisting data

I’m using electron-store to persist the data my app uses. It offers a simple API for getting and setting values in the store and can store any JavaScript literal so there’s no need to stringify and parse a JSON object to store complex values.

One thing with electron-store I found tricky was when writing unit tests for code that imported a dependency that created a Store instance, this was because the constructor required the code to be running within the Electron app lifecycle, which my tests were not.

To bypass this issue I leveraged Jest’s great mocking functionality with this mock:

1export const setMock = jest.fn();
2export const getMock = jest.fn();
3
4const mock = jest.fn().mockImplementation(() => {
5 return {
6 set: setMock,
7 get: getMock
8 };
9});
10
11export default mock;
Mocking the electron-store constructor

I could then import setMock and getMock from electron-store to access the mock functions I’m providing and assert on them being called or set values for them to return in my tests.

Recreating the Awair score graphics for the menu bar

The Awair score is visualised within the app by a ring around a circle, the ring has various levels of completion depending on the score.

In order to build this into my toolbar app I decided to only create graphics for every multiple of ten as due to the small screen real estate I figure most people wouldn’t see much difference at fine granularity.

I use Sketch for creating my graphics (if you don’t use it and you’re on a Mac I’d highly recommend it, it’s cheap and really great for making mock ups) and found this tutorial by InVision, essentially though it boils down to using gaps and dashes for the border and some Pi related maths to get the dash to only extend to the required percentage.

Summary

I’m really happy with how easy Electron made it to create a desktop app with JavaScript. I was able to get the app in a rough working state within an evening and the tricky part was dealing with electron-store and how to test it.

I’d like to write some tests for the app with Spectron, which is Electron’s UI Automation framework based on Chromedriver but I’ve not looked into how easy this would work with a Tray and Menu based app.

If you’d like to try it out the code for the app is my Gitlab (you’ll need to register your own OAuth2 app with Awair’s developer portal to run it locally).