/* ========================================================================================= * * 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 <SPI.h> #include <Wire.h> #include <LoRa.h> #include <SoftwareSerial.h> #include <ArduinoJson.h> #include "SHTSensor.h" // Lora Config #define ss 16 // Physical Pin 16 = D0 (Default is Physical Pin 5) #define rst 0 #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; // Serial PMS Config static const int RXPin = 4, TXPin = 3; static const int pollingFrequency = 2500; // ms // 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; int eventCount = 0; //counts the loop, which iterates every second int secsToPost = 1; //number of seconds to wait - also the divisor for avg /** * 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 */ char* getSensorID() { 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; } void setup() { 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 // Setup LoRa if (!setupLoRa()) { while(1); } // Die on error } void loop() { // TODO Gather Sensor Data if (readPMSdata(&pmsSerial)) { 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); Serial.println("---------------------------------------"); Serial.println("Concentration Units (environmental)"); Serial.print("PM 1.0: "); Serial.print(data.pm10_env); Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env); Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env); Serial.println("---------------------------------------"); Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um); Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um); Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um); Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um); Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um); Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um); Serial.println("---------------------------------------"); Serial.print("Temperature: "); Serial.println(getTemperature()); Serial.print("Humidity: "); Serial.println(getHumidity()); eventCount++; Serial.println("-------------------" + String(eventCount) + "--------------------"); // Choose Standard (_standard) or Environmental (_env) ppm10 = ppm10 + data.pm10_standard; ppm25 = ppm25 + data.pm25_standard; ppm100 = ppm100 + data.pm100_standard; //ppm10 = ppm10 + data.pm10_env; //ppm25 = ppm25 + data.pm25_env; //ppm100 = ppm100 + data.pm100_env; if (eventCount == secsToPost) { // Average Values over recording period avgPpm10 = ppm10 / secsToPost; avgPpm25 = ppm25 / secsToPost; avgPpm100 = ppm100 / secsToPost; avgTemperature = temperature / secsToPost; avgHumidity = humidity / secsToPost; // 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); JsonObject sensor = doc.createNestedObject("sensor"); sensor["ID"] = getSensorID(); sensor["samplePeriod"] = secsToPost; 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; // 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(); Serial.println("Packet Sent\n"); // Reset Loop Values ppm10 = 0; ppm25 = 0; ppm100 = 0; temperature = 0; humidity = 0; eventCount = 0; Serial.println("---------------+++++-------------------"); } } msgCount++; //delay(pollingFrequency); }