/* ========================================================================================= * * 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; // Temp / Humidity Sensor SHTSensor sht; // Particle Sensor Serial SoftwareSerial pmsSerial(2, 3); // LoRa Message Tracking String sensorID = ""; byte localAddress = 0xBB; byte destination = 0xFF; uint32_t 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; // 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 * * 2021-11-07 - This method proved unreliable, switched to casting ESP.getChipID() as a string instead */ String getSensorUID() { // uint64_t chipid = ESP.getChipId(); // uint16_t chip = (uint16_t)(chipid >> 32); // // char sensorID[23]; // snprintf(sensorID, 23, "EMIEI-%04X%08X", chip, (uint32_t)chipid); String sensorID = "EMIEI-"; sensorID.concat(String(ESP.getChipId())); 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; } 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; } void setup() { delay(1000); Serial.begin(115200); // Console Debug Serial.println("[+] Transmitter Node"); sensorID = getSensorUID(); 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)) { 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); Serial.print("Temp: "); Serial.print(getTemperature()); Serial.print("\t\tHumidity: "); Serial.println(getHumidity()); Serial.println("-------------------" + String(pollEventCount) + "--------------------"); // Add to Average // Standard (_standard) or Environmental (_env) ppm10 = ppm10 + data.pm10_standard; ppm25 = ppm25 + data.pm25_standard; ppm100 = ppm100 + data.pm100_standard; if (pollEventCount == sendAfterPolls) { // Average Values over recording period avgPpm10 = ppm10 / sendAfterPolls; avgPpm25 = ppm25 / sendAfterPolls; avgPpm100 = ppm100 / sendAfterPolls; 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); Serial.print("Chip ID: "); Serial.println(sensorID); // LORA SEND DynamicJsonDocument doc(2048); JsonObject metadata = doc.createNestedObject("sensorMetadata"); metadata["uid"] = sensorID; metadata["messageID"] = msgCount; 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; if (transmitData(doc)) { Serial.println("Packet Sent\n"); } else { Serial.println("[-] Failed to send packet\n"); } // TODO Wait for reply int i = 3000; int j = 0; String incoming = ""; while (j < i) { int packetSize = LoRa.parsePacket(); if (packetSize) { while (LoRa.available()) { incoming.concat((char)LoRa.read()); } // TODO Parse response to JSON, check if matches device & message ID Serial.print("Inbound!: \n"); Serial.print(incoming); break; } j+=10; delay(10); // TODO If no response then consider not delivered } Serial.print("\n"); // Reset Loop Values ppm10 = 0; ppm25 = 0; ppm100 = 0; temperature = 0; humidity = 0; pollEventCount = 0; Serial.println("---------------+++++-------------------"); } msgCount++; } delay(pollingFrequency); }