Skip to content
Snippets Groups Projects
ESP8266_Transmitter.ino 8.55 KiB
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 <TimeLib.h>
#include <LoRa.h>
#include <SoftwareSerial.h>
#include "SHTSensor.h"
#include <CCS811.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
// Sensor Config
SHTSensor sht;                          // Temp / Humidity (SHT) Sensor [ic2]
SoftwareSerial pmsSerial(2, 3);         // Particle (PMS) Sensor [Serial]
CCS811 co2Sensor;                       // CO2 Sensor [i2c]
Callum Inglis's avatar
Callum Inglis committed

struct pms5003data pmsData; // Struct for PMS Sensor Data
// Reset poll counters 
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) {
  if (newValue == -1) {
    return;
  }

  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];
  delay(1000);
  
  Serial.begin(115200); // Console Debug
Callum Inglis's avatar
Callum Inglis committed
  Serial.println("\n\n[+] Transmitter Node");

  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++;
  // Get values from sensors
  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;
  if (DEBUG) { // Print instantanious values
    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("");
Callum Inglis's avatar
Callum Inglis committed
    }

    // Prepare Data For Send
    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;