Breathing easy with Awair and Electron
— JavaScript, Technology — 6 min read
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).
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 deviceType
and 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 Tray
to 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:
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).