From 7ea7c46e69bb7f2704e636399e9b90e7a8364793 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Wed, 12 Jun 2024 23:45:34 -0500 Subject: [PATCH 01/10] revert sendFDRS to Synchronous operations, add sendFDRSAsync --- src/fdrs_lora.h | 84 ++++++++++++++++++++++++++++++++++++++++++++----- src/fdrs_node.h | 44 +++++++++++++++++++++----- 2 files changed, 113 insertions(+), 15 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index bf938cd..70a031d 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -203,8 +203,9 @@ void printLoraPacket(uint8_t *p, int size) } // Do not call this function directly, instead call transmitLoRaAsync(...) +// or transmitLoRaSync(....) // Transmits Lora data by calling RadioLib library function -// Returns void becuase function is Async and does not receive any data +// No return data. Use transmitLoRaSync(...) if status is important void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len) { uint8_t pkt[6 + (len * sizeof(DataReading))]; @@ -245,8 +246,10 @@ void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len) return; } -// For now SystemPackets will not use ACK but will calculate CRC -// Returns CRC_NULL as SystemPackets do not use ACKS at current time +// Transmits LoRa SystemPacket Data. +// Before calling this function directly please check for (loraTxState == stReady) +// to make sure another Async operation is not already in progress otherwise we might be +// waiting for ACKs from a gateway and transmit over the top void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len) { uint8_t pkt[6 + (len * sizeof(SystemPacket))]; @@ -286,6 +289,52 @@ void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len) return; } +// Synchronously Transmits LoRa data and, if configured, listens for ACKs +// Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data +crcResult transmitLoRaSync(uint16_t *destMac, DataReading *packet, uint8_t len) +{ + crcResult crcReturned = CRC_NULL; + // Do we check to see if another Async operation is in progress first??? + // If Yes, what do we do if another operation is in progress? Return and rely on retransmit retry? + // if(loraTxState == stReady) { } + transmitLoRa(destMac, packet, len); + + // if LORA_ACK is defined + if(ack) { + int retries = FDRS_LORA_RETRIES + 1; + while (retries != 0) + { + unsigned long loraAckTimeout = millis() + FDRS_ACK_TIMEOUT; + retries--; + delay(10); + while (crcReturned == CRC_NULL && (millis() < loraAckTimeout)) + { + crcReturned = LoRaTxRxOperation(); + yield(); + } + if (crcReturned == CRC_OK) + { + DBG1("LoRa ACK Received! CRC OK"); + return CRC_OK; // we're done + } + else if (crcReturned == CRC_BAD) + { + DBG1("LoRa ACK Received! CRC BAD"); + // Resend original packet again if retries are available + } + else + { + DBG1("LoRa Timeout waiting for ACK!"); + // resend original packet again if retries are available + } + // Here is our retry + transmitLoRa(destMac, packet, len); + } + + } + return crcReturned; +} + void begin_lora() { #ifdef CUSTOM_SPI @@ -337,6 +386,8 @@ void begin_lora() } } +// Asynchronous call to transfer LoRa SystemPacket data. Stores data in circular buffer until next transmit. +// Returns true if data is added to buffer. Warns if buffer data is overwritten. bool transmitLoRaAsync(uint16_t *destAddr, SystemPacket *sp, uint8_t len) { for(int i=0; i < len; i++) @@ -357,6 +408,8 @@ bool transmitLoRaAsync(uint16_t *destAddr, SystemPacket *sp, uint8_t len) } // Wrapper for transmitLoRa for DataReading type packets to handle processing Receiving CRCs and retransmitting packets +// Asynchronous call to transfer LoRa DataReading data. Stores data in circular buffer until next transmit. +// Returns true if data is added to buffer. Warns if buffer data is overwritten. bool transmitLoRaAsync(uint16_t *destAddr, DataReading *dr, uint8_t len) { // Write as much as needed and just flush out the older data if too much data writing to buffer @@ -381,6 +434,7 @@ bool transmitLoRaAsync(uint16_t *destAddr, DataReading *dr, uint8_t len) } // return the number of consecutive DRs in the DR Queue that have the same destination address +// If the size of the data is larger than LoRa data packet maximum then limit the size to LoRa max uint transmitSameAddrLoRa() { uint count = 0; @@ -459,10 +513,7 @@ bool pingReplyLoRa(uint16_t address) } // ****DO NOT CALL receiveLoRa() directly! ***** Call handleLoRa() instead! -// receiveLoRa for Sensors -// USED to get ACKs (SystemPacket type) from LoRa gateway at this point. May be used in the future to get other data // Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data - crcResult receiveLoRa() { int packetSize = radio.getPacketLength(); @@ -628,6 +679,8 @@ crcResult receiveLoRa() return CRC_NULL; } +// Uses interrupt from LoRa chip to determine start and stop of transmit and receive cycles +// Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data crcResult LoRaTxRxOperation() { crcResult crcReturned = CRC_NULL; @@ -680,7 +733,7 @@ int pingRequestLoRa(uint16_t address, uint32_t timeout) loraPing.timeout = timeout; loraPing.address = address; - // Perform blocking ping if nothing else is in process + // Perform blocking (synchronous) ping if nothing else is in process if(loraTxState == stReady) { loraPing.status = stInProcess; loraPing.start = millis(); @@ -748,6 +801,7 @@ void sendLoRaNbr(uint8_t interface) } } +// Handles completion of Asynchronous LoRa processes void handleLoRa() { static uint8_t len = 0; @@ -756,6 +810,7 @@ void handleLoRa() static unsigned long lastTxtime = 0; static unsigned long statsTime = 0; + // check status of TX or RX operation LoRaTxRxOperation(); // check for result of any ongoing async ping operations @@ -840,7 +895,7 @@ void handleLoRa() } - // It's polite to Listen more than you talk + // Time delay between consecutive transmits - TXDELAYMS if(TDIFF(lastTxtime,(TXDELAYMS + random(0,50)))) { // Start Transmit data from the DataReading queue if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady) @@ -923,4 +978,17 @@ bool reqTimeLoRa() { } } return false; +} + +// Check for LoRa Asynchronous operations to be complete +// Returns True if there are no pending LoRa Async Operations. +// Returns False if there are pending LoRa Async Operations. +bool isLoRaAsyncComplete() { + if(loraTxState == stReady && ISBUFFEMPTY(drBuff) && ISBUFFEMPTY(spBuff) && loraPing.status == stReady && loraAckState == stReady) { + return true; + } + else { + return false; + } + } \ No newline at end of file diff --git a/src/fdrs_node.h b/src/fdrs_node.h index 9179117..3d13108 100644 --- a/src/fdrs_node.h +++ b/src/fdrs_node.h @@ -170,6 +170,8 @@ void handleIncoming() } } +// Sends FDRS data synchronously +// Returns true if success and false if not successful bool sendFDRS() { if(data_count == 0) { @@ -194,27 +196,49 @@ bool sendFDRS() data_count = 0; return false; } -#endif +#endif // USE_ESPNOW #ifdef USE_LORA - transmitLoRaAsync(>wyAddress, fdrsData, data_count); - // DBG(" LoRa sent."); + crcResult crcReturned = CRC_NULL; + crcReturned = transmitLoRaSync(>wyAddress, fdrsData, data_count); + DBG(" LoRa sent."); #ifdef LORA_ACK if(crcReturned == CRC_OK) { - data_count = 0; + data_count = 0; return true; } -#endif +#endif // LORA_ACK #ifndef LORA_ACK if(crcReturned == CRC_OK || crcReturned == CRC_NULL) { data_count = 0; return true; } -#endif +#endif // !LORA_ACK else { data_count = 0; return false; } -#endif +#endif // USE_LORA +} + +// Sends FDRS data asynchronously - no spinning loop, more processor friendly, but does not return as much information +// Returns true if data is queued up, false if there is no data to be sent +bool sendFDRSAsync() +{ + if(data_count == 0) { + return false; + } + + DBG("Queueing FDRS Packet!"); +#ifdef USE_ESPNOW + esp_now_send(gatewayAddress, (uint8_t *)&fdrsData, data_count * sizeof(DataReading)); + data_count = 0; + return true; +#endif // USE_ESPNOW +#ifdef USE_LORA + transmitLoRaAsync(>wyAddress, fdrsData, data_count); + data_count = 0; + return true; +#endif // USE_LORA } void loadFDRS(float d, uint8_t t) @@ -244,6 +268,12 @@ void loadFDRS(float d, uint8_t t, uint16_t id) void sleepFDRS(uint32_t sleep_time) { + // make sure all Async operations are completed + unsigned long timeout = millis() + 1000; + while(millis() < timeout && !isLoRaAsyncComplete()) { + handleLoRa(); + yield(); + } #ifdef DEEP_SLEEP DBG(" Deep sleeping."); #ifdef ESP32 From 0a830de2dce4604ccb6263818103bdc941001834 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 22:10:28 -0500 Subject: [PATCH 02/10] FDRS nodes should not send out time notifications --- src/fdrs_time.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fdrs_time.h b/src/fdrs_time.h index 8f37a8c..17e0ea1 100644 --- a/src/fdrs_time.h +++ b/src/fdrs_time.h @@ -280,6 +280,7 @@ void checkDST() { // Periodically send time to ESP-NOW or LoRa nodes associated with this gateway/controller void sendTime() { +#ifdef FDRS_GATEWAY // Nodes do not send out time if(validTime()) { // Only send time if it is valid DBG1("Sending out time"); #if defined(USE_WIFI) || defined(USE_ETHERNET) @@ -288,6 +289,7 @@ void sendTime() { sendTimeLoRa(); sendTimeESPNow(); } +#endif // FDRS_GATEWAY } // time parameter is in Unix Time format UTC time zone From 8d319ca53738d7d4275d7dc493bd8f32342e7ce1 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 22:20:05 -0500 Subject: [PATCH 03/10] Tests for packet size exceed maximum --- src/fdrs_lora.h | 12 +++++++++++- src/fdrs_node.h | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index 70a031d..7de4709 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -10,7 +10,7 @@ #define TXDELAYMS 300 #define SPBUFFSIZE 10 -#define LORASIZE (250 / sizeof(DataReading)) +#define LORASIZE ((250 - 15) / sizeof(DataReading)) // 250 bytes minus header/crc data #define DRBUFFSIZE 100 #define ISBUFFEMPTY(buff) ((buff.endIdx == buff.startIdx) ? true: false) #define ISBUFFFULL(buff) (((buff.endIdx + 1) % buff.size) == buff.startIdx ? true: false) @@ -208,6 +208,11 @@ void printLoraPacket(uint8_t *p, int size) // No return data. Use transmitLoRaSync(...) if status is important void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len) { + // Check if amount of data provided to us is too large. Send as much data as possible and warn. + if(len * sizeof(DataReading) > (250 - 6)) { + DBG("ERROR: LoRa Transmit size too big! Truncating Data!"); + len = (250 - 6)/sizeof(DataReading); // readjusting number of DRs + } uint8_t pkt[6 + (len * sizeof(DataReading))]; uint16_t calcCRC = 0x0000; loraTxState = stInProcess; @@ -252,6 +257,11 @@ void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len) // waiting for ACKs from a gateway and transmit over the top void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len) { + // Amount of data provided to us is too large. Send as much data as possible and warn. + if(len * sizeof(SystemPacket) > (250 - 6)) { + DBG("ERROR: LoRa Transmit size too big! Truncating Data!"); + len = sizeof(SystemPacket)/(250 - 6); // readjusting number of SPs + } uint8_t pkt[6 + (len * sizeof(SystemPacket))]; uint16_t calcCRC = 0x0000; loraTxState = stInProcess; diff --git a/src/fdrs_node.h b/src/fdrs_node.h index 3d13108..1a2cc2b 100644 --- a/src/fdrs_node.h +++ b/src/fdrs_node.h @@ -13,7 +13,7 @@ DataReading theData[256]; uint8_t ln; uint8_t newData = event_clear; uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC}; -const uint16_t espnow_size = 250 / sizeof(DataReading); +const uint16_t espnow_size = (250 - 15) / sizeof(DataReading); // 250 bytes minus 15 bytes for addressing and other data crcResult crcReturned = CRC_NULL; uint8_t incMAC[6]; @@ -244,7 +244,7 @@ bool sendFDRSAsync() void loadFDRS(float d, uint8_t t) { DBG("Id: " + String(READING_ID) + " - Type: " + String(t) + " - Data loaded: " + String(d)); - if (data_count > espnow_size) + if (data_count >= espnow_size) sendFDRS(); DataReading dr; dr.id = READING_ID; @@ -256,7 +256,7 @@ void loadFDRS(float d, uint8_t t) void loadFDRS(float d, uint8_t t, uint16_t id) { DBG("Id: " + String(id) + " - Type: " + String(t) + " - Data loaded: " + String(d)); - if (data_count > espnow_size) + if (data_count >= espnow_size) sendFDRS(); DataReading dr; dr.id = id; From 1e64654eeba2cbf34ee464192b28e7a3fda741df Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 22:29:01 -0500 Subject: [PATCH 04/10] update LoRa statistics calculation --- src/fdrs_lora.h | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index 7de4709..3bb58e0 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -140,7 +140,9 @@ volatile bool operationDone = false; // flag to indicate that a packet was sent unsigned long loraAckTimeout = 0; unsigned long rxCountDR = 0; // Number of total LoRa DR packets destined for us and of valid size unsigned long rxCountSP = 0; // Number of total LoRa SP packets destined for us and of valid size +unsigned long rxCountUnk = 0; // Number of total LoRa packets received but not known (invalid size or format) unsigned long rxCountCrcOk = 0; // Number of total Lora packets with valid CRC +unsigned long rxCountCrcBad = 0; // Number of total Lora packets with valid CRC unsigned long txCountDR = 0; // Number of total LoRa DR packets transmitted unsigned long txCountSP = 0; // Number of total LoRa SP packets transmitted extern time_t now; @@ -195,7 +197,7 @@ void printLoraPacket(uint8_t *p, int size) printf("Printing packet of size %d.", size); for (int i = 0; i < size; i++) { - if (i % 2 == 0) + if (i % 16 == 0) printf("\n%02d: ", i); printf("%02X ", p[i]); } @@ -579,25 +581,24 @@ crcResult receiveLoRa() DBG1("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to node 0x" + String(sourceMAC, HEX) + "(hex)"); transmitLoRaAsync(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source newData = event_clear; // do not process data as data may be corrupt + rxCountCrcBad++; return CRC_BAD; // Exit function and do not update newData to send invalid data further on } - rxCountCrcOk++; memcpy(&theData, &packet[4], packetSize - 6); // Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC)) ln = (packetSize - 6) / sizeof(DataReading); if (memcmp(&sourceMAC, &LoRa1, 2) == 0) { // Check if it is from a registered sender newData = event_lora1; - return CRC_OK; } else if (memcmp(&sourceMAC, &LoRa2, 2) == 0) { newData = event_lora2; - return CRC_OK; } else { newData = event_lorag; - return CRC_OK; } + rxCountCrcOk++; + return CRC_OK; } else if ((packetSize - 6) == sizeof(SystemPacket)) { @@ -664,6 +665,7 @@ crcResult receiveLoRa() if(loraAckState == stInProcess) { loraAckState = stCrcMismatch; } + rxCountCrcBad++; return CRC_BAD; } } @@ -672,6 +674,7 @@ crcResult receiveLoRa() { // Uncommenting below will print out packets from other LoRa controllers being sent. // DBG2("Incoming LoRa packet of " + String(packetSize) + " bytes received from address 0x" + String(sourceMAC, HEX) + " destined for node address 0x" + String(destMAC, HEX)); // printLoraPacket(packet,sizeof(packet)); + rxCountUnk++; return CRC_NULL; } } @@ -683,6 +686,7 @@ crcResult receiveLoRa() // uint8_t packet[packetSize]; // radio.readData((uint8_t *)&packet, packetSize); // printLoraPacket(packet,sizeof(packet)); + rxCountUnk++; return CRC_NULL; } } @@ -960,9 +964,9 @@ void handleLoRa() lastTxtime = millis(); } // Print LoRa statistics - if(TDIFFSEC(statsTime,65) && (rxCountDR + rxCountSP) > 0) { + if(TDIFFSEC(statsTime,305) && (rxCountCrcOk + rxCountCrcBad) > 0) { statsTime = millis(); - DBG1("LoRa Stats - Rx DR:" + String(rxCountDR) + " SP:" + String(rxCountSP) + " Tx DR:" + String(txCountDR) + " SP:" + String(txCountSP) + " CRC OK: " + String(rxCountCrcOk/(rxCountDR + rxCountSP) * 100) + "%"); + DBG1("LoRa Stats: | RX-(DR:" + String(rxCountDR) + " SP:" + String(rxCountSP) + " UNK:" + String(rxCountUnk) + ") | TX-(DR:" + String(txCountDR) + " SP:" + String(txCountSP) + ") CRC OK: " + String((float)rxCountCrcOk/(rxCountCrcOk + rxCountCrcBad) * 100.00) + "% |"); } // Change to ready at the end so only one transmit happens per function call From 71a5d67e2d0c3931f83f1890db243abc0171dab2 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 22:39:12 -0500 Subject: [PATCH 05/10] Update comments and debugs --- src/fdrs_lora.h | 16 ++++++++-------- src/fdrs_node.h | 7 ++++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index 3bb58e0..a50fcd6 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -245,7 +245,7 @@ void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len) transmitFlag = true; if (state != RADIOLIB_ERR_NONE) { - DBG("Xmit failed, code " + String(state)); + DBG("Xmit failed, code " + String(state) + " program halted."); while (true) ; } @@ -293,7 +293,7 @@ void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len) transmitFlag = true; if (state != RADIOLIB_ERR_NONE) { - DBG("Xmit failed, code " + String(state)); + DBG("Xmit failed, code " + String(state) + " program halted."); while (true) ; } @@ -373,7 +373,7 @@ void begin_lora() } else { - DBG("RadioLib initialization failed, code " + String(state)); + DBG("RadioLib initialization failed, code " + String(state) + " program halted."); while (true) ; } @@ -392,7 +392,7 @@ void begin_lora() state = radio.startReceive(); // start listening for LoRa packets if (state != RADIOLIB_ERR_NONE) { - DBG(" failed, code " + String(state)); + DBG(" failed, code " + String(state) + " program halted."); while (true) ; } @@ -552,7 +552,7 @@ crcResult receiveLoRa() // for Node - need to listen to broadcast if (destMAC == (selfAddress[4] << 8 | selfAddress[5]) || (destMAC == 0xFFFF)) #endif - { // Check if addressed to this device or broadcast + { // printLoraPacket(packet,sizeof(packet)); DBG1("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX)); // Evaluate CRC @@ -567,12 +567,12 @@ crcResult receiveLoRa() if (calcCRC == packetCRC) { // We've received a DR and sending an ACK SystemPacket ACK = {.cmd = cmd_ack, .param = CRC_OK}; - DBG1("CRC Match, sending ACK packet to node 0x" + String(sourceMAC, HEX) + "(hex)"); + DBG1("CRC Match, sending ACK packet to node 0x" + String(sourceMAC, HEX)); transmitLoRaAsync(&sourceMAC, &ACK, 1); // Send ACK back to source } else if (packetCRC == crc16_update(calcCRC, 0xA1)) { // Sender does not want ACK and CRC is valid - DBG1("Node address 0x" + String(sourceMAC, 16) + " does not want ACK"); + DBG1("CRC Match, node address 0x" + String(sourceMAC, 16) + " does not want ACK"); } else { // We've received a DR and CRC is bad @@ -654,7 +654,7 @@ crcResult receiveLoRa() else { // data we have received is not yet programmed. How we handle is future enhancement. DBG2("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement."); - DBG2("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd)); + DBG2("ln: " + String(ln) + " data type: " + String(receiveData[0].cmd + " param: " + String(receiveData[0].param))); } rxCountCrcOk++; return CRC_OK; diff --git a/src/fdrs_node.h b/src/fdrs_node.h index 1a2cc2b..c63f3d3 100644 --- a/src/fdrs_node.h +++ b/src/fdrs_node.h @@ -189,11 +189,13 @@ bool sendFDRS() if (esp_now_ack_flag == CRC_OK) { data_count = 0; + DBG1("FDRS Packet sent successfully!"); return true; } else { data_count = 0; + DBG1("FDRS Packet send failed!"); return false; } #endif // USE_ESPNOW @@ -204,17 +206,20 @@ bool sendFDRS() #ifdef LORA_ACK if(crcReturned == CRC_OK) { data_count = 0; + DBG1("FDRS Packet sent successfully!"); return true; } #endif // LORA_ACK #ifndef LORA_ACK if(crcReturned == CRC_OK || crcReturned == CRC_NULL) { data_count = 0; - return true; + DBG1("FDRS Packet sent successfully!"); + return true; } #endif // !LORA_ACK else { data_count = 0; + DBG1("FDRS Packet send failed!"); return false; } #endif // USE_LORA From b1c1cbc534b81cd749feadaad2a20e02608d43a8 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 23:07:41 -0500 Subject: [PATCH 06/10] LoRa ACK and NAK changes --- src/fdrs_lora.h | 23 +++++++++++++++++------ src/fdrs_node.h | 8 ++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index a50fcd6..e45160b 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -4,7 +4,7 @@ // Internal Globals // Default values: overridden by settings in config, if present -#define GLOBAL_ACK_TIMEOUT 400 // LoRa ACK timeout in ms. (Minimum = 200) +#define GLOBAL_ACK_TIMEOUT 1000 // LoRa ACK timeout in ms. (Minimum = 500) #define GLOBAL_LORA_RETRIES 2 // LoRa ACK automatic retries [0 - 3] #define GLOBAL_LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278)) @@ -578,8 +578,10 @@ crcResult receiveLoRa() { // We've received a DR and CRC is bad SystemPacket NAK = {.cmd = cmd_ack, .param = CRC_BAD}; // Send NAK packet - DBG1("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to node 0x" + String(sourceMAC, HEX) + "(hex)"); - transmitLoRaAsync(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source + // Receiving not configured to listen/respond to NAK so disable for now + DBG1("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX)); + // DBG1("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to node 0x" + String(sourceMAC, HEX) + "(hex)"); + // transmitLoRaAsync(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source newData = event_clear; // do not process data as data may be corrupt rxCountCrcBad++; return CRC_BAD; // Exit function and do not update newData to send invalid data further on @@ -610,10 +612,19 @@ crcResult receiveLoRa() memcpy(receiveData, &packet[4], packetSize - 6); // Split off data portion of packet (N bytes) if (ln == 1 && receiveData[0].cmd == cmd_ack) { - DBG1("ACK Received - CRC Match"); - if(loraAckState == stInProcess) { - loraAckState = stCrcMatch; + if(receiveData[0].param == CRC_OK) { + DBG1("ACK Received - CRC Match"); + if(loraAckState == stInProcess) { + loraAckState = stCrcMatch; + } } + else if(receiveData[0].param == CRC_BAD) { + DBG1("NAK received - CRC Mismatch"); + if(loraAckState == stInProcess) { + loraAckState = stCrcMismatch; + } + // Do we send again - future need to figure out what to do + } } else if (ln == 1 && receiveData[0].cmd == cmd_ping) { // We have received a ping request or reply?? diff --git a/src/fdrs_node.h b/src/fdrs_node.h index c63f3d3..7e3602f 100644 --- a/src/fdrs_node.h +++ b/src/fdrs_node.h @@ -203,20 +203,16 @@ bool sendFDRS() crcResult crcReturned = CRC_NULL; crcReturned = transmitLoRaSync(>wyAddress, fdrsData, data_count); DBG(" LoRa sent."); -#ifdef LORA_ACK if(crcReturned == CRC_OK) { data_count = 0; DBG1("FDRS Packet sent successfully!"); return true; } -#endif // LORA_ACK -#ifndef LORA_ACK - if(crcReturned == CRC_OK || crcReturned == CRC_NULL) { + else if(ack == false && crcReturned == CRC_NULL) { data_count = 0; DBG1("FDRS Packet sent successfully!"); return true; -} -#endif // !LORA_ACK + } else { data_count = 0; DBG1("FDRS Packet send failed!"); From 1f455e7d431845e2b82e47319cdb6a303dfeecda Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 23:18:33 -0500 Subject: [PATCH 07/10] LoRa Inter-message delay changes --- src/fdrs_datatypes.h | 5 +- src/fdrs_lora.h | 113 +++++++++++++++++++++---------------------- 2 files changed, 57 insertions(+), 61 deletions(-) diff --git a/src/fdrs_datatypes.h b/src/fdrs_datatypes.h index 41efb00..863da27 100644 --- a/src/fdrs_datatypes.h +++ b/src/fdrs_datatypes.h @@ -39,11 +39,12 @@ enum ping_t { ping_reply }; -enum pingstate_t { +enum commstate_t { stReady, stInProcess, stCrcMismatch, stCrcMatch, + stInterMessageDelay, stCompleted }; @@ -103,7 +104,7 @@ uint size; }; struct Ping { - pingstate_t status = stReady; + commstate_t status = stReady; unsigned long start; uint timeout; uint16_t address; diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index e45160b..9662a8d 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -8,7 +8,7 @@ #define GLOBAL_LORA_RETRIES 2 // LoRa ACK automatic retries [0 - 3] #define GLOBAL_LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278)) -#define TXDELAYMS 300 +#define INTERMSGDELAY 100 #define SPBUFFSIZE 10 #define LORASIZE ((250 - 15) / sizeof(DataReading)) // 250 bytes minus header/crc data #define DRBUFFSIZE 100 @@ -112,8 +112,8 @@ DRRingBuffer drBuff = {.dr = (DataReading*)calloc(DRBUFFSIZE,sizeof(DataReading) SPRingBuffer spBuff = {.sp = (SystemPacket*)calloc(SPBUFFSIZE,sizeof(SystemPacket)), \ .address = (uint16_t*)calloc(SPBUFFSIZE,sizeof(uint16_t)), .startIdx = 0, .endIdx = 0, .size = SPBUFFSIZE}; -int loraTxState = stReady; -int loraAckState = stReady; +commstate_t loraTxState = stReady; +commstate_t loraAckState = stReady; #ifdef FDRS_GATEWAY @@ -145,6 +145,7 @@ unsigned long rxCountCrcOk = 0; // Number of total Lora packets with unsigned long rxCountCrcBad = 0; // Number of total Lora packets with valid CRC unsigned long txCountDR = 0; // Number of total LoRa DR packets transmitted unsigned long txCountSP = 0; // Number of total LoRa SP packets transmitted +unsigned long lastTxComplete = 0; // Last time that a LoRa transmit was completed extern time_t now; time_t netTimeOffset = UINT32_MAX; // One direction of LoRa Ping time in units of seconds (1/2 full ping time) unsigned long tx_start_time = 0; @@ -832,8 +833,8 @@ void handleLoRa() static uint8_t len = 0; static DataReading *data; static uint16_t address; - static unsigned long lastTxtime = 0; static unsigned long statsTime = 0; + static unsigned long lastTimeSourcePing = 0; // check status of TX or RX operation LoRaTxRxOperation(); @@ -900,9 +901,6 @@ void handleLoRa() // resend original packet again if retries are available loraAckState = stReady; } - if(loraTxState == stCompleted) { - loraTxState = stReady; - } return; } @@ -918,62 +916,54 @@ void handleLoRa() transmitLoRa((spBuff.address + spBuff.startIdx), (spBuff.sp + spBuff.startIdx), 1); BUFFINCSTART(spBuff); } - - - // Time delay between consecutive transmits - TXDELAYMS - if(TDIFF(lastTxtime,(TXDELAYMS + random(0,50)))) { - // Start Transmit data from the DataReading queue - if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady) - { - // for(int i=drBuff.startIdx; i!=drBuff.endIdx; i = (i + 1) % drBuff.size) { - // printf("id: %d, type: %d data: %f address: %X\n",(drBuff.dr + i)->id, (drBuff.dr + i)->t, (drBuff.dr + i)->d, *(spBuff.address + spBuff.startIdx)); - // } - - // data memory is not freed when retries are exhausted and DataReading queue is not full - if(data == NULL) { - // Get number of DRs going to same destination address - len = transmitSameAddrLoRa(); - // TransmitLoRa cannot handle a circular buffer so need data in one contiguous segment of memory - data = (DataReading *)malloc(len * sizeof(DataReading)); - // Transfer data readings from the ring buffer to our local buffer - for(int i=0; i < len; i++) { - *(data + i) = *(drBuff.dr + ((drBuff.startIdx + i) % drBuff.size)); - } - address = *(drBuff.address + drBuff.startIdx); - // now we have the data, we can release it from the ring buffer - drBuff.startIdx = (drBuff.startIdx + len) % drBuff.size; - } - DBG2("Length: " + String(len) + " Address: 0x" + String(address,HEX) + " Data:"); - // for(int i=0; i< len; i++) { - // printf("id: %d, type: %d data: %f\n",(data + i)->id, (data + i)->t, (data + i)->d); - // } - if(!ack && data != NULL) - { - transmitLoRa(&address, data, len); - free(data); - data = NULL; - len = 0; - } - else if(data != NULL) - { - retries--; - loraAckState = stInProcess; - transmitLoRa(&address, data, len); + + // Start Transmit data from the DataReading queue + if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady) + { + // for(int i=drBuff.startIdx; i!=drBuff.endIdx; i = (i + 1) % drBuff.size) { + // printf("id: %d, type: %d data: %f address: %X\n",(drBuff.dr + i)->id, (drBuff.dr + i)->t, (drBuff.dr + i)->d, *(spBuff.address + spBuff.startIdx)); + // } + + // data memory is not freed when retries are exhausted and DataReading queue is not full + if(data == NULL) { + // Get number of DRs going to same destination address + len = transmitSameAddrLoRa(); + // TransmitLoRa cannot handle a circular buffer so need data in one contiguous segment of memory + data = (DataReading *)malloc(len * sizeof(DataReading)); + // Transfer data readings from the ring buffer to our local buffer + for(int i=0; i < len; i++) { + *(data + i) = *(drBuff.dr + ((drBuff.startIdx + i) % drBuff.size)); } + address = *(drBuff.address + drBuff.startIdx); + // now we have the data, we can release it from the ring buffer + drBuff.startIdx = (drBuff.startIdx + len) % drBuff.size; } - - // Ping LoRa time master to estimate time delay in radio link - if(timeSource.tmNetIf == TMIF_LORA) { - static unsigned long lastTimeSourcePing = 0; - - // ping the time source every 10 minutes - if(TDIFFMIN(lastTimeSourcePing,10) || lastTimeSourcePing == 0) { - pingRequestLoRa(timeSource.tmAddress,4000); - lastTimeSourcePing = millis(); - } + DBG2("Length: " + String(len) + " Address: 0x" + String(address,HEX) + " Data:"); + // for(int i=0; i< len; i++) { + // printf("id: %d, type: %d data: %f\n",(data + i)->id, (data + i)->t, (data + i)->d); + // } + if(!ack && data != NULL) + { + transmitLoRa(&address, data, len); + free(data); + data = NULL; + len = 0; } - lastTxtime = millis(); + else if(data != NULL) + { + retries--; + loraAckState = stInProcess; + transmitLoRa(&address, data, len); + } + } + + // Ping LoRa time master to estimate time delay in radio link + // ping the time source every 10 minutes + if(timeSource.tmNetIf == TMIF_LORA && (TDIFFMIN(lastTimeSourcePing,10) || lastTimeSourcePing == 0)) { + pingRequestLoRa(timeSource.tmAddress,4000); + lastTimeSourcePing = millis(); } + // Print LoRa statistics if(TDIFFSEC(statsTime,305) && (rxCountCrcOk + rxCountCrcBad) > 0) { statsTime = millis(); @@ -981,7 +971,12 @@ void handleLoRa() } // Change to ready at the end so only one transmit happens per function call + // also enforce LoRa inter-message transmit delays if(loraTxState == stCompleted) { + loraTxState = stInterMessageDelay; + lastTxComplete = millis(); + } + else if(loraTxState == stInterMessageDelay && TDIFF(lastTxComplete,(INTERMSGDELAY + random(0,50)))) { loraTxState = stReady; } return; From 586ed6e6036fd1378ab11c6296ae9d53ac081e54 Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Sun, 16 Jun 2024 23:22:38 -0500 Subject: [PATCH 08/10] LoRa Update transmitLoRaSync with inter-message delays --- src/fdrs_lora.h | 101 ++++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/fdrs_lora.h b/src/fdrs_lora.h index 9662a8d..751de7c 100644 --- a/src/fdrs_lora.h +++ b/src/fdrs_lora.h @@ -302,52 +302,6 @@ void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len) return; } -// Synchronously Transmits LoRa data and, if configured, listens for ACKs -// Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data -crcResult transmitLoRaSync(uint16_t *destMac, DataReading *packet, uint8_t len) -{ - crcResult crcReturned = CRC_NULL; - // Do we check to see if another Async operation is in progress first??? - // If Yes, what do we do if another operation is in progress? Return and rely on retransmit retry? - // if(loraTxState == stReady) { } - transmitLoRa(destMac, packet, len); - - // if LORA_ACK is defined - if(ack) { - int retries = FDRS_LORA_RETRIES + 1; - while (retries != 0) - { - unsigned long loraAckTimeout = millis() + FDRS_ACK_TIMEOUT; - retries--; - delay(10); - while (crcReturned == CRC_NULL && (millis() < loraAckTimeout)) - { - crcReturned = LoRaTxRxOperation(); - yield(); - } - if (crcReturned == CRC_OK) - { - DBG1("LoRa ACK Received! CRC OK"); - return CRC_OK; // we're done - } - else if (crcReturned == CRC_BAD) - { - DBG1("LoRa ACK Received! CRC BAD"); - // Resend original packet again if retries are available - } - else - { - DBG1("LoRa Timeout waiting for ACK!"); - // resend original packet again if retries are available - } - // Here is our retry - transmitLoRa(destMac, packet, len); - } - - } - return crcReturned; -} - void begin_lora() { #ifdef CUSTOM_SPI @@ -982,6 +936,61 @@ void handleLoRa() return; } +// Synchronously Transmits LoRa data and, if configured, listens for ACKs +// Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data +crcResult transmitLoRaSync(uint16_t *destMac, DataReading *packet, uint8_t len) +{ + crcResult crcReturned = CRC_NULL; + unsigned long timeout = millis() + (3 * INTERMSGDELAY); + + // LoRa inter-transmit message delay to prevent data corruption in LoRa packets. + // We don't want to delay forever so we need a timeout as well + while(loraTxState != stReady && millis() < timeout) { + handleLoRa(); + yield(); + } + if(millis() > timeout) { + DBG("LoRa Error. TxState not ready"); + return CRC_BAD; + } + transmitLoRa(destMac, packet, len); + + // if LORA_ACK is defined + if(ack) { + int retries = FDRS_LORA_RETRIES + 1; + while (retries != 0) + { + unsigned long loraAckTimeout = millis() + FDRS_ACK_TIMEOUT; + retries--; + delay(10); + while (crcReturned == CRC_NULL && (millis() < loraAckTimeout)) + { + crcReturned = LoRaTxRxOperation(); + yield(); + } + if (crcReturned == CRC_OK) + { + DBG1("LoRa ACK Received! CRC OK"); + return CRC_OK; // we're done + } + else if (crcReturned == CRC_BAD) + { + DBG1("LoRa ACK Received! CRC BAD"); + // Resend original packet again if retries are available + } + else + { + DBG1("LoRa Timeout waiting for ACK!"); + // resend original packet again if retries are available + } + // Here is our retry + transmitLoRa(destMac, packet, len); + } + + } + return crcReturned; +} + // Only for use in nodes - not intended to be used in gateway bool reqTimeLoRa() { SystemPacket sys_packet = {.cmd = cmd_time, .param = 0}; From 1a131a8c0f0ae33ef76731fd2681d07289a5ebbf Mon Sep 17 00:00:00 2001 From: Jeff Lehman Date: Wed, 26 Jun 2024 18:32:00 -0500 Subject: [PATCH 09/10] sendFDRS synchronous transmit - compilation testing --- src/fdrs_node.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/fdrs_node.h b/src/fdrs_node.h index 7e3602f..7bcc212 100644 --- a/src/fdrs_node.h +++ b/src/fdrs_node.h @@ -270,11 +270,13 @@ void loadFDRS(float d, uint8_t t, uint16_t id) void sleepFDRS(uint32_t sleep_time) { // make sure all Async operations are completed +#ifdef USE_LORA unsigned long timeout = millis() + 1000; while(millis() < timeout && !isLoRaAsyncComplete()) { handleLoRa(); yield(); } +#endif // USE_LORA #ifdef DEEP_SLEEP DBG(" Deep sleeping."); #ifdef ESP32 @@ -284,7 +286,7 @@ void sleepFDRS(uint32_t sleep_time) #ifdef ESP8266 ESP.deepSleep(sleep_time * 1000000); #endif -#endif +#endif // DEEP_SLEEP DBG(" Delaying."); delay(sleep_time * 1000); } From e1a2a205f6d3bff9ea8ce50eb2487370166032b4 Mon Sep 17 00:00:00 2001 From: Timm Bogner <64260873+timmbogner@users.noreply.github.com> Date: Fri, 28 Jun 2024 07:47:03 -0500 Subject: [PATCH 10/10] update docs for async --- extras/Node.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extras/Node.md b/extras/Node.md index d88b165..d254234 100644 --- a/extras/Node.md +++ b/extras/Node.md @@ -62,13 +62,15 @@ Loads some data into the current packet. 'data' is a float, 'type' is the data t #### `loadFDRS((float data, uint8_t type);` Same as above, but the 'id' is preset to the node's `READING_ID`. #### `bool sendFDRS();` -Sends the current packet using ESP-NOW and/or LoRa. Returns true if packet is confirmed to have been recieved successfully by the gateway. +Sends the current packet using ESP-NOW and/or LoRa. Returns true if packet is confirmed to have been received successfully by the gateway. +#### `bool sendFDRSAsync();` +Queues the current packet to be sent asynchronously (in the background). Be sure to use `loopFDRS()` to ensure the system can process the packet. Returns true if packet is successfully added to queue. #### `sleepFDRS(seconds)` Time to sleep in seconds. If ```DEEP_SLEEP``` is enabled, the device will enter sleep. Otherwise it will use a simple ```delay()```. ## Controller API #### ```addFDRS(void callback);``` -Initializes controller functionality by selecting the function to be called when incoming commands are recieved. If using LoRa, the controller will automatically recieve any packets sent with broadcastLoRa(), provided they were sent by the paired gateway. ESP-NOW requires the device to register with its gateway before it will recieve incoming commands. This is done automatically, and the ESP-NOW node will continue recieving data until the paired gateway is reset. A maximum of 16 ESP-NOW controllers can recieve data from a single gateway. There is no limit to how many LoRa controllers can listen to the same gateway. +Initializes controller functionality by selecting the function to be called when incoming commands are received. If using LoRa, the controller will automatically receive any packets sent with broadcastLoRa(), provided they were sent by the paired gateway. ESP-NOW requires the device to register with its gateway before it will receive incoming commands. This is done automatically, and the ESP-NOW node will continue recieving data until the paired gateway is reset. A maximum of 16 ESP-NOW controllers can receive data from a single gateway. There is no limit to how many LoRa controllers can listen to the same gateway. #### ```subscribeFDRS(uint16_t sub_id);``` Sets the device to listen for a specific DataReading id. When a DataReading with id ```sub_id``` is received, the callback function will be called and given the full DataReading as a parameter. #### ```unsubscribeFDRS(uint16_t sub_id);``` @@ -120,7 +122,7 @@ OLED I²C pins. OLED reset pin. Use '-1' if not present or known. # ## Callback Function -The callback function is executed when data arrives with an ID that the controller is subscribed to. Inside of this function, the user has access to the incoming DataReading. If multiple readings are recieved, the function will be called for each of them. While you should always be brief in interrupt callbacks (ISRs), it's okay to do more in this one. +The callback function is executed when data arrives with an ID that the controller is subscribed to. Inside of this function, the user has access to the incoming DataReading. If multiple readings are received, the function will be called for each of them. While you should always be brief in interrupt callbacks (ISRs), it's okay to do more in this one. ## Type Definitions For the moment, my thought is to reserve the first two bits of the type. I might use them in the future to indicate the data size or type (bool, char,  int, float, etc?). This leaves us with 64 possible type definitions. If you have more types to add, please get in touch! ```