Is your house making you sick?
The connection between substandard living or working environments and higher rates of illnesses and injuries is long established. Exposure to high temperatures can cause headaches, dehydration, and dizziness. Some symptoms are more subtle. For instance, the morale, productivity or decision-making abilities of people in the space can be adversely affected.
The long term effects of living in a poor environment can include the development of more severe conditions including respiratory illnesses (such as asthma) and hypertension.
The discussion over living conditions is an ongoing one here in New Zealand, particularly in winter. Many houses - especially rentals - are damp with insufficient insulation. The local news sites carry stories of people suffering in sub-par accommodation:
In July this year, the Residential Tenancies (Healthy Homes Standards) Regulations 2019 came into effect. These standards are designed to provide a safer living space for tenants and introduce "specific and minimum standards for heating, insulation, ventilation, moisture ingress and drainage, and draught stopping in rental properties".
For my entry in the Sensing the World Challenge, I decided to build an indoor environmental quality monitor. I wanted to find out whether my home could be affecting my health.
Measuring indoor environmental quality
I did some research into the factors that are needed for a "healthy home", and arrived at the Guidelines for Healthy Housing, published by the World Health Organisation (WHO) in 1998. This document highlights (among other things) the importance of a comfortable environment to a person's wellbeing and the figures within the document are quoted by other governmental and non-governmental organisations as best-practice for healthy living spaces.
I learned there are 3 main ingredients to a healthy home:
- Keep your home warm - the World Health Organisation suggests a minimum of 18 degrees Celsius in living areas or 21 degrees in the bedroom
- Keep moisture out - damp air can encourage mould growth, which in turn can cause respiratory problems
- Keep the air clean - poor ventilation, smoking inside, or overuse of cleaning products and air "fresheners" can affect the air quality
This meant I needed to measure these 3 factors:
- Temperature
- Humidity
- Air quality
In addition, I decided to measure some other metrics that I thought would be useful to determine the "health" of an environment:
- Dew point - this can be approximated using the temperature/humidity readings and can help provide an indication of comfort
- Ambient light - poor lighting can affect eye health
- Ambient low frequency noise - high background noise levels can contribute to stress or anxiety, and some studies suggest a link between traffic noise and high blood pressure
The hardware
The Avnet Azure Sphere board comes with a number of onboard sensors including ambient light, temperature, air pressure, and 3-axis accelerometer. For expansion, it provides 2 Mikroe expansion slots, and also a grove connector. The Mikroe slots are very flexible, offering analog and digital I/O as well as 3.3V/5V power. Potentially, by utilising off-the-shelf Click boards, I could build a proof of concept without needing a soldering iron.
The best way to extend the functionality of the Azure Sphere is through the Click board sockets. These sockets are versatile, providing power at both 3.3 and 5 V, and can support analogue and digital I/O as well as the ubiquitous I2C/SPI/UART protocols. The Mikroe website lists hundreds of different Click boards, but I elected to purchase a Environment Click board. This board seemed to be ideal for my project, as it provided temperature/humidity/pressure/VOC gas all in one chip.
I also bought the Mic Click board in order to monitor ambient sound. To measure ambient light, I chose to use the on-board ambient light sensor.
The firmware
The Avnet Starter Kit demo project was the starting point for my Azure Sphere application, but my final code is heavily modified. There was a large learning curve associated with mastering the specifics of the Azure Sphere platform. This wasn't helped by the multiple changes to the Azure Sphere SDK that occurred during the project. I converted the high-level application to build using CMake, as Microsoft will be dropping support for the VS project format in the near future.
The firmware consists of 2 applications. The high-level application is responsible for the overall orchestration: it collects sensor data from the real-time app and directly from the I2C sensors, and then passes this up to Azure IoT. The real-time application handles all ADC operations. Current versions of Azure Sphere SDK do provide an ADC API for high-level applications, but I opted to use the real-time core for these tasks as I didn't want to block the high-level application during ambient noise sampling.
Measuring ambient air quality
The sensor on the Environment Click board is a Bosch BME680. The advantage of choosing this sensor was the availability of the BSEC library which takes the uncompensated temperature/humidity/air pressure/gas resistance readings from the sensor and provides processing to produce a measure of IAQ (indoor air quality). This library is closed-source but Bosch provide a number of pre-compiled libraries for various architectures, including Cortex-A7.
The first hurdle was the BME680 driver. After filling in the necessary stubs so the driver could read/write over I2C, I was puzzled as to why the data returned from the sensor was sometimes wrong. My first assumption was that my code was bad, but eventually I realised it was an issue with the Azure Sphere libraries themselves. My blog post at the time highlights my frustrations and a workaround. Microsoft promise the fix is coming to Azure Sphere OS 19.11 (due in Dec 2019).
Integrating the BSEC library was probably the most time-consuming part of this project. I initially thought it would be a simple case of linking a pre-built library to my high-level app, but the linker spat out a number of errors when I tried it. I spent quite a bit of time trying to overcome these problems, including overriding default Azure Sphere options. After a protracted period, I concluded that Bosch don't actually provide a library that is compatible with Linux on a Cortex-A7. I then contacted Bosch in the hope of getting a version of the library that would work for Azure Sphere, but unfortunately they weren't able to provide one in time. They suggested two alternatives:
- Host the BSEC library externally, e.g. on a hosted VM
- Try the M4 version of the library on the real-time core.
Neither of these was ideal, but in order to move forward I opted for the second option. Here's the basic process I ended up with:
This is essentially a convoluted work-around for the unavailable library but hopefully in the future Bosch will provide a library that can be used with the Cortex-A7 high level applications.
Bosch provides a table of how to interpret the IAQ number that is output by the BSEC library:
Measuring ambient noise
Besides temperature and humidity, noise pollution can also be detrimental to the comfort level and health of a person:
I bought the Mic Click board in order to measure ambient noise. This board has a MEMs microphone and a couple of stages of gain before the analogue output appears at the Click socket.
When integrating analog sensors with the Avnet Azure Sphere board, it is important to take heed of the output voltage range of the sensor. It's easy to think that just because the sensor fits in the Click socket that it's compatible with the Avnet board, but that's not necessarily the case. By design the Mic Click outputs 0-3.3V, but the MT3260 analog inputs can only handle 2.5V. To reduce the output from the Click board, I added a resistor across the output. Ideally you would use a 300k resistor, but in my case I used 200k, giving me an output of 0-2.2V from the Mic Click board.
Once the board was modded, I had to decide how best to sample ambient noise. On a "normal" micro-controller (where the full datasheet is public), you would be able to configure the ADC in conjunction with interrupts, timers and DMA to automate this process without loading the CPU. The available documentation on the Azure Sphere is insufficient to accomplish this at present, although I am certain the hardware is fully capable. Instead I have to run the ADC in polling mode, triggering each ADC sample manually, and blocking the core as I did so. This meant it made more sense to do this acquisition with a RTCore app, and pass the result to the high-level app for aggregation and dispatch.
The kind of sound I was interested in measuring was long-term low-frequency continuous noise (e.g. traffic, HVAC). I chose a sample time of 100ms, which is not long enough to block the real-time core for too long, but long enough to be able to eliminate any transient sounds (e.g isolated claps, knocks).
I acquire N samples, subtract my DC offset, and calculate the mean-square to give me a sense of the average energy of the signal:
I threshold this number to determine the noise pollution level. For a rough calibration I measured the average audio energy in 3 environments:
- Low ambient noise - Quiet home
- Moderate ambient noise - my work environment with HVAC, other people
- High ambient noise - Outdoors with steady traffic
Getting all this going proved more difficult than expected, mostly due to inadequate documentation of the Cortex-M4 core. Using the Azure Sphere example ADC project as a base, I modified the GPT3 code to leave that timer free-running so I could use it for accurate timing.
The cloud
After running through the excellent Azure Sphere tutorials from Brian Willess and testing the Azure Sphere starter kit with both IoT Hub and IoT Central, I chose to use Azure IoT Central as the base for my cloud platform. I created a new custom application, added a new device template and populated all the fields I intended to report. This whole process is extremely straight-forward and relatively painless - full credit to Microsoft for making it so easy.
When creating the IoT Central app, you can use the 7-day free trial, but I would recommend using the Pay-As-You-Go plan. You won't have to pay anything for this application, as long as you have less than 5 devices and send under 50,000 messages a month. Meeting these conditions is easy if you only have one device. Sending a message once every 60 seconds equates to less than 45,000 messages, so there's some buffer for additional events. My IoT Central dashboard has now been running for well over a month and it hasn't cost me anything.
The firmware sends the following measured and calculated values up to Azure:
- IAQ number
- Air quality state (based on the above table)
- Temperature
- Humidity
- Air pressure
- Dew point (calculation based on https://www.omnicalculator.com/physics/dew-point#howto)
- Ambient noise level
- Ambient light
Because I am sending IAQ up as a state, then its value is displayed as a colour-coded bar, so you can see the current indoor air quality at a glance.
The important parameters were added to my device dashboard.
I wanted to be notified when my environment became bad, so I configured a number of rules in my device template. IoT Central will send me an email if the temperature goes above or below the recommended thresholds.
An interesting observation is you can't set rules based on "state", only on events and telemetry data. I then decided to modify my firmware to send add additional events (e.g. when my IAQ went bad) so I could trigger notifications from those events.
Video and demo
Link to code
All code for the project is available at https://github.com/jkuek/EnvironmentMonitor_AzureSphere
You will also need to download the BSEC library from Bosch (it's free but the license prevents me from including it in my repository).
Future improvements
- Potentially you could use this device in an outdoors environment
- Adding a smoke sensor would turn this into a smoke alarm
Acknowledgements
Thanks to:
Top Comments