Skip to content
Snippets Groups Projects
ESP8266_Transmitter.ino 8.87 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)
 *
 *  =========================================================================================
 */

#include <LoRa.h>
#include <SoftwareSerial.h>
#include "SHTSensor.h"
#define ss 16 // Physical Pin 16 = D0 (Default is Physical Pin 5)
#define dio0 15 // Physical Pin 15 = D8 (Default is Physical Pin 4)
static const int loraSpreadingFactor = 7;
static const int loraSignalBandwidth = 125E3;
static const int loraFrequency = 433E6;
static const int RXPin = 4, TXPin = 3;

// Temp / Humidity Sensor
SHTSensor sht;

// Particle Sensor Serial
SoftwareSerial pmsSerial(2, 3);

// LoRa Message Tracking
byte localAddress = 0xBB;
byte destination = 0xFF;
byte msgCount = 0;

// Sensor Values - Temp / Humidity
double temperature;
double humidity;
double avgTemperature = 0;
double avgHumidity = 0;

// Sensor Values - Co2
byte co2;
double avgCo2 = 0;

// Sensor Values - Particles
uint32_t ppm10;
uint32_t ppm25;
uint32_t ppm100;
uint32_t avgPpm10 = 0;
uint32_t avgPpm25 = 0;
uint32_t avgPpm100 = 0;

// Partial Sensor Data
struct pms5003data {
  uint16_t framelen;
  uint16_t pm10_standard, pm25_standard, pm100_standard;
  uint16_t pm10_env, pm25_env, pm100_env;
  uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
  uint16_t unused;
  uint16_t checksum;
};

struct pms5003data data;
Callum Inglis's avatar
Callum Inglis committed

// Polling / Transmitting Config
int pollingFrequency = 1000; // Polling Frequency, ms
int pollEventCount = 0; // Number of times data has been sampled in this recording period
int sendAfterPolls = 5; // Sample Period, after which Transmit Reading 
/**
 * Setup Humidity / Temperature Sensor
 */
bool setupSHT() {
  Wire.begin();

  if (!sht.init()) {
    Serial.println("[-] SHT Init Failed");
    return false;
  }
  
  sht.setAccuracy(SHTSensor::SHT_ACCURACY_MEDIUM);
  return true;
}

double getTemperature() {
  double t = -1;

  if (sht.readSample()) {
    t = sht.getTemperature();
    temperature += t;
  }

  return t; // -1 on error TODO Make this better!
}

double getHumidity() {
  double h = -1;

  if (sht.readSample()) {
    h = sht.getHumidity();
    humidity += h;
  }

  return h; // -1 on error
}

bool setupLoRa() {
  LoRa.setPins(ss, rst, dio0);  
  if (!LoRa.begin(loraFrequency)) {
    Serial.println("[-] Fatal. Starting LoRa failed!");
    return false;
  }

  LoRa.setSpreadingFactor(loraSpreadingFactor);
  LoRa.setSignalBandwidth(loraSignalBandwidth);

  Serial.println("[+] LoRa Initialized OK!");
  return true;
}

/* 
 * Return ESP Module ID, based on MAC address
 * Source: https://arduino.stackexchange.com/a/58678
 */
Callum Inglis's avatar
Callum Inglis committed
char* getSensorUID() {
  uint64_t chipid = ESP.getChipId();
  uint16_t chip = (uint16_t)(chipid >> 32);
  
  char sensorID[50];
  snprintf(sensorID, 50, "EMIEI-%04X%08X", chip, (uint32_t)chipid);
  
  return sensorID;
}

/**
 * Get data from PMS Partical Sensor
 * @param Stream s PMS Serial Connection
 * @modifies struct data PMS Sensor Data
 * @returns boolean Data Read Success status
 */
boolean readPMSdata(Stream *s) {
  if (!s->available()) {
    return false;
  }
  
  // Read a byte at a time until we get to the special '0x42' start-byte
  if (s->peek() != 0x42) {
    s->read();
    return false;
  }
 
  // Now read all 32 bytes
  if (s->available() < 32) {
    return false;
  }
    
  uint8_t buffer[32];    
  uint16_t sum = 0;
  s->readBytes(buffer, 32);
 
  // get checksum ready
  for (uint8_t i=0; i<30; i++) {
    sum += buffer[i];
  }
  
  // The data comes in endian'd, this solves it so it works on all platforms
  uint16_t buffer_u16[15];
  for (uint8_t i=0; i<15; i++) {
    buffer_u16[i] = buffer[2 + i*2 + 1];
    buffer_u16[i] += (buffer[2 + i*2] << 8);
  }

  // Struct it
  memcpy((void *)&data, (void *)buffer_u16, 30);
 
  if (sum != data.checksum) {
    Serial.println("Checksum failure");
    return false;
  }

  return true;
}

