diff --git a/ESP8266_Transmitter/ESP8266_Transmitter.ino b/ESP8266_Transmitter/ESP8266_Transmitter.ino index b3fa9a4dbdd36a4d1a67c4d76497c17c4efd019a..a3ac0ab844625ac825ccf40339ac5b127c288530 100644 --- a/ESP8266_Transmitter/ESP8266_Transmitter.ino +++ b/ESP8266_Transmitter/ESP8266_Transmitter.ino @@ -29,11 +29,13 @@ * ========================================================================================= */ +#include <TimeLib.h> #include <SPI.h> #include <Wire.h> #include <LoRa.h> #include <SoftwareSerial.h> #include <ArduinoJson.h> +#include <base64.h> #include "SHTSensor.h" // Lora Config @@ -90,9 +92,10 @@ struct pms5003data { struct pms5003data data; // Polling / Transmitting Config -int pollingFrequency = 1000; // Polling Frequency, ms +int pollingFrequency = 2500; // 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 +int sendAfterPolls = 10; // Sample Period, after which Transmit Reading +int reservationTime = 1; // How long do we require use of TX / RX airways for sending packets? /** * Setup Humidity / Temperature Sensor @@ -187,7 +190,7 @@ boolean readPMSdata(Stream *s) { } uint8_t buffer[32]; - uint16_t sum = 0; + uint32_t sum = 0; s->readBytes(buffer, 32); // get checksum ready @@ -207,29 +210,148 @@ boolean readPMSdata(Stream *s) { if (sum != data.checksum) { Serial.println("Checksum failure"); + Serial.print("Expected: "); Serial.print(sum); + Serial.print(", Got: "); Serial.print(data.checksum); 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); +/* Turn on Reciever, + * listen for messages for [listenDuration] seconds, + * Return true if no messages recieved + */ +boolean clearToSend(int listenDuration) { + int listenUntil = now() + listenDuration; + + // Listen until timeout expires + while (listenUntil >= now()) { + int packetSize = LoRa.parsePacket(); + + if (packetSize) { + return false; // Other message heard on Rx, we can not transmit just now. + } + + delay(5); + } + + return true; // We didn't hear anything, so continue +} + +// Listen of listenDuration seconds +// If we see a response to our txHello packet, and txHello.okToTransmit is True, we can send our packet +boolean listenTxHelloAccept(int listenDuration) { + int time_now = now() + listenDuration; + + // Listen until timeout expires + while (time_now >= now()) { + Serial.println("Listening for auth"); + int packetSize = LoRa.parsePacket(); + + if (packetSize) { + String incoming = ""; + char temp; + + while (LoRa.available()) { + // TODO - Tidy this up, ensure we only read in valid JSON + temp = (char)LoRa.read(); + + // Opening { + if (incoming.length() == 0 && temp == '{') { + incoming = "{"; + + // Closing } + } else if (temp == '}') { + incoming.concat("}"); + break; + + // Anything else that's valid + } else if (incoming.length() > 0) { + incoming.concat(temp); + } + } + + // DEBUG +// Serial.print("in listenTxHelloAccept() Recieved: \n"); +// Serial.println(incoming); + + // Verify its OK to transmit + StaticJsonDocument<200> doc; + deserializeJson(doc, incoming); + + const bool okToCopy = doc["okToTransmit"]; + const String authIsForUid = doc["uid"]; + + // Verify txHello.okToTransmit is True & UID Match + return (okToCopy && authIsForUid == getSensorUID()); + } + + delay(5); + } + + return false; // We didn't hear anything, conside this as meaning "Can't Send" +} + +void sendJsonPayloadWithLoRa(DynamicJsonDocument payload) { + LoRa.beginPacket(); + serializeJson(payload, LoRa); + LoRa.endPacket(); +} + +// TODO - Finish Acks & Naks +boolean transmitData(DynamicJsonDocument payload) { + + // Listen for other communication + // Rx - Listen for other messages, if no messages heard then continue +// if (!clearToSend(0.5)) { +// Serial.println("[-] Airways busy, could not send"); +// delay(random(500, 1250)); // Introduce random delay to avoid another collision +// return false; +// } + + + // Send short "clear to send?" packet + // Tx - "I've got data to send!" + UID + // RX - Continue upon recieving "OK" + UID + TX_Auth_ID + DynamicJsonDocument txHello(2048); + txHello["uid"] = sensorID; + txHello["reservationTime"] = reservationTime; // How long do we require reservation of radio? + sendJsonPayloadWithLoRa(txHello); + + if (!listenTxHelloAccept(reservationTime * 1.1)) { // Else we have clear to send + return false; // Can't transmit just now + + } else { + Serial.println("OK To Transmit"); + } + + + // Transmit Payload + // Tx - Send payload + UID + TX_Auth_ID + // Rx - Listen for Ack/Nack + Serial.println("Sending Payload Now"); + sendJsonPayloadWithLoRa(payload); + + + // TODO Await Response Ack/Nak + + + // TODO Listen for ack/nak + + // T+2 Rx OFF + // Handle Ack + UID + Next_Send_Interval + // Handle Nak + UID: GoTo T-3 + // Handle No Response: GoTo T-3 + + // T+3 Tx/Rx Window Expires + //LoRa.write(localAddress); // LoRa.write(msgCount); //LoRa.write(outgoing.length()); //LoRa.print(outgoing); - serializeJson(doc, LoRa); - LoRa.endPacket(); - + return true; } @@ -252,111 +374,119 @@ void setup() { 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 = ""; + // Error reading PMS Data, ignore on this round + if (!readPMSdata(&pmsSerial)) { + data.pm10_standard = 0; + data.pm25_standard = 0; + data.pm100_standard = 0; + } + + pollEventCount++; + + Serial.println(); + Serial.print(String(pollEventCount) + ") Std Units: "); + 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.print(data.pm100_standard); + Serial.print("\t\tTemp: "); Serial.print(getTemperature()); + Serial.print("\t\tHumidity: "); Serial.print(getHumidity()); + + // 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.println("---------------------------------------"); + Serial.print("Avg ppm10: "); Serial.print(avgPpm10); + Serial.print("\t\tAvg ppm25: "); Serial.print(avgPpm25); + Serial.print("\t\tAvg ppm100: "); Serial.print(avgPpm100); + Serial.print("\t\tAvg Temp: "); Serial.print(avgTemperature); + Serial.print("\t\tAvg Humidity: "); Serial.println(avgHumidity); + Serial.print("Chip ID: "); Serial.println(sensorID); + Serial.println(""); + + // 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"); - 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 + } else { + while (!transmitData(doc)){ + Serial.println("[-] Failed to send packet, retrying\n"); + delay(reservationTime + random(250, 1250)); // Introduce random delay to avoid another collision } - Serial.print("\n"); - - // Reset Loop Values - ppm10 = 0; - ppm25 = 0; - ppm100 = 0; - temperature = 0; - humidity = 0; - - pollEventCount = 0; - Serial.println("---------------+++++-------------------"); } + Serial.println("---------------------------------------"); + + // 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++; }