In this project, we developed an Indoor Air Quality (IAQ) monitoring system. It utilizes Arduino-based sensors to collect data, Azure cloud services to manage and store this IAQ data and a custom-made app made using MIT App Inventor to manage the Bluetooth communication on the mobile device side.
Our sensor is divided into two parts: a Communication Unit and a Sensor Unit, this allows the system to be utilized in many ways. The sensor unit can be carried around independently of the communication unit and connects to the user's mobile device using an app, allowing the sensor to work as a smartwatch for air quality. When connected to the communication unit and with more sensors in different rooms, it can form a network and store the data in the cloud, allowing easy access in one place, via a website, for a homeowner or a facilities manager to visualize how the air quality changed. Furthermore, user still can use their mobile app to connect the sensors and as they move from room to room throughout the day the app register where the user was and it is able to show them their personal IAQ data, such as their average exposure.
In this part, you will assemble the entire physical part of the sensor needed to run the code available at the GitHub Repo. As this is a big step it can be further broken down into more manageable steps.
- Printing the Case.
- Assembling the Perfboard.
- Assembling the Connector.
- Putting in the Case.
Printing the Case
The files for the case can be found in the GitHub Repo at the bottom of the post and should be printed with supports, as a suggestion you should print them with their openings facing up.
Assembling the Perfboard
While the case is being printed you can work on the circuit, for this part, you will need:
- Perfboard 7cmx9cm
- Male and female headers
- Flexible wires
- Heat-shrink tubes
- Solder and soldering iron
- 4 Adafruit Stemma QT cables
Start by soldering the six-pin female headers to support both sides of the SCD-30, these headers are needed to have enough space for the cables between the sensor unit and the communication unit. We soldered our header 14 pins apart, but it does not need to be exact, as long the sensor is well-supported it will work fine.
Then place the PMSA at the other end of the board, making sure that the air exhaust and intake are facing the edges of the perfboard. After that solder the BME680 closest to the PMSA as shown in the picture below.
Next, you need to solder a wire to the set pin of the PMSA and a female header to the other end of that wire (don't forget the heat-shrink tube), this allows the user to turn off the sensor when not using it, saving some battery.
AssemblingThe Connector
A connector is required to send the data between the communication and sensor unit. The schematic below shows which pins need to be connected.
To build this connector you will need:
- 4 short wires.
- 4 medium wires
- 1 Female 8-pin headers.
- 1 Male 8-pin headers.
- 1 Female 2-pin headers.
- 1 Female 3-pin headers.
- 3 Female 1-pin header.
- 16 Heat-shrink tubes.
(note: use multicore wires as they will need to bend a lot later)
Start by soldering 4 short wires to the 8-pin male header. On the other end of the wires solder a 2-pin female header on the 1st and 2nd pins (from the edge of the 8-pin header) and two 1-pin female headers on the 3rd and 4th pins. Also, don't forget to add the heat-shrink tubes.
note: in this case, I am using a 3-pin header instead of 2 pins header due to some issues on the communication unit, but solder the 2 pins header on the wires with the transparent heat-shrink, and the single-pin headers on the remaining 2.
After that, measure the four medium wires to be longer than the sensor case (X CMtall), this is important because it will be easier to place the perfboard and other components inside the case.
The following step is to solder the 8-pin female header to one end of the wires and on the other end, solder the single-pin header on the third wire from the edge of the 8-pin header, and the 3 pins header on the other end of the remaining wires. While doing this step make sure that the Pin A1 of the ESP32-S3 will be connected to the RX pin of the ESP32-S2, the GND and +5 lines, will be connected to the GND and USB pins, respectively, of the ESPs, as shown in the schematic before.
Putting in the Case
Back to the communication unit, slide the ESP32-S2 to its slot, then grab the male 8-pin header side of the connector and connect the 2-pin header to Rx and Tx pins, the 3rd (the shorter one) to the USB pin and the 4th wire (longer wire with single-pin header) to the GND pin. The next step is to hot glue the connector to the communication unit case, in this part use both cases as reference to ensure the connector will be glued to the right dept, avoiding gaps when the units are connected.
Similar to how you glued the male side of the connector repeat the process for the sensor unit.
Now it is time to put the components inside the sensor case, start by connecting the SCD-30 to the ESP32-S3 using a STEMMA QT cable and if you haven't connected the sensors before, chain them using the STEMMA QT cables. Then, align the perfboard with the rails, making sure that the SCD-30 sensor is toward the bottom of the case, and that the connector wires run between the sensor and the perfboard, as shown in the image below.
Proceed to connect the wire of the connector as shown in the schematic above, then connect the PMSA set pin to D12, and the battery to its designated port. (Be careful with the battery polarity, as the orientation of the connector is NOT standardized)
After that, slide everything to its position. (You likely will have to bend the female headers for everything to fit properly)
Finally slide the lid and the sensor and communication will be complete.
While users can check data directly on the sensor, it truly shines when the information is accessible where it is needed most. To ensure this, we will utilize Microsoft Azure, Cosmos DB, and a custom website, enabling users to monitor all sensors from a single location and integrate the data into automation systems. For this part you will need to:
- Set up the Azure IoT Hub.
- Set up Azure IoT device.
- Set up CosmosDB.
- Create a local host for the website.
Setting up the Azure IoT Hub
Azure IoT Hub is a management service hosted in the cloud that acts as a central message hub for communication between an IoT application and its attached devices. Azure IoT Hub allows you to "securely connect, monitor, and manage billions of devices to develop Internet of Things (IoT) applications."
Devices can connect to Azure IoT Hub using the following protocols: HTTPS, AMPQ and MQTT. Azure also provides SDKs for many programming languages to abstract these protocols.
Azure IoT Hub
- Goto portal.azure.com.
- Login into your Azure account.
- On the navigation panel on the left click “Create a resource”.
- Then click the category of “Internet of Things” and click “IoT Hub” service.
- You will be prompted to select a subscription, resource group, region and IoT Hub name in the screenshot below. Click “Review + Create” to continue. Wait a few minutes for the IoT Hub to be created and deployed.
- Once on the overview page, record the hostname (e.g., IotHubName.azure-devices.net).
Azure IoT device
- Add Azure IoT devices.
- On the home page, click the IoT Hub that has been created.
- On the left panel, Click “Devices” under “Device Management”.
- Create the “Add” button to add a new device.
- Enter a name for the device. Use “Symmetric key” as the Authentication type. Then click the “Save” button to create the device.
- On the devices page, you can see the device list. Click the device you have created. Record the “Device ID” and “Primary key” which will be used for the sensor device to connect to the Azure IoT Hub.
The IoT Hub cannot store incoming messages so these must be forwarded to other Azure services. IoT Hub routing is added where specific Azure services can be connected as an endpoint, including the built-in endpoint, event hub, service bus, and storage account, blob storage. Currently, a native endpoint for CosmosDB has been made available. This takes the pain away of having to set up extra resources between the IoT Hub and CosmosDB, just to transport messages from one resource to another, which is mostly done using a Stream Analytics job or custom Azure Functions.
Create a CosmosDB account
- On the Azure IoT home page, search “Azure Cosmos DB”. Click “Create”.
- A wizard is started as below. Fill in the necessary in each step.
Finally, review the creation and start the creation of the account. The account will be created after a few minutes.
Then you create a container inside the database.
Create the CosmosDB routing endpoint
- Open IoT Hub resource. Click “Message routing” under the “Hub settings” category on the left panel.
- Click “Custom endpoints” and click “Add”. On the Add a route page. Choose the endpoint type as “Cosmos DB”. Enter the Endpoint name and Cosmos DB account.
Creating a local host for the website
To access the data in a more user-friendly way you can download the website we wrote for this sensor from our GitHub Repo below. To get the website running you will need to complete some steps:
- Install Python 3.9 (Microsoft Store Link).
- Inside the project folder, run the following command: pip install -r requirements.txt.
- Open cosmosdb.py and add your endpoint, database name and container name.
- Run server.py.
- Open azure_client.html.
For now, the website is empty because none of the sensors is sending any data, once you are done with everything and the sensor and communication units are running, data will start to appear on the website. note: The website only works while the sever.py is running.
Note 2: This website could be hosted online, but we opted for this local host because this was a proof of concept, and we didn't think it was worth paying for a domain and host.
Communication Unit CodeThe communication Unit is responsible for receiving the data from the sensor unit and sending it to the cloud. We opted for this design so that it is easier to upgrade the sensors without having to change or modify how the communication with the server operates. To set up this part you have to complete the following steps.
- Install libraries.
- Configure the settings.
Installing Libraries
- On Arduino IDE, open library manager.
- Search for Azure SDK for C library by Microsoft and click install.
- Open boards manager in Arduino IDE.
- Install or change esp32 by Espressif Systems to version 2.0.17 (in our test newer version of the board manager would not compile).
Configurations
To use the Azure SDK for C library to connect to Azure IoT Hub, with the Arduino IDE open, an example can be found at File/examples/Azure SDK for C/Azure_IoT_Hub_ESP32. Several settings need to be done to let it work.
- Open iot_configs.h.
- Set WiFi ssid to IOT_CONFIG_WIFI_SSID.
- Set the WiFi password to IOT_CONFIG_WIFI_PASSWORD.
- Set Azure IoT Hub name to IOT_CONFIG_IOTHUB_FQDN.
- Set IoT Hub device ID to IOT_CONFIG_DEVICE_ID.
- Set the Primary key of the device to IOT_CONFIG_DEVICE_KEY.
// IoT Configs
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
// Wifi
#define IOT_CONFIG_WIFI_SSID "Your SSID"
#define IOT_CONFIG_WIFI_PASSWORD "Your Password"
// Azure IoT
#define IOT_CONFIG_IOTHUB_FQDN "[Your Azure IoT Host Name].azure-devices.net"
#define IOT_CONFIG_DEVICE_ID "Device ID"
#define IOT_CONFIG_DEVICE_KEY "Device Key"
// Publish 1 message every 2 seconds
#define TELEMETRY_FREQUENCY_MILLISECS 2000
//------------------------------------------
Extra Steps
When using the code provided in the Github Repository at the bottom of this page there are a few extra steps before you can upload the code.
- Make the modification mentioned above on the file located at Communication Unit\iaqazure\src\azure\iot_configs.h.
- For each device, you should set up a unique iothub fqnd, device id and device key.
- In the file Communication Unit\iaqazure\iaqazure.ino you should change PART_ID for each communication Unit.
- Copy the folder Uofc_Iaq_Library located at Communication Unit\iaqazure\lib to your Arduino libraries folder.
- Inside iaqazure.ino PART_IOT is defined as 1 and that each unit has unique PART_ID
- Upload the iaqazure.ino file to the communication unit.
The sensor unit is responsible for collecting the data, sending it to the communication unit and broadcasting it through Bluetooth, allowing it to be used like a smart thermostat, when connected to the communication unit, by supplying the data to a smart-home system. It can also be used as a smartwatch by connecting to a mobile device and collecting personal air quality. To achieve all these functions the code can be broken down into the following parts:
- Including libraries.
- Initializing the sensors.
- Reading the sensors.
- Sending data to the communication unit.
- Sending data through Bluetooth.
Including libraries
Before we can start with the Sensor unit code, you will need to install the esp32 board manager by espressif if you have not done that in the previous section, install the Bosch BSEC 2 Library, and copy the iaqbsec.h header file from the GitHub Repo below, into the same folder containing the sketch you will be writing the sensor unit code code. Now, inside the Arduine IDE, install and include the following libraries.
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_GFX.h> // Core graphics library
#include "Adafruit_ST7789.h" // Hardware-specific library for ST7789
#include <Adafruit_SCD30.h> // CO2 Sensor
#include <Adafruit_PM25AQI.h> // PM Sensor
#include <HardwareSerial.h> // Inteface with Dr. with the communication unit
#include "iaqbsec.h" // VOC & Pressure Sensor
#include <BLEDevice.h> // Bluetooth Low Energy Librabries
#include <BLEAdvertising.h> // Bluetooth Low Energy Librabries
#include <vector>
As there are many include and definitions you can add them to a separate header file and only include this header in the main file.
Initializing the sensors
To initialize the sensors you first need to create their objects and then you can call their begin function.
//Modules Initialization
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); // Display
Adafruit_SCD30 scd30; //CO2 Sensor
Adafruit_PM25AQI aqi = Adafruit_PM25AQI(); //PM Sensor
PM25_AQI_Data pmsa0031; //Data for the PM sensor
IaqBsec* iaqbme = new IaqBsec();// Pressure and VOC Sensor
//----------------------------------------------
void setup(){
// turn on the TFT / I2C power supply
pinMode(TFT_I2C_POWER, OUTPUT);
digitalWrite(TFT_I2C_POWER, HIGH);
delay(10);
//initialization SCD30
Serial.println("Iniating CO2 Sensor");
if (!scd30.begin()) {
Serial.print("Failed to find SCD30 chip");
while (1) { delay(10); }
}
Serial.println("SCD30 Found!");
scd30.setMeasurementInterval(60); //time in seconds
Serial.print("Interval: ");
Serial.print(scd30.getMeasurementInterval());
Serial.println(" S");
//----------------------------------------------
//initialization PMSA0031
pinMode(PMSA_SET, OUTPUT); // Any digital pin works, we used pin 12
digitalWrite(PMSA_SET, HIGH);
Serial.println("Iniating PM Sensor");
delay(3000); //Wait for Sensor to boot up
if (! aqi.begin_I2C()) { // connect to the sensor over I2C
Serial.println("Could not find PM 2.5 sensor!");
while (1) delay(10);
}
Serial.println("PM25 found!");
digitalWrite(PMSA_SET, LOW); // keep in mind the sensor cannot be used
//when is set is LOW
//----------------------------------------------
//initialization BME688
Serial.println(F("Iniating VOC Sensor"));
iaqbme->setup();
//----------------------------------------------
//initialization Display
pinMode(TFT_BACKLITE, OUTPUT); // turn on backlite
digitalWrite(TFT_BACKLITE, HIGH);
tft.init(135, 240); // Init ST7789 240x135
tft.setRotation(3);
tft.fillScreen(ST77XX_BLACK);// any color in hex works
//----------------------------------------------
}
Reading the sensors
Collecting the data from the sensor is a little tricky because the BME688 requires you to call its read function with less than a second interval, while you will usually read the other sensors less frequently, and, the PMSA requires a few seconds to get ready for reading its data. This is one way to implement that, the iaqbme->read is called every 500 ms, while the other sensors are read every minute, and the PMSA is activated 5 seconds before being read and turned off right after that.
// Reading the sensors
#define SAMPLING_PERIOD 60000 // 1 minutes sampling period
unsigned long last_time = millis(); // Timer collect the data every minute
void loop() {
// Reads VOC Sensor
iaqbme->read(); //This should be called every 500ms
if(millis()-last_time > SAMPLING_PERIOD-5000) //55 seconds
digitalWrite(PMSA_SET, HIGH); //Enables the PM Sensor 5 S before measuring
//give some time for the sensor start working again
if(millis()-last_time > SAMPLING_PERIOD){
// Reads CO2 Sensorts
if (scd30.dataReady()){
if (!scd30.read()){
Serial.println("Error reading CO2 data");
delay(1000);
return;
}
}
// Reads PM Sensor
if (! aqi.read(&pmsa0031)) {
Serial.println("Error reading PM data");
delay(1000);
return;
}
digitalWrite(PMSA_SET, LOW); // Disables the Sensor
// Data Collection
//-------------------------------------------
// For this part all variables are floats
// Collect the data from SCD 30
data_package.temperature = scd30.temperature;
data_package.humidity = scd30.relative_humidity;
data_package.co2 = scd30.CO2;
// Collect the data from PMSA 0031
data_package.pm1dot0 = pmsa0031.pm10_env;
data_package.pm2dot5 = pmsa0031.pm25_env;
data_package.pm10 = pmsa0031.pm100_env;
// Collect the data from BME 688
data_package.gasTemperature = iaqbme->bme.temperature;
data_package.gasHumidity= iaqbme->bme.humidity;
data_package.gasPressure = iaqbme->bme.pressure/100.0; //Convert to hPa
data_package.gasResistance = iaqbme->bme.gas_resistance/1000.0; //Ohms -> Komhs
data_package.gasIaq = iaqbme->bme.iaq;
data_package.gaseVoc = iaqbme->bme.evoc;
data_package.gaseCo2 = iaqbme->bme.eco2;
//-------------------------------------------
last_time = millis();
}
delay(500);
}
Expanding on the PMSA, this sensor is the most energy-hungry out of them, consuming around 100mA, so if you plan to use it on a battery it’s a good idea to use the set pin to turn on only before reading it. With some testing, we could enable the sensor 2.5 seconds before reading. However, we didn’t have the tools to determine how much time it needs to give the most accurate reading, so feel free to experiment with this interval.
Sending data to the Communication unit
Next, to send the data to the communication unit, we decided to use the serial, on the second serial pins the ESP 32 has, but you can use the Tx and Rx pins, with a different baud rate than the Serial used to communicate with the computer.
//Serial Communication
#define RXD2 18
#define TXD2 17
HardwareSerial Interface(1); //Data Interface
void setup(){
Interface.begin(9600, SERIAL_8N1, RXD2, TXD2);
}
void loop(){
// Format
//T=26.24,RH=28.83,P=886.46,CO2=496.52,VOC=0.58,PM1.0=0.00,PM2.5=0.00,PM10=0.00,[\n]
message = "T=" +
String(data_package.temperature) + ",RH=" +
String(data_package.humidity) + ",P=" +
String(data_package.gasPressure) + ",CO2=" +
String(data_package.co2) + ",VOC=" +
String(data_package.gaseVoc) + ",PM1.0=" +
String(data_package.pm1dot0) + ",PM2.5=" +
String(data_package.pm2dot5) + ",PM10="+
String(data_package.pm10) + '\n';
Interface.println(message);
}
//----------------------------------------------
The following snippet is how the data is shown on the display
// Configurations
tft.fillScreen(ST77XX_BLACK);
tft.setTextSize(2);
tft.setCursor(0, 0);
tft.setTextColor(ST77XX_WHITE);
// Printing to the screen
tft.print("Temp : "); tft.print(data_package.temperature); tft.println(" *C");
tft.print("RH : "); tft.print(data_package.humidity); tft.println(" %");
tft.print("Pres : "); tft.print(iaqbme->bme.pressure/100.0); tft.println(" hPa");
tft.print("CO2 : "); tft.print(data_package.co2); tft.println(" ppm");
tft.print("eVOC : "); tft.print(data_package.gaseVoc); tft.println(" PPM");
tft.print("PM 1.0: "); tft.print(data_package.pm1dot0); tft.println(" ug/m3");
tft.print("PM 2.5: "); tft.print(data_package.pm2dot5); tft.println(" ug/m3");
tft.print("PM 10 : "); tft.print(data_package.pm10); tft.println(" ug/m3");
Sending data through Bluetooth
The sensor unit is also capable of sending data through Bluetooth. To accomplish that we decided to use the legacy advertisement and share the data as part of the complete local name because it was the only way we could find the device when using an app created with MIT App Inventor.
#define BLE_INTERVAL 5000
#define SENSOR_ID 'X' // Change the X for the sensor ID (0-9)
// Configurations
esp_ble_gap_ext_adv_params_t legacy_adv_params = {
.type = ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND,
.interval_min = 800,
.interval_max = 800,
.channel_map = ADV_CHNL_ALL,
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
.filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
.primary_phy = ESP_BLE_GAP_PHY_1M,
.max_skip = 0,
.secondary_phy = ESP_BLE_GAP_PHY_1M,
.sid = 2,
.scan_req_notif = false,
};
static uint8_t legacy_adv_data[] = {
0x02, 0x01, 0x06, //Flags
0x02, 0x0a, 0xeb, //Tx Power
22, 0x09, //lenght, Complete Local Name
'I', 'A', 'Q', '_', 'S', 'e', 'n', 's', 'o', 'r', '_', SENSOR_ID, ';', //[20]
'0', '0', '0', '0', '0', '0', '0', 0X0
};
static uint8_t legacy_scan_rsp_data[] = {
22, 0x09,
'I', 'A', 'Q', '_', 'S', 'e', 'n', 's', 'o', 'r', '_', SENSOR_ID, ';', //[20]
'0', '0', '0', '0', '0', '0', '0', 0X0
};
uint8_t addr_legacy[6] = {0xc0, 0xde, 0x52, 0x00, 0x00, 0x03};
BLEMultiAdvertising advert(1); // max number of advertisement data
// Extra Variables
unsigned int last_ble = 0;
String sdata = "";
int ble_i = 0;
void setup(){
BLEDevice::init("");
advert.setAdvertisingParams(0, &legacy_adv_params);
advert.setAdvertisingData(0, sizeof(legacy_adv_data), &legacy_adv_data[0]);
advert.setScanRspData(0, sizeof(legacy_scan_rsp_data), &legacy_scan_rsp_data[0]);
advert.setInstanceAddress(0, addr_legacy);
advert.setDuration(0);
delay(1000);
advert.start(1, 0); // Start advertising with instance count 1 and inf duration
}
void loop(){
if(millis() - last_ble > BLE_INTERVAL){ //sends the Bluetooth data every 5s
switch(ble_i){
case(0):
sdata = "T=" + String((int)round(data_package.temperature)) + ';';
break;
case(1):
sdata = "H=" + String((int)round(data_package.humidity)) + ';';
break;
case(2):
sdata = "P=" + String((int)round(data_package.gasPressure)) + ';';
break;
case(3):
sdata = "C=" + String((int)round(data_package.co2)) + ';';
break;
case(4):
sdata = "V=" + String(data_package.gaseVoc) + ';';
break;
case(5):
sdata = "O=" + String((int)round(data_package.pm1dot0)) + ';';
break;
case(6):
sdata = "W=" + String((int)round(data_package.pm2dot5)) + ';';
break;
case(7):
sdata = "N=" + String((int)round(data_package.pm10)) + ';';
break;
}
Serial.println(sdata);
BLEDevice::stopAdvertising(); //Stop Advertised to updated data
for(int j = 0; j <= sdata.length(); j++){
legacy_adv_data[j+21] = sdata[j];//Copy the data to the name of the device
// +21 to skip to the space for the data
legacy_scan_rsp_data[j+15] = sdata[j]; //Copy to the scan response
// +15 to skip to the space for the data
}
advert.setAdvertisingData(0, sizeof(legacy_adv_data), &legacy_adv_data[0]);
advert.setScanRspData(0, sizeof(legacy_scan_rsp_data), &legacy_scan_rsp_data[0]);
delay(500);
advert.start(1, 0); // Restart advertising to apply the new data
ble_i++;
if(ble_i == 8)
ble_i = 0;
last_ble = millis();
}
}
//----------------------------------------------
The sensor ID is important to be changed and matched to the communication unit so that it is easier to know which sensor the user was close to and to enter the data for the personal IAQ data.
The full code can be found in the GitHub Repo below, before using it open iaq_sensor.h and make sure that:
- BLE_MODE is set 2.
- SENSOR_ID matches the same id from the communication unit.
In order to receive the Bluetooth data from the sensor you will need your own app. For this part, you will be using MIT App Inventor, as it is simple and easy to create an app using this platform.
Start by adding a vertical arrangement, and add labels, a list view, a list picker, a button, and a switch into this arrangement, as shown in the picture below.
Note: You can leave elements, like a disconnect button, invisible as they don't serve a purpose until the mobile device is connected to the sensor unit.
Proceed to add non-visible components, such as clocks, and a spreadsheet, which you can set up by following this guide, then set the scan clock to fire every 2.5s and the upload clock to 60s.
Next, initialize a few variables that will be useful later.
Before we start working on the Bluetooth Low Energy (BLE) extension, you need to understand how data will be obtained. Since this sensor will be sitting in a room and lots of people might want to receive data from this sensor, you can't actually connect to the sensor because the ESP-32 only support a few connections simultaneously, so you will only keep scanning and as long you are close to the sensor the mobile device will be able to find the sensor and read the data from the name of the device, which is cycling through the different air quality parameters.
Now, it is time to start working with the BLE extension, download the aix file for BluetoothLE Extension and add it to MIT App Inventor, then add the Bluetooth Low Energy invisible component.
Next, add these blocks to your app's logic, for it to be able to scan for BLE devices, and add the block of the second picture so that you can check the devices found.
When a device is found it is added to the stringDeviceList in the following format:
DeviceAddress DeviceName RSSI,
After that, you need to create functions to handle what to do when the sensor is found and lost. When a device is found it should record its address, set the connection status to true, record the connection time, reset the disconnection counter, and update some of the labels to show information to the user. When a device is lost it should record the disconnection time, add a new entry on the spreadsheet reporting when the user "connected" and "disconnected" to a sensor, reset the variables that store the IAQ data, and update some of the labels.
Then, you can create the logic to manage the connection with the sensors, this logic should be able to find the closest sensor to the user, which you can use the RSSI to accomplish that. The RSSI is a number from -100 to 0 with -100 being too weak for connection and 0 being the strongest signal (usually it will sit on -60 for nearby devices). Also, the logic should be able to handle switching to a closer sensor, as sometimes the sensors will not be far from each other. Another factor to consider is that sometimes the app might not find the device in one scan cycle so you should allow it to try to find the device more than once before considering that the user is far from the sensor and "disconnect. (in the code below I allowed 10 tries, but you can try with fewer tries)
After that, you create a function to extract the data, which will be in the following format.
Another_Device
, 00:00:00:00:00 IAQ_Sensor_X;
T=00
;0 -60,
Other_Device
To avoid looping again through the device list, you can find the index of the address of the device and split the text to contain only the section with data.
Note: In these photos, I show only the logic for temperature, but you can copy the blocks for temperature, paste them below on the else if and make small changes to collect the remaining data.
Finally, you set up the scan clock to call these functions when it fires, and the upload clock to send the data the mobile app read to another tab of the spreadsheet.
If any of the parts are not clear, you can check the comments in the project (.iai) file in the GitHub Repo.
Limitations
There are some limitations to this app due to the way it is programmed and due to it being made using MIT App inventor, these limitations are:
- When moving from room to room the app must be open.
- The app can be closed for a while but not for too long or it will reset and the data will be lost.
- It only uploads the current data to the spreadsheet when the app is open.
To ensure that everything is working you can connect both the Sensor unit and the communication unit to your computer and check the Serial Monitor, for each time the sensor unit reads new values it also sends them to the serial monitor. The communication unit also uses the serial monitor as a log console by printing the result of each attempt to send a message to the database. Also, the data in the communication unit should match the data in the sensor unit.
Then, you can run the sever.py mentioned when setting up the cloud, open the website through the HTML file, choose the sensor from the dropdown button, and the next time the sensor collects data the graph should update.
Feel free to modify any parts of the code and test with different values, as these values are the ones that worked best for my setup, but they might not be the best for yours. I hope that you were able to create your own IAQ system and if you need help feel free to ask in the comments.
I would like to thank Dr. Simon Li who sponsored us by providing the needed components and accompanied the entire progress of this project. Thanks to my partner, Dr. Sheng Li, for participating in this project and assisting me. Also, thanks to Abduljalel Wadouh and Ibrahim Hashmi who worked in this project in the previous years and provided a base for us to expand upon and last but not least the University of Calgary Electrical Makerspace for supplying us with materials, and allowing us to use its spaces and equipment to bring this project to reality.
Comments