diff --git a/ESP8266_Transmitter/ESP8266_Transmitter.ino b/ESP8266_Transmitter/ESP8266_Transmitter.ino index bddeadc164194b3b9277b39b3126739108ac4394..47229255dd7399f09248a61638175af0a8701ec9 100644 --- a/ESP8266_Transmitter/ESP8266_Transmitter.ino +++ b/ESP8266_Transmitter/ESP8266_Transmitter.ino @@ -38,6 +38,16 @@ #include <base64.h> #include "SHTSensor.h" +// Debug / Polling / Transmitting Config +#define DEBUG 1 +#define MIN_RAND_DELAY 500 // ms +#define MAX_RAND_DELAY 1250 // ms +#define POLLING_FREQUENCY 2500 // ms Take value every POLLING_FREQUENCY ms +#define MAX_TRANSMISSION_RETRIES 5 // No. Retries before recordings new values then retry +#define TX_AFTER_N_READINGS 10 // No. of samples, after which Transmit Avg Readings +#define TX_RESERVATION_TIME 1 // How long do we require use of TX for sending packets? (Seconds) +int pollEventCount = 0; // Number of times data has been sampled in this recording period + // Lora Config #define ss 16 // Physical Pin 16 = D0 (Default is Physical Pin 5) #define rst 0 @@ -61,23 +71,12 @@ 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; +double temperature; // Sensor Values - Temp +double humidity; // Sensor Values - Humidity +double co2; // Sensor Values - Co2 +uint32_t ppm10; // Sensor Values - Particulate +uint32_t ppm25; // Sensor Values - Particulate +uint32_t ppm100; // Sensor Values - Particulate // Partial Sensor Data struct pms5003data { @@ -91,12 +90,6 @@ struct pms5003data { struct pms5003data data; -// Polling / Transmitting Config -int pollingFrequency = 2500; // Polling Frequency, ms -int pollEventCount = 0; // Number of times data has been sampled in this recording period -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 */ @@ -117,10 +110,9 @@ double getTemperature() { if (sht.readSample()) { t = sht.getTemperature(); - temperature += t; } - return t; // -1 on error TODO Make this better! + return t; // -1 on error } double getHumidity() { @@ -128,12 +120,29 @@ double getHumidity() { if (sht.readSample()) { h = sht.getHumidity(); - humidity += h; } return h; // -1 on error } +/** + * TODO getCo2() + */ +double getCo2() { + return 0; +} + + +// TODO DOC +void resetCounters() { + ppm10 = 0; + ppm25 = 0; + ppm100 = 0; + temperature = 0; + humidity = 0; + pollEventCount = 0; +} + bool setupLoRa() { LoRa.setPins(ss, rst, dio0); if (!LoRa.begin(loraFrequency)) { @@ -155,15 +164,8 @@ bool setupLoRa() { * 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-"; + String sensorID = "EMIEI-"; // [E]nviornmental [M]onitoring [I]ndepenet of [E]xisting [I]nfrastructure sensorID.concat(String(ESP.getChipId())); - return sensorID; } @@ -218,9 +220,51 @@ boolean readPMSdata(Stream *s) { return true; } -/* Turn on Reciever, - * listen for messages for [listenDuration] seconds, - * Return true if no messages recieved +/** + * Provided sensor data, construct JSON object ready for transmission, Averaged over numSamples + * + * @param messageID + * @param numSamples Numer of samples averages + * @param avgPpm10 Average Particuate readings + * @param avgPpm25 Average Particuate readings + * @param avgPpm100 Average Particuate readings + * @param avgTemperature Average Temperature + * @param avgHumidity Average Humidity + * @param avgCo2 Average Co2 + * + * @return DynamicJsonDocument sensorData + */ +DynamicJsonDocument prepareSensorData(int messageID, int numSamples, uint32_t avgPpm10, uint32_t avgPpm25, uint32_t avgPpm100, double avgTemperature, double avgHumidity, double avgCo2) { + 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"] = 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"); // TODO Co2 + co2["tmp"] = avgCo2; + + return doc; +} + +/** + * Listen on Lora for other messages, returns true if no messages detected within [listenDuration] seconds + * Use prior to transmissions to avoid interruption of other messages + * + * @param int listenDuration How long to listen on Lora RX in Seconds + * @returns boolean If messages detected within listenDuration */ boolean clearToSend(int listenDuration) { int listenUntil = now() + listenDuration; @@ -230,7 +274,8 @@ boolean clearToSend(int listenDuration) { int packetSize = LoRa.parsePacket(); if (packetSize) { - return false; // Other message heard on Rx, we can not transmit just now. + Serial.println("[-] TX Busy - Not Clear To Send"); + return false; // Other message heard on Rx, infer that we can not transmit just now. } delay(5); @@ -239,193 +284,223 @@ boolean clearToSend(int listenDuration) { 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 messageID) { - int time_now = now() + listenDuration; +// Introduce random delay to avoid another collision +void waitRandomDelay(int minSeconds = 0) { + int randDelay = (minSeconds * 1000) + random(MIN_RAND_DELAY, MAX_RAND_DELAY); + if (DEBUG){ + Serial.println("[i] Delay for " + String(randDelay) + "ms"); + } + delay(randDelay); +} + +/** + * Send short "clear to send?" packet + * Tx - "I've got data to send!" + UID + * @param messageID ID of this message / sample period + */ +void sendTxHello(int messageID) { + Serial.println("[+] Transmit - \"Hello\""); + + DynamicJsonDocument txHello(2048); + txHello["uid"] = getSensorUID(); + txHello["reservationTime"] = TX_RESERVATION_TIME; + txHello["messageID"] = messageID; + + sendJsonPayloadWithLoRa(txHello); +} + +/** + * Send Payload + * Tx - Sensor Payload + * @param payload JSON Payload to be sent + */ +void sendTxPayload(DynamicJsonDocument payload) { + Serial.println("[+] Transmit - Payload"); + sendJsonPayloadWithLoRa(payload); +} + +/** + * Listen for messages on TX, expecting JSON + * + * @param listenDuration How long to listen on Lora RX in Seconds + * @return StaticJsonDocument|null RX Payload in JSON, null on timeout reached or error + */ +StaticJsonDocument<1024> listenForAndConsumeMessage(int listenDuration) { + int listenUntil = now() + listenDuration; + StaticJsonDocument<1024> json; // Listen until timeout expires - Serial.println("[+] Transmit - \"Hello\" - Listening for ack & clear to send"); - while (time_now >= now()) { + while (listenUntil >= now()) { int packetSize = LoRa.parsePacket(); + + if (!packetSize) { + delay(3); + continue; + } + + // Read in packet, ensure we only bring in anything after { and before } inclusive + String incoming = ""; + char temp; - 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); - } - } + while (LoRa.available()) { + temp = (char)LoRa.read(); - // DEBUG -// Serial.print("in listenTxHelloAccept() Recieved: \n"); -// Serial.println(incoming); - - // Verify its OK to transmit - StaticJsonDocument<200> doc; - deserializeJson(doc, incoming); - - const bool okToTransmit = doc["okTransmit"]; - const String authIsForUid = doc["uid"]; - const int authIsForMessageID = doc["messageID"]; - const String gatewayUid = doc["gatewayUid"]; - - // Verify txHello.okToTransmit is True & UID Match & Message IDs Match - if (authIsForUid == getSensorUID()) { Serial.println("[+] Transmit - \"Hello\" - Sensor UID Match!"); } else { Serial.println("[-] Transmit - \"Hello\" - Sensor UID Mis-Match! " + String(authIsForUid) + " vs " + String(getSensorUID())); } - if (authIsForMessageID == messageID) { Serial.println("[+] Transmit - \"Hello\" - Message ID Match!"); } else { Serial.println("[-] Transmit - \"Hello\" - MessageID Mis-Match!"); } - - return (okToTransmit - && authIsForUid == getSensorUID() - && authIsForMessageID == messageID); + if (incoming.length() == 0 && temp == '{') { // Opening { + incoming = "{"; + + } else if (temp == '}') { // Closing } + incoming.concat("}"); + break; + + } else if (incoming.length() > 0) { // Anything else that's valid + incoming.concat(temp); + } } - delay(3); + // Deserialize - TODO Error Handling https://arduinojson.org/v6/api/json/deserializejson/ + deserializeJson(json, incoming); + break; } - Serial.println("[-] Transmit - \"Hello\" - Timeout while waiting for Clear to Send\n\n"); - return false; // We didn't hear anything, conside this as meaning "Can't Send" + return json; } -void sendJsonPayloadWithLoRa(DynamicJsonDocument payload) { - LoRa.beginPacket(); - serializeJson(payload, LoRa); - LoRa.endPacket(); -} +/** + * Listen for response to TxHello for [listenDuration] seconds, check Sensor UID & Message ID match and return true if we have clear to transmit payload. + * @param listenDuration How long to listen on Lora RX in Seconds + * @param messageID ID of message we are expecting to receive + * @return boolean Clear to Transmit + */ +boolean listenForTxHelloAccept(int listenDuration, int messageID) { + Serial.println("[+] Transmit - \"Hello\" - Listening for \"Hello\" Ack & Clear to Send"); -// TODO - Finish Acks & Naks -boolean transmitData(DynamicJsonDocument payload) { + StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration); - // 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 + // Timeout, likely TX Hello was not recieved or ack got lost + if (json.isNull()) { + Serial.println("[-] Transmit - \"Hello\" - Timeout while waiting for Clear to Send\n\n"); + return false; + } - // TODO Refactor - while (!clearToSend(0.5)) { - Serial.println("[-] Airways busy, could not send"); - delay(random(500, 1250)); // Introduce random delay to avoid another collision + const bool okToTransmit = json["okTransmit"]; + const String authIsForUid = json["uid"]; + const int authIsForMessageID = json["messageID"]; + const String gatewayUid = json["gatewayUid"]; + + // Verify txHello.okTransmit is True and Sensor UID & Message IDs Match + if (DEBUG) { + if (authIsForUid == getSensorUID()) { + Serial.println("[+] Transmit - \"Hello\" - Sensor UID Match!"); + } else { + Serial.println("[-] Transmit - \"Hello\" - Sensor UID Mis-Match! " + String(authIsForUid) + " vs " + String(getSensorUID())); + } + + if (authIsForMessageID == messageID) { + Serial.println("[+] Transmit - \"Hello\" - Message ID Match!"); + } else { + Serial.println("[-] Transmit - \"Hello\" - MessageID Mis-Match!"); } - //return false; } - Serial.println("[+] Transmit - \"Hello\""); - // 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? - txHello["messageID"] = msgCount; - sendJsonPayloadWithLoRa(txHello); + // Ok To Trasmit, Sensor UID Match & Message ID Match + bool clearToSend = (okToTransmit + && authIsForUid == getSensorUID() + && authIsForMessageID == messageID); - if (!listenTxHelloAccept(reservationTime * 1.5, msgCount)) { // Can't transmit just now - Serial.println("[-] Transmit - \"Hello\" - Can Not Transmit At This Time\n"); - return false; + if (DEBUG) { + if (clearToSend) { + Serial.println("[+] Transmit - \"Hello\" - Recieved \"Clear to Transmit\" Payload"); + } else { + Serial.println("[-] Transmit - \"Hello\" - Can Not Transmit At This Time"); + } } - Serial.println("[+] Recieved - Clear to Transmit Payload"); // Else we have clear to send + return clearToSend; +} + +// TODO DOC +boolean listenForTxPayloadAccept(int listenDuration, int messageID) { + Serial.println("[.] Transmit - Payload - Listening for Ack"); - // Transmit Payload - // Tx - Send payload + UID + TX_Auth_ID - // Rx - Listen for Ack/Nack - Serial.println("[+] Transmit - Payload"); - sendJsonPayloadWithLoRa(payload); + StaticJsonDocument<1024> json = listenForAndConsumeMessage(listenDuration); + // Timeout, likely TX Payload was not recieved or ack got lost. + if (json.isNull()) { + Serial.println("[-] Transmit - Payload - Ack Timeout Reached - Assuming Message Was Not Delivered\n\n"); + return false; + } - // TODO Await Response Ack/Nak - int ackTimeout = 2; // Seconds - int time_now = now() + ackTimeout; + const bool ackStatus = json["ackStatus"]; + const String authIsForUid = json["uid"]; + const int authIsForMessageID = json["replyMsgID"]; + const String gatewayUid = json["gatewayUid"]; + - // Listen until timeout expires - Serial.println("[.] Transmit - Payload - Listening for Ack"); - while (time_now >= now()) { - int packetSize = LoRa.parsePacket(); + // Verify Sensor UID Match + if (authIsForUid == getSensorUID() + && authIsForMessageID == messageID) { + + Serial.println("[+] Transmit - Payload - Ack Recieved: " + String(ackStatus) + "\n"); - 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); - } - } + if (ackStatus) { return true; } // It all worked :) + + // TODO Retransmit, recover, etc + Serial.println("[-] Transmit - Payload - Ack Failed - TODO Setup Retransmission"); + return false; + } + + // TODO Else UID Mis-Match so we wait for next message + Serial.println("[-] Transmit - Payload - Ack Message ID or Sensor ID Mis-Match"); + return false; +} + +/** + * Send JSON Payload over LoRa + * @param payload JSON Payload to be send + */ +void sendJsonPayloadWithLoRa(DynamicJsonDocument payload) { + LoRa.beginPacket(); + serializeJson(payload, LoRa); + LoRa.endPacket(); +} - // DEBUG -// Serial.print("\nin listedForAck() Recieved: \n"); -// Serial.println(incoming); +// TODO - Finish Acks & Naks +boolean transmitData(DynamicJsonDocument payload) { - StaticJsonDocument<200> doc; - deserializeJson(doc, incoming); + // TODO MAX_RETRIES - const bool ackStatus = doc["ackStatus"]; - const String authIsForUid = doc["uid"]; - const String gatewayUid = doc["gatewayUid"]; + // Listen for other communication + // Rx - Listen for other messages, if no messages heard then continue + if (!clearToSend(0.5)) { + waitRandomDelay(); // Wait for short time before retrying + while (!clearToSend(0.5)) { waitRandomDelay(); } // TODO MAX_TIMEOUT + } - // Verify txHello.okToTransmit is True & UID Match - if (authIsForUid == getSensorUID()) { - Serial.println("[+] Transmit - Payload - Ack Recieved: " + String(ackStatus) + "\n"); - - if (ackStatus) { return true; } // It all worked :) + // Send TX Hello + sendTxHello(msgCount); - // TODO Retransmit, recover, etc - return false; - } + // Await TX Hello Auth - Expect: Timeout | Not Auth | Accept + Clear To Transmit + if (!listenForTxHelloAccept(TX_RESERVATION_TIME * 1.5, msgCount)) { + return false; // Can't transmit just now, we will retry + } - // Else UID Mis-Match so we wait for next message - } + // Send TX Payload + sendTxPayload(payload); - delay(5); + // Await TX Payload Ack - Expect: Timeout | Nack | Accept + Data Match + if (!listenForTxPayloadAccept(2, msgCount)) { + return false; // TODO Ack Failed Setup a retry here! } - // TODO After Timeout we need to deal with! - Serial.println("[-] Transmit - Payload - Ack Timeout Reached - Assuming Message Was Not Delivered"); + // TODO Update Sensor Config - // 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 + // TODO Clear values & Continue + resetCounters(); + Serial.println("Packet Sent Succesfully\n"); + Serial.println("---------------+++++-------------------"); + msgCount++; - - //LoRa.write(localAddress); -// LoRa.write(msgCount); - //LoRa.write(outgoing.length()); - //LoRa.print(outgoing); - - return true; + return true; } void setup() { @@ -434,17 +509,14 @@ void setup() { Serial.begin(115200); // Console Debug Serial.println("\n\n[+] Transmitter Node"); - sensorID = getSensorUID(); - pmsSerial.begin(9600); // Partical Sensor - // Setup Sensors + // Setup Hardware if (!setupSHT()) { while(1); } // Temp/Humidity - Die on Error - - // Setup LoRa if (!setupLoRa()) { while(1); } // Die on error } +// Main void loop() { // TODO Gather Sensor Data @@ -457,93 +529,74 @@ void loop() { pollEventCount++; - Serial.println(); - Serial.print(String(pollEventCount) + ") "); - 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()); + if (DEBUG) { + Serial.println(); + Serial.print(String(pollEventCount) + ") "); + 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()); + Serial.print("\t\tCo2: "); Serial.print(getCo2()); + } // Add to Average - // Standard (_standard) or Environmental (_env) - ppm10 = ppm10 + data.pm10_standard; + temperature += getTemperature(); + humidity += getHumidity(); + co2 += getCo2(); + ppm10 = ppm10 + data.pm10_standard; // Standard (_standard) or Environmental (_env) readings for Particulate Data 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.print(avgHumidity); - Serial.print("\t\tChip 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"); - - } else { - int maxRetries = 10; // TODO Move to Config - int numRetries = 1; - - while (!transmitData(doc) && numRetries < maxRetries){ - numRetries++; - Serial.println("[-] Failed to send packet, retrying. Attempt " + String(numRetries) + " of " + String(maxRetries) + "\n"); - delay(reservationTime + random(1250, 5250)); // Introduce random delay to avoid another collision - } - - if (numRetries >= maxRetries) { - Serial.println("[-] Failed to send packet, max retries reached. Aborting"); + // If we should now transmit + if (pollEventCount >= TX_AFTER_N_READINGS) { - // TODO Don't Clear Counters, record more values then try to retransmit - } + // Average Values over recording period + double avgTemperature = temperature / pollEventCount; + double avgHumidity = humidity / pollEventCount; + double avgCo2 = co2 / pollEventCount; + uint32_t avgPpm10 = ppm10 / pollEventCount; + uint32_t avgPpm25 = ppm25 / pollEventCount; + uint32_t avgPpm100 = ppm100 / pollEventCount; + + if (DEBUG) { + 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.print(avgHumidity); + Serial.print("\t\tAvg Co2: "); Serial.print(avgCo2); + Serial.print("\t\tChip ID: "); Serial.println(getSensorUID()); + Serial.println(""); } + + // Prepare Data For Send + DynamicJsonDocument sensorData = prepareSensorData(msgCount, pollEventCount, avgPpm10, avgPpm25, avgPpm100, avgTemperature, avgHumidity, avgCo2); - // Reset Loop Values - ppm10 = 0; - ppm25 = 0; - ppm100 = 0; - temperature = 0; - humidity = 0; + // Transmit + if (transmitData(sensorData)) { + return; // It all worked, values reset, now record new values + } + + // Transmission failed, handle re-tries + int numRetries = 1; - pollEventCount = 0; - Serial.println("---------------+++++-------------------"); + 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); // 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; + } - msgCount++; + // 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; } - delay(pollingFrequency); + delay(POLLING_FREQUENCY); }