Newer
Older
/* =========================================================================================
*
* CS408 Environmental Monitoring Independent of Existing Infrastructure
* Copyright (C) 2021 Callum Inglis
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Contact: Callum.Inglis.2018(at)uni.strath.ac.uk
*
* =========================================================================================
*
* Libraries Used
* ArduinoJson v6 Library - https://arduinojson.org/v6/doc/ (https://github.com/sandeepmistry/arduino-LoRa/blob/master/LICENSE)
* LoRa Library - https://github.com/sandeepmistry/arduino-LoRa/blob/master/API.md (https://github.com/bblanchon/ArduinoJson/blob/6.x/LICENSE.md)
* SHTSensor - https://github.com/Sensirion/arduino-sht (https://github.com/Sensirion/arduino-sht/blob/master/LICENSE)
* CCS811 - https://wiki.keyestudio.com/KS0457_keyestudio_CCS811_Carbon_Dioxide_Air_Quality_Sensor
*
* =========================================================================================
*
* Add board to Arduino IDE - http://arduino.esp8266.com/stable/package_esp8266com_index.json
*
#include <SPI.h>
#include <LoRa.h>
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
// Definitions, helper files, etc
#include "config.h"
#include "_sht.h"
#include "_co2.h"
#include "_lora.h"
#include "_pms.h"
#include "_transmission.h"
int pollEventCount = 0; // Number of times data has been sampled in this recording period
int pollEventCountPerSensor[NUM_SENSORS] = {0}; // Number of times each sensor has been polled in this recording period (Used for averaging values)
double totalValuePerSensor[NUM_SENSORS] = {0}; // Store reading from each sensor here. We will then average later on
SHTSensor sht; // Temp / Humidity (SHT) Sensor [ic2]
SoftwareSerial pmsSerial(2, 3); // Particle (PMS) Sensor [Serial]
CCS811 co2Sensor; // CO2 Sensor [i2c]
struct pms5003data pmsData; // Struct for PMS Sensor Data
void resetCounters() {
pollEventCount = 0;
for (int i = 0; i < NUM_SENSORS; i++) {
pollEventCountPerSensor[i] = 0;
totalValuePerSensor[i] = 0;
/**
* Provided sensor data, construct JSON object ready for transmission, Averaged over numSamples
*
* @param messageID Number of messages sent since startup
* @param numSamples Numer of samples averages
* @return DynamicJsonDocument sensorData
*/
DynamicJsonDocument prepareSensorData(int messageID, int numSamples) {
DynamicJsonDocument doc(2048);
JsonObject metadata = doc.createNestedObject("sensorMetadata");
metadata["uid"] = getSensorUID();
metadata["messageID"] = messageID;
metadata["samplePeriod"] = numSamples;
JsonObject data = doc.createNestedObject("data");
JsonObject ppm = data.createNestedObject("ppm"); // Particulates
ppm["p10"] = getAverageValue(IDX_PPM10);
ppm["p25"] = getAverageValue(IDX_PPM25);
ppm["p100"] = getAverageValue(IDX_PPM100);
JsonObject sht = data.createNestedObject("sht"); // Temp, Humidity
sht["temperature"] = getAverageValue(IDX_TEMPERATURE);
sht["humidity"] = getAverageValue(IDX_HUMIDITY);
JsonObject co2 = data.createNestedObject("co2"); // Co2
co2["co2"] = getAverageValue(IDX_CO2);
/**
* Add recorded values to the total, do not store false/failed/invalid values, e.g. -1
*/
void addValueToTotal(double newValue, int idx_sensor_count) {
}
pollEventCountPerSensor[idx_sensor_count] += 1;
totalValuePerSensor[idx_sensor_count] += newValue;
/**
* Get average value for a particular sensor, exluding any error values
* If there are no valid values, return -1 to indicate error
*/
double getAverageValue(int idx_sensor_count) {
if (pollEventCountPerSensor[idx_sensor_count] == 0) { // Ensure we do not divide by 0 (e.g. 0 valid values)
return -1;
}
// Otherwise divide total by number of times we sampled this value
return totalValuePerSensor[idx_sensor_count] / pollEventCountPerSensor[idx_sensor_count];
void setup() {
delay(1000);
Serial.begin(115200); // Console Debug
pmsSerial.begin(9600); // Partical Sensor
// Setup Hardware TODO Handle broken sensor!
if (!setupSHT(sht)) { /*while(1);*/ } // Temp/Humidity - Die on Error
if (!setupCO2(co2Sensor)) { /*while(1);*/ }
if (!setupLoRa()) { /*while(1);*/ } // Die on error
}
void loop() {
delay(POLLING_FREQUENCY);
pollEventCount++;
double instantTemperature = getTemperature(sht); // Prevent polling of sensors too frequently when debugging
double instantHumidity = getHumidity(sht);
double instantCo2 = getCo2(co2Sensor);
int instantPPM10, instantPPM25, instantPPM100;
if (!readPMSdata(&pmsSerial, &pmsData)) { // Error reading PMS Data, ignore on this round
instantPPM10 = instantPPM25 = instantPPM100 = -1;
} else {
instantPPM10 = pmsData.pm10_standard; // Standard (_standard) or Environmental (_env) readings for Particulate Data
instantPPM25 = pmsData.pm25_standard;
instantPPM100 = pmsData.pm100_standard;
Serial.println(); Serial.print(String(pollEventCount) + ") "); Serial.print("PM 1.0: "); Serial.print(instantPPM10); Serial.print("\t\tPM 2.5: "); Serial.print(instantPPM25); Serial.print("\t\tPM 10: "); Serial.print(instantPPM100);
Serial.print("\t\tTemp: "); Serial.print(instantTemperature); Serial.print("\t\tHumidity: "); Serial.print(instantHumidity); Serial.print("\t\tCo2: "); Serial.print(instantCo2);
// Add to running total, we will divide by the number of times samples later on to get an average over sample period
addValueToTotal(instantTemperature, IDX_TEMPERATURE);
addValueToTotal(instantHumidity, IDX_HUMIDITY);
addValueToTotal(instantCo2, IDX_CO2);
addValueToTotal(instantPPM10, IDX_PPM10);
addValueToTotal(instantPPM25, IDX_PPM25);
addValueToTotal(instantPPM100, IDX_PPM100);
// If we should now transmit
if (pollEventCount >= TX_AFTER_N_READINGS) {
if (DEBUG) { // Print average values over this period
Serial.println(""); Serial.print("Avg ppm10: "); Serial.print(getAverageValue(IDX_PPM10)); Serial.print("\t\tAvg ppm25: "); Serial.print(getAverageValue(IDX_PPM25)); Serial.print("\t\tAvg ppm100: "); Serial.print(getAverageValue(IDX_PPM100));
Serial.print("\t\tAvg Temp: "); Serial.print(getAverageValue(IDX_TEMPERATURE)); Serial.print("\t\tAvg Humidity: "); Serial.print(getAverageValue(IDX_HUMIDITY)); Serial.print("\t\tAvg Co2: "); Serial.print(getAverageValue(IDX_CO2));
Serial.print("\t\tChip ID: "); Serial.println(getSensorUID()); Serial.println("");
DynamicJsonDocument sensorData = prepareSensorData(msgCount, pollEventCount);
// Transmit
if (transmitData(sensorData)) {
return; // It all worked, values reset, now record new values
}
// Transmission failed, handle re-tries
int numRetries = 1;
waitRandomDelay(TX_RESERVATION_TIME*2); // Introduce random delay to avoid another collision
while (!transmitData(sensorData) && numRetries < MAX_TRANSMISSION_RETRIES){
numRetries++;
Serial.println("[-] Failed to send packet, retrying. Attempt " + String(numRetries) + " of " + String(MAX_TRANSMISSION_RETRIES) + "\n");
waitRandomDelay(TX_RESERVATION_TIME*2); // Introduce random delay to avoid another collision
}
// We were able to transmit after retries, values reset, now record new values
if (numRetries < MAX_TRANSMISSION_RETRIES) {
return;
}
// Failed to transmit - Don't Clear Counters, record more values then try to retransmit on next send
Serial.println("[-] Failed to send packet, max retries reached. Aborting");
return;