Merge pull request #217 from aviateur17/lora_tx_sync

revert sendFDRS to Synchronous operations, add sendFDRSAsync
main
Timm Bogner 4 months ago committed by GitHub
commit d6e63eb50b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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);` #### `loadFDRS((float data, uint8_t type);`
Same as above, but the 'id' is preset to the node's `READING_ID`. Same as above, but the 'id' is preset to the node's `READING_ID`.
#### `bool sendFDRS();` #### `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)` #### `sleepFDRS(seconds)`
Time to sleep in seconds. If ```DEEP_SLEEP``` is enabled, the device will enter sleep. Otherwise it will use a simple ```delay()```. Time to sleep in seconds. If ```DEEP_SLEEP``` is enabled, the device will enter sleep. Otherwise it will use a simple ```delay()```.
## Controller API ## Controller API
#### ```addFDRS(void callback);``` #### ```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);``` #### ```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. 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);``` #### ```unsubscribeFDRS(uint16_t sub_id);```
@ -120,7 +122,7 @@ OLED I²C pins.
OLED reset pin. Use '-1' if not present or known. OLED reset pin. Use '-1' if not present or known.
# #
## Callback Function ## 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 ## 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! 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!
``` ```

@ -39,11 +39,12 @@ enum ping_t {
ping_reply ping_reply
}; };
enum pingstate_t { enum commstate_t {
stReady, stReady,
stInProcess, stInProcess,
stCrcMismatch, stCrcMismatch,
stCrcMatch, stCrcMatch,
stInterMessageDelay,
stCompleted stCompleted
}; };
@ -103,7 +104,7 @@ uint size;
}; };
struct Ping { struct Ping {
pingstate_t status = stReady; commstate_t status = stReady;
unsigned long start; unsigned long start;
uint timeout; uint timeout;
uint16_t address; uint16_t address;

@ -4,13 +4,13 @@
// Internal Globals // Internal Globals
// Default values: overridden by settings in config, if present // 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_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 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 SPBUFFSIZE 10
#define LORASIZE (250 / sizeof(DataReading)) #define LORASIZE ((250 - 15) / sizeof(DataReading)) // 250 bytes minus header/crc data
#define DRBUFFSIZE 100 #define DRBUFFSIZE 100
#define ISBUFFEMPTY(buff) ((buff.endIdx == buff.startIdx) ? true: false) #define ISBUFFEMPTY(buff) ((buff.endIdx == buff.startIdx) ? true: false)
#define ISBUFFFULL(buff) (((buff.endIdx + 1) % buff.size) == buff.startIdx ? true: false) #define ISBUFFFULL(buff) (((buff.endIdx + 1) % buff.size) == buff.startIdx ? true: false)
@ -112,8 +112,8 @@ DRRingBuffer drBuff = {.dr = (DataReading*)calloc(DRBUFFSIZE,sizeof(DataReading)
SPRingBuffer spBuff = {.sp = (SystemPacket*)calloc(SPBUFFSIZE,sizeof(SystemPacket)), \ SPRingBuffer spBuff = {.sp = (SystemPacket*)calloc(SPBUFFSIZE,sizeof(SystemPacket)), \
.address = (uint16_t*)calloc(SPBUFFSIZE,sizeof(uint16_t)), .startIdx = 0, .endIdx = 0, .size = SPBUFFSIZE}; .address = (uint16_t*)calloc(SPBUFFSIZE,sizeof(uint16_t)), .startIdx = 0, .endIdx = 0, .size = SPBUFFSIZE};
int loraTxState = stReady; commstate_t loraTxState = stReady;
int loraAckState = stReady; commstate_t loraAckState = stReady;
#ifdef FDRS_GATEWAY #ifdef FDRS_GATEWAY
@ -140,9 +140,12 @@ volatile bool operationDone = false; // flag to indicate that a packet was sent
unsigned long loraAckTimeout = 0; unsigned long loraAckTimeout = 0;
unsigned long rxCountDR = 0; // Number of total LoRa DR packets destined for us and of valid size 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 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 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 txCountDR = 0; // Number of total LoRa DR packets transmitted
unsigned long txCountSP = 0; // Number of total LoRa SP 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; extern time_t now;
time_t netTimeOffset = UINT32_MAX; // One direction of LoRa Ping time in units of seconds (1/2 full ping time) 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; unsigned long tx_start_time = 0;
@ -195,7 +198,7 @@ void printLoraPacket(uint8_t *p, int size)
printf("Printing packet of size %d.", size); printf("Printing packet of size %d.", size);
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
if (i % 2 == 0) if (i % 16 == 0)
printf("\n%02d: ", i); printf("\n%02d: ", i);
printf("%02X ", p[i]); printf("%02X ", p[i]);
} }
@ -203,10 +206,16 @@ void printLoraPacket(uint8_t *p, int size)
} }
// Do not call this function directly, instead call transmitLoRaAsync(...) // Do not call this function directly, instead call transmitLoRaAsync(...)
// or transmitLoRaSync(....)
// Transmits Lora data by calling RadioLib library function // 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) 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))]; uint8_t pkt[6 + (len * sizeof(DataReading))];
uint16_t calcCRC = 0x0000; uint16_t calcCRC = 0x0000;
loraTxState = stInProcess; loraTxState = stInProcess;
@ -237,7 +246,7 @@ void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len)
transmitFlag = true; transmitFlag = true;
if (state != RADIOLIB_ERR_NONE) if (state != RADIOLIB_ERR_NONE)
{ {
DBG("Xmit failed, code " + String(state)); DBG("Xmit failed, code " + String(state) + " program halted.");
while (true) while (true)
; ;
} }
@ -245,10 +254,17 @@ void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len)
return; return;
} }
// For now SystemPackets will not use ACK but will calculate CRC // Transmits LoRa SystemPacket Data.
// Returns CRC_NULL as SystemPackets do not use ACKS at current time // 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) 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))]; uint8_t pkt[6 + (len * sizeof(SystemPacket))];
uint16_t calcCRC = 0x0000; uint16_t calcCRC = 0x0000;
loraTxState = stInProcess; loraTxState = stInProcess;
@ -278,7 +294,7 @@ void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len)
transmitFlag = true; transmitFlag = true;
if (state != RADIOLIB_ERR_NONE) if (state != RADIOLIB_ERR_NONE)
{ {
DBG("Xmit failed, code " + String(state)); DBG("Xmit failed, code " + String(state) + " program halted.");
while (true) while (true)
; ;
} }
@ -312,7 +328,7 @@ void begin_lora()
} }
else else
{ {
DBG("RadioLib initialization failed, code " + String(state)); DBG("RadioLib initialization failed, code " + String(state) + " program halted.");
while (true) while (true)
; ;
} }
@ -331,12 +347,14 @@ void begin_lora()
state = radio.startReceive(); // start listening for LoRa packets state = radio.startReceive(); // start listening for LoRa packets
if (state != RADIOLIB_ERR_NONE) if (state != RADIOLIB_ERR_NONE)
{ {
DBG(" failed, code " + String(state)); DBG(" failed, code " + String(state) + " program halted.");
while (true) while (true)
; ;
} }
} }
// 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) bool transmitLoRaAsync(uint16_t *destAddr, SystemPacket *sp, uint8_t len)
{ {
for(int i=0; i < len; i++) for(int i=0; i < len; i++)
@ -357,6 +375,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 // 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) 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 // Write as much as needed and just flush out the older data if too much data writing to buffer
@ -381,6 +401,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 // 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 transmitSameAddrLoRa() {
uint count = 0; uint count = 0;
@ -459,10 +480,7 @@ bool pingReplyLoRa(uint16_t address)
} }
// ****DO NOT CALL receiveLoRa() directly! ***** Call handleLoRa() instead! // ****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 // Return type is crcResult struct - CRC_OK, CRC_BAD, CRC_NULL. CRC_NULL used for non-ack data
crcResult receiveLoRa() crcResult receiveLoRa()
{ {
int packetSize = radio.getPacketLength(); int packetSize = radio.getPacketLength();
@ -489,7 +507,7 @@ crcResult receiveLoRa()
// for Node - need to listen to broadcast // for Node - need to listen to broadcast
if (destMAC == (selfAddress[4] << 8 | selfAddress[5]) || (destMAC == 0xFFFF)) if (destMAC == (selfAddress[4] << 8 | selfAddress[5]) || (destMAC == 0xFFFF))
#endif #endif
{ // Check if addressed to this device or broadcast {
// printLoraPacket(packet,sizeof(packet)); // 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)); DBG1("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX));
// Evaluate CRC // Evaluate CRC
@ -504,39 +522,40 @@ crcResult receiveLoRa()
if (calcCRC == packetCRC) if (calcCRC == packetCRC)
{ // We've received a DR and sending an ACK { // We've received a DR and sending an ACK
SystemPacket ACK = {.cmd = cmd_ack, .param = CRC_OK}; 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 transmitLoRaAsync(&sourceMAC, &ACK, 1); // Send ACK back to source
} }
else if (packetCRC == crc16_update(calcCRC, 0xA1)) else if (packetCRC == crc16_update(calcCRC, 0xA1))
{ // Sender does not want ACK and CRC is valid { // 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 else
{ // We've received a DR and CRC is bad { // We've received a DR and CRC is bad
SystemPacket NAK = {.cmd = cmd_ack, .param = CRC_BAD}; SystemPacket NAK = {.cmd = cmd_ack, .param = CRC_BAD};
// Send NAK packet // 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)"); // Receiving not configured to listen/respond to NAK so disable for now
transmitLoRaAsync(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source 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 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 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)) 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); ln = (packetSize - 6) / sizeof(DataReading);
if (memcmp(&sourceMAC, &LoRa1, 2) == 0) if (memcmp(&sourceMAC, &LoRa1, 2) == 0)
{ // Check if it is from a registered sender { // Check if it is from a registered sender
newData = event_lora1; newData = event_lora1;
return CRC_OK;
} }
else if (memcmp(&sourceMAC, &LoRa2, 2) == 0) else if (memcmp(&sourceMAC, &LoRa2, 2) == 0)
{ {
newData = event_lora2; newData = event_lora2;
return CRC_OK;
} }
else { else {
newData = event_lorag; newData = event_lorag;
return CRC_OK;
} }
rxCountCrcOk++;
return CRC_OK;
} }
else if ((packetSize - 6) == sizeof(SystemPacket)) else if ((packetSize - 6) == sizeof(SystemPacket))
{ {
@ -548,10 +567,19 @@ crcResult receiveLoRa()
memcpy(receiveData, &packet[4], packetSize - 6); // Split off data portion of packet (N bytes) memcpy(receiveData, &packet[4], packetSize - 6); // Split off data portion of packet (N bytes)
if (ln == 1 && receiveData[0].cmd == cmd_ack) if (ln == 1 && receiveData[0].cmd == cmd_ack)
{ {
DBG1("ACK Received - CRC Match"); if(receiveData[0].param == CRC_OK) {
if(loraAckState == stInProcess) { DBG1("ACK Received - CRC Match");
loraAckState = stCrcMatch; 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) else if (ln == 1 && receiveData[0].cmd == cmd_ping)
{ // We have received a ping request or reply?? { // We have received a ping request or reply??
@ -592,7 +620,7 @@ crcResult receiveLoRa()
else else
{ // data we have received is not yet programmed. How we handle is future enhancement. { // 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("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++; rxCountCrcOk++;
return CRC_OK; return CRC_OK;
@ -603,6 +631,7 @@ crcResult receiveLoRa()
if(loraAckState == stInProcess) { if(loraAckState == stInProcess) {
loraAckState = stCrcMismatch; loraAckState = stCrcMismatch;
} }
rxCountCrcBad++;
return CRC_BAD; return CRC_BAD;
} }
} }
@ -611,6 +640,7 @@ crcResult receiveLoRa()
{ // Uncommenting below will print out packets from other LoRa controllers being sent. { // 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)); // 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)); // printLoraPacket(packet,sizeof(packet));
rxCountUnk++;
return CRC_NULL; return CRC_NULL;
} }
} }
@ -622,12 +652,15 @@ crcResult receiveLoRa()
// uint8_t packet[packetSize]; // uint8_t packet[packetSize];
// radio.readData((uint8_t *)&packet, packetSize); // radio.readData((uint8_t *)&packet, packetSize);
// printLoraPacket(packet,sizeof(packet)); // printLoraPacket(packet,sizeof(packet));
rxCountUnk++;
return CRC_NULL; return CRC_NULL;
} }
} }
return CRC_NULL; 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 LoRaTxRxOperation()
{ {
crcResult crcReturned = CRC_NULL; crcResult crcReturned = CRC_NULL;
@ -680,7 +713,7 @@ int pingRequestLoRa(uint16_t address, uint32_t timeout)
loraPing.timeout = timeout; loraPing.timeout = timeout;
loraPing.address = address; 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) { if(loraTxState == stReady) {
loraPing.status = stInProcess; loraPing.status = stInProcess;
loraPing.start = millis(); loraPing.start = millis();
@ -748,14 +781,16 @@ void sendLoRaNbr(uint8_t interface)
} }
} }
// Handles completion of Asynchronous LoRa processes
void handleLoRa() void handleLoRa()
{ {
static uint8_t len = 0; static uint8_t len = 0;
static DataReading *data; static DataReading *data;
static uint16_t address; static uint16_t address;
static unsigned long lastTxtime = 0;
static unsigned long statsTime = 0; static unsigned long statsTime = 0;
static unsigned long lastTimeSourcePing = 0;
// check status of TX or RX operation
LoRaTxRxOperation(); LoRaTxRxOperation();
// check for result of any ongoing async ping operations // check for result of any ongoing async ping operations
@ -820,9 +855,6 @@ void handleLoRa()
// resend original packet again if retries are available // resend original packet again if retries are available
loraAckState = stReady; loraAckState = stReady;
} }
if(loraTxState == stCompleted) {
loraTxState = stReady;
}
return; return;
} }
@ -838,75 +870,127 @@ void handleLoRa()
transmitLoRa((spBuff.address + spBuff.startIdx), (spBuff.sp + spBuff.startIdx), 1); transmitLoRa((spBuff.address + spBuff.startIdx), (spBuff.sp + spBuff.startIdx), 1);
BUFFINCSTART(spBuff); BUFFINCSTART(spBuff);
} }
// Start Transmit data from the DataReading queue
// It's polite to Listen more than you talk if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady)
if(TDIFF(lastTxtime,(TXDELAYMS + random(0,50)))) { {
// Start Transmit data from the DataReading queue // for(int i=drBuff.startIdx; i!=drBuff.endIdx; i = (i + 1) % drBuff.size) {
if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady) // 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));
{ // }
// 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
// data memory is not freed when retries are exhausted and DataReading queue is not full len = transmitSameAddrLoRa();
if(data == NULL) { // TransmitLoRa cannot handle a circular buffer so need data in one contiguous segment of memory
// Get number of DRs going to same destination address data = (DataReading *)malloc(len * sizeof(DataReading));
len = transmitSameAddrLoRa(); // Transfer data readings from the ring buffer to our local buffer
// TransmitLoRa cannot handle a circular buffer so need data in one contiguous segment of memory for(int i=0; i < len; i++) {
data = (DataReading *)malloc(len * sizeof(DataReading)); *(data + i) = *(drBuff.dr + ((drBuff.startIdx + i) % drBuff.size));
// 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);
} }
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:");
// Ping LoRa time master to estimate time delay in radio link // for(int i=0; i< len; i++) {
if(timeSource.tmNetIf == TMIF_LORA) { // printf("id: %d, type: %d data: %f\n",(data + i)->id, (data + i)->t, (data + i)->d);
static unsigned long lastTimeSourcePing = 0; // }
if(!ack && data != NULL)
// ping the time source every 10 minutes {
if(TDIFFMIN(lastTimeSourcePing,10) || lastTimeSourcePing == 0) { transmitLoRa(&address, data, len);
pingRequestLoRa(timeSource.tmAddress,4000); free(data);
lastTimeSourcePing = millis(); data = NULL;
} len = 0;
}
else if(data != NULL)
{
retries--;
loraAckState = stInProcess;
transmitLoRa(&address, data, len);
} }
lastTxtime = millis();
} }
// 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 // Print LoRa statistics
if(TDIFFSEC(statsTime,65) && (rxCountDR + rxCountSP) > 0) { if(TDIFFSEC(statsTime,305) && (rxCountCrcOk + rxCountCrcBad) > 0) {
statsTime = millis(); 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 // Change to ready at the end so only one transmit happens per function call
// also enforce LoRa inter-message transmit delays
if(loraTxState == stCompleted) { if(loraTxState == stCompleted) {
loraTxState = stInterMessageDelay;
lastTxComplete = millis();
}
else if(loraTxState == stInterMessageDelay && TDIFF(lastTxComplete,(INTERMSGDELAY + random(0,50)))) {
loraTxState = stReady; loraTxState = stReady;
} }
return; 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 // Only for use in nodes - not intended to be used in gateway
bool reqTimeLoRa() { bool reqTimeLoRa() {
SystemPacket sys_packet = {.cmd = cmd_time, .param = 0}; SystemPacket sys_packet = {.cmd = cmd_time, .param = 0};
@ -923,4 +1007,17 @@ bool reqTimeLoRa() {
} }
} }
return false; 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;
}
} }

@ -13,7 +13,7 @@ DataReading theData[256];
uint8_t ln; uint8_t ln;
uint8_t newData = event_clear; uint8_t newData = event_clear;
uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC}; 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; crcResult crcReturned = CRC_NULL;
uint8_t incMAC[6]; uint8_t incMAC[6];
@ -170,6 +170,8 @@ void handleIncoming()
} }
} }
// Sends FDRS data synchronously
// Returns true if success and false if not successful
bool sendFDRS() bool sendFDRS()
{ {
if(data_count == 0) { if(data_count == 0) {
@ -187,40 +189,63 @@ bool sendFDRS()
if (esp_now_ack_flag == CRC_OK) if (esp_now_ack_flag == CRC_OK)
{ {
data_count = 0; data_count = 0;
DBG1("FDRS Packet sent successfully!");
return true; return true;
} }
else else
{ {
data_count = 0; data_count = 0;
DBG1("FDRS Packet send failed!");
return false; return false;
} }
#endif #endif // USE_ESPNOW
#ifdef USE_LORA #ifdef USE_LORA
transmitLoRaAsync(&gtwyAddress, fdrsData, data_count); crcResult crcReturned = CRC_NULL;
// DBG(" LoRa sent."); crcReturned = transmitLoRaSync(&gtwyAddress, fdrsData, data_count);
#ifdef LORA_ACK DBG(" LoRa sent.");
if(crcReturned == CRC_OK) { if(crcReturned == CRC_OK) {
data_count = 0; data_count = 0;
DBG1("FDRS Packet sent successfully!");
return true; return true;
} }
#endif else if(ack == false && crcReturned == CRC_NULL) {
#ifndef LORA_ACK
if(crcReturned == CRC_OK || crcReturned == CRC_NULL) {
data_count = 0; data_count = 0;
return true; DBG1("FDRS Packet sent successfully!");
} return true;
#endif }
else { else {
data_count = 0; data_count = 0;
DBG1("FDRS Packet send failed!");
return false; 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(&gtwyAddress, fdrsData, data_count);
data_count = 0;
return true;
#endif // USE_LORA
} }
void loadFDRS(float d, uint8_t t) void loadFDRS(float d, uint8_t t)
{ {
DBG("Id: " + String(READING_ID) + " - Type: " + String(t) + " - Data loaded: " + String(d)); DBG("Id: " + String(READING_ID) + " - Type: " + String(t) + " - Data loaded: " + String(d));
if (data_count > espnow_size) if (data_count >= espnow_size)
sendFDRS(); sendFDRS();
DataReading dr; DataReading dr;
dr.id = READING_ID; dr.id = READING_ID;
@ -232,7 +257,7 @@ void loadFDRS(float d, uint8_t t)
void loadFDRS(float d, uint8_t t, uint16_t id) void loadFDRS(float d, uint8_t t, uint16_t id)
{ {
DBG("Id: " + String(id) + " - Type: " + String(t) + " - Data loaded: " + String(d)); DBG("Id: " + String(id) + " - Type: " + String(t) + " - Data loaded: " + String(d));
if (data_count > espnow_size) if (data_count >= espnow_size)
sendFDRS(); sendFDRS();
DataReading dr; DataReading dr;
dr.id = id; dr.id = id;
@ -244,6 +269,14 @@ void loadFDRS(float d, uint8_t t, uint16_t id)
void sleepFDRS(uint32_t sleep_time) 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 #ifdef DEEP_SLEEP
DBG(" Deep sleeping."); DBG(" Deep sleeping.");
#ifdef ESP32 #ifdef ESP32
@ -253,7 +286,7 @@ void sleepFDRS(uint32_t sleep_time)
#ifdef ESP8266 #ifdef ESP8266
ESP.deepSleep(sleep_time * 1000000); ESP.deepSleep(sleep_time * 1000000);
#endif #endif
#endif #endif // DEEP_SLEEP
DBG(" Delaying."); DBG(" Delaying.");
delay(sleep_time * 1000); delay(sleep_time * 1000);
} }

@ -289,6 +289,7 @@ void checkDST() {
// Periodically send time to ESP-NOW or LoRa nodes associated with this gateway/controller // Periodically send time to ESP-NOW or LoRa nodes associated with this gateway/controller
void sendTime() { void sendTime() {
#ifdef FDRS_GATEWAY // Nodes do not send out time
if(validTime()) { // Only send time if it is valid if(validTime()) { // Only send time if it is valid
DBG1("Sending out time"); DBG1("Sending out time");
#if defined(USE_WIFI) || defined(USE_ETHERNET) #if defined(USE_WIFI) || defined(USE_ETHERNET)
@ -297,6 +298,7 @@ void sendTime() {
sendTimeLoRa(); sendTimeLoRa();
sendTimeESPNow(); sendTimeESPNow();
} }
#endif // FDRS_GATEWAY
} }
// time parameter is in Unix Time format UTC time zone // time parameter is in Unix Time format UTC time zone

Loading…
Cancel
Save