Callum Inglis's avatar
Callum Inglis committed
boolean transmitData(DynamicJsonDocument doc) {
  // TODO Include methods for error detection here
  
    LoRa.beginPacket();
    //LoRa.print(avgPpm10); LoRa.print(',');
    //LoRa.print(avgPpm25); LoRa.print(',');
    //LoRa.print(avgPpm100); LoRa.print(',');
    //LoRa.print(avgTemperature); LoRa.print(',');
    //LoRa.print(avgHumidity);
    
    //LoRa.write(localAddress);
//      LoRa.write(msgCount);
    //LoRa.write(outgoing.length());
    //LoRa.print(outgoing);
    serializeJson(doc, LoRa);
    LoRa.endPacket();

    return true;
}

  delay(1000);
  
  Serial.begin(115200); // Console Debug
  Serial.println("[+] Transmitter Node");

  pmsSerial.begin(9600); // Partical Sensor

  // Setup Sensors
  if (!setupSHT()) { while(1); } // Temp/Humidity - Die on Error

  if (!setupLoRa()) { while(1); } // Die on error
}

void loop() {
  // TODO Gather Sensor Data
  if (readPMSdata(&pmsSerial)) {
Callum Inglis's avatar
Callum Inglis committed
    pollEventCount++;
    
    Serial.println();
    Serial.println("---------------------------------------");
    Serial.println("Concentration Units (standard)");
    Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
    Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
    Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
    
Callum Inglis's avatar
Callum Inglis committed
    Serial.print("Temp: "); Serial.print(getTemperature());
    Serial.print("\t\tHumidity: "); Serial.println(getHumidity());
    Serial.println("-------------------" + String(pollEventCount) + "--------------------");
Callum Inglis's avatar
Callum Inglis committed
    // Add to Average
    // Standard (_standard) or Environmental (_env)
    ppm10 = ppm10 + data.pm10_standard;
    ppm25 = ppm25 + data.pm25_standard;
    ppm100 = ppm100 + data.pm100_standard;

Callum Inglis's avatar
Callum Inglis committed
    if (pollEventCount == sendAfterPolls) {
      // Average Values over recording period
Callum Inglis's avatar
Callum Inglis committed
      avgPpm10 = ppm10 / sendAfterPolls;
      avgPpm25 = ppm25 / sendAfterPolls;
      avgPpm100 = ppm100 / sendAfterPolls;
Callum Inglis's avatar
Callum Inglis committed
      avgTemperature = temperature / sendAfterPolls;
      avgHumidity = humidity / sendAfterPolls;
      // TODO Transmit Sensor Data
      Serial.print("Avg ppm10: "); Serial.println(avgPpm10);
      Serial.print("Avg ppm25: "); Serial.println(avgPpm25);
      Serial.print("Avg ppm100: "); Serial.println(avgPpm100);

      Serial.print("Avg Temperature: "); Serial.println(avgTemperature);
      Serial.print("Avg Humidity: "); Serial.println(avgHumidity);
      // LORA SEND
      DynamicJsonDocument doc(1024);

Callum Inglis's avatar
Callum Inglis committed
      JsonObject metadata = doc.createNestedObject("sensorMetadata");
      metadata["uid"] = getSensorUID();
Callum Inglis's avatar
Callum Inglis committed
      metadata["samplePeriod"] = sendAfterPolls; // TODO: Multiply by poll duration!

      JsonObject data = doc.createNestedObject("data");

      JsonObject ppm = data.createNestedObject("ppm"); // Particulates
      ppm["p10"] = avgPpm10;
      ppm["p25"] = avgPpm25;
      ppm["p100"] = avgPpm100;

      JsonObject sht = data.createNestedObject("sht");  // Temp, Humidity
      sht["temperature"] = avgTemperature;
      sht["humidity"] = avgHumidity;

      JsonObject co2 = data.createNestedObject("co2"); // Co2
      co2["tmp"] = 0;
Callum Inglis's avatar
Callum Inglis committed
      if (transmitData(doc)) {
        Serial.println("Packet Sent\n");
      } else {
        Serial.println("[-] Failed to send packet\n");
      }

      // Reset Loop Values
      ppm10 = 0;
      ppm25 = 0;
      ppm100 = 0;
      temperature = 0;
      humidity = 0;
Callum Inglis's avatar
Callum Inglis committed
      pollEventCount = 0;
      Serial.println("---------------+++++-------------------");
    }
  }

  msgCount++;
Callum Inglis's avatar
Callum Inglis committed
  delay(pollingFrequency);