About the project
My original plan
I work at a PC all day. Whilst I try to make sure I get some exercise at other times, it's easy to spend the day being fairly sedentary. My original idea for a "Sensing the World" project was to measure my heart rate at various intervals throughout the day and to insist that I recorded at least one event that was 20% above my resting heart rate. This might mean that I cycle in to work. Maybe I'll run up the stairs coming back from lunch and quickly take a reading to prove it. I could even get really strict lock my PC if these criteria weren't met. No code for you, lazy bones! I'm not sure what my boss would think if I could complete some urgent work on time though.
As with many project, things didn't quite turned out as I expected. Unfortunately just as I was starting the project I was knocked off my motorbike by a careless driver. I ended up with 5 broken ribs, a shoulder blade broken in 3 places and a bruised lung. With multiple broken ribs I could hardly breathe properly. In fact during my hospital stay a nurse frequently took my vitals (heart rate, blood pressure, blood oxygenation) and would tell me that if my blood oxygen (SpO2) wasn't high enough I wouldn't be allowed to go home and I really wanted to go home. It needed to be above 95% and I struggled to get it above 80% at times. I spent 3 days in hospital but eventually - after promising to do some breathing exercises - I managed to escape and get myself home. One thing was for certain though. I wouldn't be running up stairs any time soon.
A new plan
I'd already ordered a Heart Rate 4 Click board to do the heart rate monitoring and I noticed this also takes SpO2 measurements. I sensed a slight change in tack. I could make sure that as I sat at my PC typing with one hand, I kept breathing properly and kept my SpO2 up! The project could continue.
Architecture
I decided that it would be based around the Avnet Azure Sphere Development kit and Heart Rate 4 click. These could send measurements of both my Heart Rate and SpO2 up to Azure. I would then need an application on my PC that could nag me to take readings and ensure they were good enough. I initially looked at IoT Central for graphing the data. Unfortunately, whilst IoT Central lets you view and graph data, it doesn't seem possible to then query this data remotely. That leaves IoT Hub. As its name suggests IoT Hub it designed for routing data - it can be passed to Azure Storage, Azure Service Bus or to Time Series Insights. Time Series Insights also allows you to view and graph data, but also has a facility to query the data externally. Perfect.
I mulled over the best way to communicate with the Azure Sphere device. For outgoing measurements, the standard way is to use the standard azure_iot_utilies.c code as demonstrated brilliantly by bwilless in his Azure Sphere blog series Avnet's Azure Sphere Starter-Kit (Out of Box Demo) Part 2 of 3 . This gives a nice AzureIoT_SendMessage method that allows us to send data on to IoT Hub and through to Time Series Insights. That will do.
As far as instructing the device to take a measurement we have two ways to communicate with Azure Sphere - either a Direct Method Call or a Cloud to Device Message . They differ in that one is a synchronous call that gives you a reply and the other is asynchronous fire-and-forget (with a later "message received" call if required). To be honest either will do. Now that I am confident I can do what I need I'll leave that decision until a bit later.
Well, that's my broad approach and architecture sorted. I may not know exactly how this is all going to work yet, but I have a direction at least. Let's get coding!
Project components
Azure Sphere
Hardware
It's often easiest to start at the back and work forwards. The hardware consists of just and Avnet Azure Sphere Starter Kit (as I'm sure does everyone else's Sensing the World project) to which I've added a Heart Rate 4 Click board to mikroBUS socket 1. After a little bit of work getting the Click OLED-C to work with Azure Sphere I added this too.
So just 2 click modules to slot in - no soldering required! Hmmm. Unfortunately not. It turns out that both devices use pin 2 of their respective mikroBUS sockets and that both sockets connect this to GPIO2. The other mikroBUS pins seem to be distinct GPIOs or a selectable shared bus. I have no idea why this one clashes, but it does. It means that I can't use the OLED-C alongside either the HeartRate 4 or NFC modules I have.
After getting the code working for the OLED-C and seeing that nice colour screen, I didn't want to drop it. I decided there had to be a simple solution, and here it is. I removed pin 2 to isolate if from the Azure Sphere board and jumpered across to the adjoining unused pin. Now the EN (enable) pin on my OLED-C is a non-standard but more useful GPIO28. I've kept the pin in case I ever want to put it back. Obviously it would have been possible to use some jumper wires to rearrange the connection a bit, but I wanted it to sit nicely on the board as it was intended to rather than dangling in mid-air.
After a little bit of modification I can now slot these in next to each other on the Avnet Azure Sphere Starter Kit and they both work.
Code
The code for Azure Sphere starts off very much like the demo code linked to above. We initialize the I/O and the I2C communication with the Heart Rate 4 board. Then we sit in a loop pinging Azure IoT Hub to keep a connection alive. Either a button press or a Cloud to Device Method will trigger taking a heart rate and SpO2 reading.
This is a snippet of the main code loop:
int main(void) { // Init GPIO and I2C init_gpio(); init_i2c(); oledc_spiDriverInit((T_OLEDC_P)0, (T_OLEDC_P)0); oledc_init(); show_splash_screen(); // Initialize Heart Rate 4 sensor maxim_max30102_i2c_setup(hr4_read_i2c, hr4_write_i2c); if (!AzureIoT_SetupClient()) { Log_Debug("ERROR: Failed to set up IoT Hub client\n"); return; } // Wait for any incoming Cloud to Device messages AzureIoT_SetMessageReceivedCallback(&messageCall); // Everything else is driven from an incoming direct method call while (!terminationRequired) { // Blink green LED to show we're running and connected to Azure IoT Hub GPIO_SetValue(greenLedFd, GPIO_Value_Low); nanosleep(&sleepTime1s, NULL); GPIO_SetValue(greenLedFd, GPIO_Value_High); nanosleep(&sleepTime1s, NULL); // This needs to be called frequently in order to keep active the flow of data with the Azure IoT Hub AzureIoT_DoPeriodicTasks(); if (takingReadings) continue; // Button A will trigger a heart rate / O2 reading GPIO_Value_Type buttonAState; int result = GPIO_GetValue(buttonAGpioFd, &buttonAState); if (buttonAState == GPIO_Value_Low) takeReadings = true; if (takeReadings) { // Take a reading takingReadings = true; takeReadings = false; GPIO_SetValue(redLedFd, GPIO_Value_Low); if (get_hr4_readings(&heart_rate, &o2)) { // Send reading to Azure IoT Hub snprintf(jsonBuffer, JSON_BUFFER_SIZE, JSON_TEMPLATE, heart_rate, o2); Log_Debug("Sending: %s\n", jsonBuffer); AzureIoT_SendMessage(jsonBuffer); } GPIO_SetValue(redLedFd, GPIO_Value_High); takingReadings = false; } }
As far as detecting the incoming Cloud to Device method goes, that's actually surprisingly simple - especially as we aren't trying to parse any incoming parameters. If the payload is "Read" then we're good to go.
/* Process incoming Cloud to Device Method */ void messageCall(const char* payload) { // If it's "Read" then take a reading if ((strcmp(payload, "Read") == 0) && !takingReadings) takeReadings = true; }
Azure IoT Hub
Setting up Azure IoT Hub is fairly simple. rather than go into detail about how to do this, i will once again refer you to the excellent guide that got me there. You need to register your device with IoT Hub and copy the primary connection string. This will be used by your device to connect to IoT Hub for the "I'm here" AzureIoT_DoPeriodicTasks() pings and the reading passed back using AzureIoT_SendMessage() calls in the code above.
Time Series Insights
As I mentioned at the start of this blog post, I decide that Time Series Insights was the best way to both check my data visually and to be able to query the same data. As usual, Microsoft have documented this well. Once again, things move quickly. I found that using the Preview version of TSI made things a lot easier. Obviously if you're reading this a few months after I have written it, these features may have become standard.
First of all, you need to create your TSI environment. The things to look out for here are:
- Select the PAYG tier! For me, the S1 Tier was esitmated at £111.80 per month and S2 at £1006.17. So far, my PAYG cost has been negligible.
- Choose you Time Series ID appropriately as it can't be changed later. I chose deviceId as that's all I needed.
Secondly, you need an Event Source - although this can be added later, Chose your existing IoT Hub, with a access policy as iothubowner and a consumer group of $Default. You should find that this is all that's needed to create your Time Series Environment.
Viewing your environment is a little odd. You need to select it and click the "Go to environment" button. This takes you out of Azure and onto a completely separate site - https://insights.timeseries.azure.com/preview. To be honest it's a little odd finding your way around when there isn't any data to view, so I'd suggest pumping in a few events via Iot Hub before you do. Just make sure something has called AzureIoT_SendMessage() with some meaningful data. You will need to select Unassigned Time Series Instances, then the deviceId (fred in my case) to add some measurements to the display. As my data is a little sporadic it can look a little odd even if it is correct. It's really set up for large amounts of frequent and/or regular data. This is a view over the last working week.
Time Series Insights is obviously a very powerful tool for analysing trends in a typical production estate with hundreds or thousands of remote devices reporting back to IoT Hub. It seems a little overkill for measuring my heart rate, but it works well and I feel it's a good tool to have in your toolbox if you're working on a large IoT project.
When your goal is to learn, sometimes an inappropriate tool that's new to you is exactly the right tool for the job! Anyway, the main reason I chose to use Time Series Insights is the fact that you can also query this store of data. Let's move on to the final piece of the puzzle - our front end application which will do exactly that.
Windows application
I wanted to keep the Windows application as small and unobtrusive as possible. As such, it has no UI in normal use. I've set it to run automatically on startup and it sits in the notification area of the taskbar with a small heart icon.
The functionality of the desktop application is key to how the whole process works. It will periodically check to see whether a reading is required. I've decided that it should ask for a reading if it hasn't had one in the last 2 hours. Also, I didn't want to go away to lunch and find that the application had been repeatedly nagging me, so there's a nag limit. Once an hour is enough. If it's prompted for a reading in the last hour then it won't ask again, even if it didn't get one.
There's also a right-click context menu. This allows me to see today's stats or to manually request a reading. Today's stats are a summary of the range of readings for today. The request to take a reading fires off a Cloud to Device method via Azure Iot Hub which trigger the measurement process on the Azure Sphere device.
Source code
You will find all source code for this project at https://github.com/FredMurphy/SensingMyself
Demonstration
To give you a sense of how the combination of the application and device work in practice, here's a video of the application detecting that I've been a bit slack and not provided a recent reading. This prompts the Azure Sphere device to go into action and ask me to provide one.
I hope you've found this project interesting. I've certainly found it useful. It was far too easy to not take deep enough breaths due to the pain in my ribs when I did so. This makes sure I don't get too absorbed in whatever I'm doing and slip into the habit of taking shallow and ineffective breaths. I'll be sure to post an update once I'm ready for phase two!
Top Comments