Rework LoRa code - combine gateway and node code together

pull/193/head
Jeff Lehman 3 months ago
parent 978825bf5a
commit 08724ab20d

@ -72,6 +72,32 @@ typedef struct TimeSource {
unsigned long tmLastTimeSet;
} TimeSource;
enum LoRaState {
stReady,
stInProcess,
stCrcMismatch,
stCrcMatch,
stCompleted
};
typedef struct LoRaPing {
LoRaState status = stReady;
unsigned long start;
uint timeout;
uint16_t address;
uint32_t response = __UINT32_MAX__;
} LoRaPing;
typedef struct LoRaTaskSP {
SystemPacket data;
uint16_t dstAddress;
} tskLoRaTaskSP;
typedef struct LoRaTaskDR {
DataReading data;
uint16_t dstAddress;
} tskLoRaTaskDR;
#ifndef ESP32
typedef int esp_err_t;
#define ESP_FAIL 0

@ -83,7 +83,7 @@ void printFDRS(DataReading*, int);
#include "fdrs_gateway_espnow.h"
#endif
#ifdef USE_LORA
#include "fdrs_gateway_lora.h"
#include "fdrs_lora.h"
#endif
#ifdef USE_WIFI
#include "fdrs_gateway_wifi.h"

@ -1,740 +0,0 @@
#include <RadioLib.h>
#define GLOBAL_ACK_TIMEOUT 400 // LoRa ACK timeout in ms. (Minimum = 200)
#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))
// select LoRa band configuration
#if defined(LORA_FREQUENCY)
#define FDRS_LORA_FREQUENCY LORA_FREQUENCY
#else
#define FDRS_LORA_FREQUENCY GLOBAL_LORA_FREQUENCY
#endif // LORA_FREQUENCY
// select LoRa SF configuration
#if defined(LORA_SF)
#define FDRS_LORA_SF LORA_SF
#else
#define FDRS_LORA_SF GLOBAL_LORA_SF
#endif // LORA_SF
// select LoRa ACK configuration
#if defined(LORA_ACK) || defined(GLOBAL_LORA_ACK)
#define FDRS_LORA_ACK
#endif // LORA_ACK
// select LoRa ACK Timeout configuration
#if defined(LORA_ACK_TIMEOUT)
#define FDRS_ACK_TIMEOUT LORA_ACK_TIMEOUT
#else
#define FDRS_ACK_TIMEOUT GLOBAL_ACK_TIMEOUT
#endif // LORA_ACK_TIMEOUT
// select LoRa Retry configuration
#if defined(LORA_RETRIES)
#define FDRS_LORA_RETRIES LORA_RETRIES
#else
#define FDRS_LORA_RETRIES GLOBAL_LORA_RETRIES
#endif // LORA_RETRIES
// select LoRa Tx Power configuration
#if defined(LORA_TXPWR)
#define FDRS_LORA_TXPWR LORA_TXPWR
#else
#define FDRS_LORA_TXPWR GLOBAL_LORA_TXPWR
#endif // LORA_TXPWR
// select LoRa BANDWIDTH configuration
#if defined(LORA_BANDWIDTH)
#define FDRS_LORA_BANDWIDTH LORA_BANDWIDTH
#else
#define FDRS_LORA_BANDWIDTH GLOBAL_LORA_BANDWIDTH
#endif // LORA_BANDWIDTH
// select LoRa Coding Rate configuration
#if defined(LORA_CR)
#define FDRS_LORA_CR LORA_CR
#else
#define FDRS_LORA_CR GLOBAL_LORA_CR
#endif // LORA_CR
// select LoRa SyncWord configuration
#if defined(LORA_SYNCWORD)
#define FDRS_LORA_SYNCWORD LORA_SYNCWORD
#else
#define FDRS_LORA_SYNCWORD GLOBAL_LORA_SYNCWORD
#endif // LORA_SYNCWORD
// select LoRa Release Interval configuration
#if defined(LORA_INTERVAL)
#define FDRS_LORA_INTERVAL LORA_INTERVAL
#else
#define FDRS_LORA_INTERVAL GLOBAL_LORA_INTERVAL
#endif // LORA_INTERVAL
#ifndef LORA_BUSY
#define LORA_BUSY RADIOLIB_NC
#endif
const uint8_t lora_size = 250 / sizeof(DataReading);
#ifdef CUSTOM_SPI
#ifdef ARDUINO_ARCH_RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI1);
#endif // RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI);
#else
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY);
#endif // CUSTOM_SPI
enum PingStatusLoRa {
psNotStarted,
psWaiting,
psCompleted,
};
typedef struct LoRaPing {
PingStatusLoRa status = psNotStarted;
unsigned long start;
uint timeout;
uint16_t address;
uint32_t response = __UINT32_MAX__;
} LoRaPing;
LoRaPing loraPing;
#ifndef USE_ESPNOW // mac_prefix used for both ESP-NOW and LoRa - avoid redefinition warnings
const uint8_t mac_prefix[] = {MAC_PREFIX};
const uint8_t selfAddress[] = {MAC_PREFIX, UNIT_MAC};
#endif
bool transmitFlag = false; // flag to indicate transmission or reception state
volatile bool enableInterrupt = true; // disable interrupt when it's not needed
volatile bool operationDone = false; // flag to indicate that a packet was sent or received
uint16_t LoRa1 = ((mac_prefix[4] << 8) | LORA_NEIGHBOR_1); // Use 2 bytes for LoRa addressing instead of previous 3 bytes
uint16_t LoRa2 = ((mac_prefix[4] << 8) | LORA_NEIGHBOR_2);
uint16_t loraGwAddress = ((selfAddress[4] << 8) | selfAddress[5]); // last 2 bytes of gateway address
uint16_t loraBroadcast = 0xFFFF;
unsigned long receivedLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long ackOkLoRaMsg = 0; // Number of total LoRa packets with valid CRC
extern time_t now;
time_t netTimeOffset = UINT32_MAX; // One direction of LoRa Ping time in units of seconds (1/2 full ping time)
typedef struct DataBuffer
{
DataReading buffer[256];
uint8_t len = 0;
} DataBuffer;
DataBuffer LORA1Buffer;
DataBuffer LORA2Buffer;
DataBuffer LORABBuffer;
enum
{
TxLoRa1,
TxLoRa2,
TxLoRaB,
TxIdle
} TxStatus = TxIdle;
uint8_t tx_buffer_position = 0;
uint32_t tx_start_time;
// Function prototypes
crcResult transmitLoRa(uint16_t *, DataReading *, uint8_t);
crcResult transmitLoRa(uint16_t *, SystemPacket *, uint8_t);
static uint16_t crc16_update(uint16_t, uint8_t);
crcResult handleLoRa();
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void)
{
// check if the interrupt is enabled
if (!enableInterrupt)
{
return;
}
// we sent or received packet, set the flag
operationDone = true;
}
// crc16_update used by both LoRa and filesystem
// CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71
/** @ingroup util_crc16
Processor-independent CRC-16 calculation.
Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)<br>
Initial value: 0xFFFF
This CRC is normally used in disk-drive controllers.
@param uint16_t crc (0x0000..0xFFFF)
@param uint8_t a (0x00..0xFF)
@return calculated CRC (0x0000..0xFFFF)
*/
static uint16_t crc16_update(uint16_t crc, uint8_t a)
{
int i;
crc ^= a;
for (i = 0; i < 8; ++i)
{
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
return crc;
}
crcResult transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len)
{
crcResult crcReturned = CRC_NULL;
uint16_t calcCRC = 0x0000;
uint8_t pkt[6 + (len * sizeof(DataReading))];
pkt[0] = (*destMac >> 8); // high byte of destination MAC
pkt[1] = (*destMac & 0x00FF); // low byte of destination MAC
pkt[2] = selfAddress[4]; // high byte of source MAC (ourselves)
pkt[3] = selfAddress[5]; // low byte of source MAC
memcpy(&pkt[4], packet, len * sizeof(DataReading)); // copy data portion of packet
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{ // Last 2 bytes are CRC so do not include them in the calculation itself
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
//if (*destMac == 0xFFFF)
//{
calcCRC = crc16_update(calcCRC, 0xA1);
//}
pkt[(len * sizeof(DataReading) + 4)] = (calcCRC >> 8); // Append calculated CRC to the last 2 bytes of the packet
pkt[(len * sizeof(DataReading) + 5)] = (calcCRC & 0x00FF);
DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to LoRa MAC 0x" + String(*destMac, HEX));
// printLoraPacket(pkt,sizeof(pkt));
int state = radio.startTransmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG("transmit failed, code " + String(state));
while (true)
;
}
return crcReturned;
}
crcResult transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len)
{
crcResult crcReturned = CRC_NULL;
uint16_t calcCRC = 0x0000;
uint8_t pkt[6 + (len * sizeof(SystemPacket))];
pkt[0] = (*destMac >> 8); // high byte of destination MAC
pkt[1] = (*destMac & 0x00FF); // low byte of destination MAC
pkt[2] = selfAddress[4]; // high byte of source MAC (ourselves)
pkt[3] = selfAddress[5]; // low byte of source MAC
memcpy(&pkt[4], packet, len * sizeof(SystemPacket)); // copy data portion of packet
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{ // Last 2 bytes are CRC so do not include them in the calculation itself
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
calcCRC = crc16_update(calcCRC, 0xA1); // No ACK for SystemPacket messages so generate new CRC with 0xA1
pkt[(len * sizeof(SystemPacket) + 4)] = (calcCRC >> 8); // Append calculated CRC to the last 2 bytes of the packet
pkt[(len * sizeof(SystemPacket) + 5)] = (calcCRC & 0x00FF);
DBG("Transmitting LoRa SYSTEM message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to LoRa MAC 0x" + String(*destMac, HEX));
// printLoraPacket(pkt,sizeof(pkt));
int state = radio.transmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG("transmit failed, code " + String(state));
while (true)
;
}
return crcReturned;
}
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)
printf("\n%02d: ", i);
printf("%02X ", p[i]);
}
printf("\n");
}
void begin_lora()
{
#ifdef CUSTOM_SPI
#ifdef ESP32
SPI.begin(LORA_SPI_SCK, LORA_SPI_MISO, LORA_SPI_MOSI);
#endif // ESP32
#ifdef ARDUINO_ARCH_RP2040
SPI1.setRX(LORA_SPI_MISO);
SPI1.setTX(LORA_SPI_MOSI);
SPI1.setSCK(LORA_SPI_SCK);
SPI1.begin(false);
#endif //ARDUINO_ARCH_RP2040
#endif // CUSTOM_SPI
#ifdef USE_SX126X
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR);
#else
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR, 8, 0);
#endif
if (state == RADIOLIB_ERR_NONE)
{
DBG("RadioLib initialization successful!");
}
else
{
DBG("RadioLib initialization failed, code " + String(state));
while (true)
;
}
#ifdef USE_SX126X
radio.setDio1Action(setFlag);
#else
radio.setDio0Action(setFlag, RISING);
#endif
radio.setCRC(false);
DBG("LoRa Initialized. Frequency: " + String(FDRS_LORA_FREQUENCY) + " Bandwidth: " + String(FDRS_LORA_BANDWIDTH) + " SF: " + String(FDRS_LORA_SF) + " CR: " + String(FDRS_LORA_CR) + " SyncWord: " + String(FDRS_LORA_SYNCWORD) + " Tx Power: " + String(FDRS_LORA_TXPWR) + "dBm");
state = radio.startReceive(); // start listening for LoRa packets
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG("start receive failed, code " + String(state));
while (true)
;
}
}
crcResult getLoRa()
{
int packetSize = radio.getPacketLength();
if ((((packetSize - 6) % sizeof(DataReading) == 0) || ((packetSize - 6) % sizeof(SystemPacket) == 0)) && packetSize > 0)
{ // packet size should be 6 bytes plus multiple of size of DataReading
uint8_t packet[packetSize];
uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet
uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet
uint16_t sourceMAC = 0x0000;
uint16_t destMAC = 0x0000;
radio.readData((uint8_t *)&packet, packetSize);
destMAC = (packet[0] << 8) | packet[1];
sourceMAC = (packet[2] << 8) | packet[3];
packetCRC = ((packet[packetSize - 2] << 8) | packet[packetSize - 1]);
// DBG("Packet Address: 0x" + String(packet[0], HEX) + String(packet[1], HEX) + " Self Address: 0x" + String(selfAddress[4], HEX) + String(selfAddress[5], HEX));
if (destMAC == (selfAddress[4] << 8 | selfAddress[5]))
{ // Check if addressed to this device (2 bytes, bytes 1 and 2)
// printLoraPacket(packet,sizeof(packet));
if (receivedLoRaMsg != 0)
{ // Avoid divide by 0
DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg) + ", CRC Ok Pct " + String((float)ackOkLoRaMsg / receivedLoRaMsg * 100) + "%");
}
else
{
DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg));
}
receivedLoRaMsg++;
// Evaluate CRC
for (int i = 0; i < (packetSize - 2); i++)
{ // Last 2 bytes of packet are the CRC so do not include them in calculation
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, packet[i]);
}
if ((packetSize - 6) % sizeof(DataReading) == 0)
{ // DataReading type packet
if (calcCRC == packetCRC)
{
SystemPacket ACK = {.cmd = cmd_ack, .param = CRC_OK};
DBG("CRC Match, sending ACK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)");
transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source
}
else if (packetCRC == crc16_update(calcCRC, 0xA1))
{ // Sender does not want ACK and CRC is valid
DBG("Address 0x" + String(sourceMAC, 16) + " does not want ACK");
}
else
{
SystemPacket NAK = {.cmd = cmd_ack, .param = CRC_BAD};
// Send NAK packet to sensor
DBG("CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX) + " Sending NAK packet to sensor 0x" + String(sourceMAC, HEX) + "(hex)");
transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source
newData = event_clear; // do not process data as data may be corrupt
return CRC_BAD; // Exit function and do not update newData to send invalid data further on
}
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);
ackOkLoRaMsg++;
if (memcmp(&sourceMAC, &LoRa1, 2) == 0)
{ // Check if it is from a registered sender
newData = event_lora1;
return CRC_OK;
}
if (memcmp(&sourceMAC, &LoRa2, 2) == 0)
{
newData = event_lora2;
return CRC_OK;
}
newData = event_lorag;
return CRC_OK;
}
else if ((packetSize - 6) % sizeof(SystemPacket) == 0)
{
unsigned int ln = (packetSize - 6) / sizeof(SystemPacket);
SystemPacket receiveData[ln];
// SystemPacket data type do not require ACKs so we don't care which of the two CRC calculations are used
if (packetCRC == calcCRC || packetCRC == crc16_update(calcCRC, 0xA1))
{
memcpy(receiveData, &packet[4], packetSize - 6); // Split off data portion of packet (N bytes)
if (ln == 1 && receiveData[0].cmd == cmd_ack)
{
DBG("ACK Received - CRC Match");
}
else if (ln == 1 && receiveData[0].cmd == cmd_ping)
{ // We have received a ping request or reply??
if (receiveData[0].param == 1)
{ // This is a reply to our ping request
loraPing.status = psCompleted;
DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX));
}
else if (receiveData[0].param == 0)
{
DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying.");
SystemPacket pingReply = {.cmd = cmd_ping, .param = 1};
transmitLoRa(&sourceMAC, &pingReply, 1);
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_time) {
if(timeSource.tmNetIf <= TMIF_LORA) {
DBG("Time rcv from LoRa 0x" + String(sourceMAC, HEX));
if(timeSource.tmNetIf == TMIF_NONE) {
timeSource.tmNetIf = TMIF_LORA;
timeSource.tmAddress = sourceMAC;
timeSource.tmSource = TMS_NET;
DBG("Time source is LoRa 0x" + String(sourceMAC, HEX));
}
if(timeSource.tmAddress == sourceMAC) {
if(setTime(receiveData[0].param)) {
timeSource.tmLastTimeSet = millis();
}
}
else {
DBG("LoRa 0x" + String(sourceMAC, HEX) + " is not time master, discarding request");
}
}
else {
// higher quality source is time master
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_time_req) {
theCmd.cmd = receiveData[0].cmd;
theCmd.param = receiveData[0].param;
// Data processing to be handled by handleCommands() in gateway.h/node.h
}
else
{ // data we have received is not yet programmed. How we handle is future enhancement.
DBG1("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement.");
DBG1("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd));
}
ackOkLoRaMsg++;
return CRC_OK;
}
else
{
DBG1("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX));
return CRC_BAD;
}
}
}
else
{
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));
return CRC_NULL;
}
}
else
{
if (packetSize != 0)
{
DBG2("Incoming LoRa packet of " + String(packetSize) + " bytes not processed.");
// uint8_t packet[packetSize];
// radio.readData((uint8_t *)&packet, packetSize);
// printLoraPacket(packet,sizeof(packet));
return CRC_NULL;
}
}
return CRC_NULL;
}
// Sends packet to any node that is paired to this gateway
void broadcastLoRa()
{
DBG("Sending to LoRa broadcast buffer");
for (int i = 0; i < ln; i++)
{
LORABBuffer.buffer[LORABBuffer.len + i] = theData[i];
}
LORABBuffer.len += ln;
}
// Sends packet to neighbor gateways
void sendLoRaNbr(uint8_t interface)
{
DBG("Sending to LoRa neighbor buffer");
switch (interface)
{
case 1:
{
for (int i = 0; i < ln; i++)
{
LORA1Buffer.buffer[LORA1Buffer.len + i] = theData[i];
}
LORA1Buffer.len += ln;
break;
}
case 2:
{
for (int i = 0; i < ln; i++)
{
LORA2Buffer.buffer[LORA2Buffer.len + i] = theData[i];
}
LORA2Buffer.len += ln;
break;
}
}
}
void asyncReleaseLoRa(bool first_run)
{
delay(3);
if (first_run)
{
if (LORA1Buffer.len > 0) {
TxStatus = TxLoRa1;
} else if (LORA2Buffer.len > 0) {
TxStatus = TxLoRa2;
} else if (LORABBuffer.len > 0) {
TxStatus = TxLoRaB;
} else {
goto TxFin;
}
tx_start_time = millis();
}
switch (TxStatus)
{
case TxLoRa1:
if (LORA1Buffer.len - tx_buffer_position > lora_size) {
transmitLoRa(&LoRa1, &LORA1Buffer.buffer[tx_buffer_position], lora_size);
tx_buffer_position += lora_size;
} else {
transmitLoRa(&LoRa1, &LORA1Buffer.buffer[tx_buffer_position], LORA1Buffer.len - tx_buffer_position);
tx_buffer_position = 0;
if (LORA2Buffer.len > 0) {
TxStatus = TxLoRa2;
} else if ((LORABBuffer.len > 0)) {
TxStatus = TxLoRaB;
} else {
goto TxFin;
}
}
break;
case TxLoRa2:
if (LORA2Buffer.len - tx_buffer_position > lora_size) {
transmitLoRa(&LoRa2, &LORA2Buffer.buffer[tx_buffer_position], lora_size);
tx_buffer_position += lora_size;
} else {
transmitLoRa(&LoRa2, &LORA2Buffer.buffer[tx_buffer_position], LORA2Buffer.len - tx_buffer_position);
tx_buffer_position = 0;
if (LORABBuffer.len > 0) {
TxStatus = TxLoRaB;
} else {
goto TxFin;
}
}
break;
case TxLoRaB:
if (LORABBuffer.len - tx_buffer_position > lora_size) {
transmitLoRa(&loraBroadcast, &LORABBuffer.buffer[tx_buffer_position], lora_size);
tx_buffer_position += lora_size;
} else {
transmitLoRa(&loraBroadcast, &LORABBuffer.buffer[tx_buffer_position], LORABBuffer.len - tx_buffer_position);
TxFin:
if (LORABBuffer.len + LORA1Buffer.len + LORA2Buffer.len > 0) {
LORABBuffer.len = 0;
LORA1Buffer.len = 0;
LORA2Buffer.len = 0;
tx_buffer_position = 0;
TxStatus = TxIdle;
}
}
break;
}
}
void asyncReleaseLoRaFirst()
{
asyncReleaseLoRa(true);
}
// FDRS Sensor pings address and listens for a defined amount of time for a reply
// Asynchronous - this initiates the ping and handleLoRa is the callback.
void pingFDRSLoRa(uint16_t address, uint timeout)
{
if(loraPing.status == psNotStarted) {
SystemPacket sys_packet = {.cmd = cmd_ping, .param = 0};
DBG1("LoRa ping sent to address: 0x" + String(address, HEX));
loraPing.timeout = timeout;
loraPing.status = psWaiting;
loraPing.address = address;
transmitLoRa(&address, &sys_packet, 1);
loraPing.start = millis();
}
return;
}
// Pings the LoRa time master periodically to calculate the time delay in the LoRa radio link
// Returns success or failure of the ping result
void pingLoRaTimeMaster() {
static unsigned long lastTimeMasterPing = 0;
// ping the time master every 10 minutes
if(TDIFFMIN(lastTimeMasterPing,10)) {
pingFDRSLoRa(timeSource.tmAddress,4000);
lastTimeMasterPing = millis();
}
return;
}
crcResult handleLoRa()
{
crcResult crcReturned = CRC_NULL;
if (operationDone) // the interrupt was triggered
{
// DBG("Interrupt triggered");
// DBG("TxFlag: " + String(transmitFlag));
// DBG("TxStatus: " + String(TxStatus));
enableInterrupt = false;
operationDone = false;
if (transmitFlag) // the previous operation was transmission
{
radio.finishTransmit();
if (TxStatus != TxIdle)
{
asyncReleaseLoRa(false);
enableInterrupt = true;
}
else
{
DBG("LoRa airtime: " + String(millis() - tx_start_time) + "ms");
radio.startReceive(); // return to listen mode
enableInterrupt = true;
transmitFlag = false;
}
}
else // the previous operation was reception
{
crcReturned = getLoRa();
if (!transmitFlag) // return to listen if no transmission was begun
{
radio.startReceive();
}
enableInterrupt = true;
}
}
if(loraPing.status == psCompleted) {
loraPing.response = millis() - loraPing.start;
DBG("LoRa Ping Returned: " + String(loraPing.response) + "ms.");
if(loraPing.address == timeSource.tmAddress) {
netTimeOffset = loraPing.response/2/1000;
adjTimeforNetDelay(netTimeOffset);
}
loraPing.status = psNotStarted;
loraPing.start = 0;
loraPing.timeout = 0;
loraPing.address = 0;
loraPing.response = __UINT32_MAX__;
}
if(loraPing.status == psWaiting && (TDIFF(loraPing.start,loraPing.timeout))) {
DBG1("No LoRa ping returned within " + String(loraPing.timeout) + "ms.");
loraPing.status = psNotStarted;
loraPing.start = 0;
loraPing.timeout = 0;
loraPing.address = 0;
loraPing.response = __UINT32_MAX__;
}
// Ping LoRa time master to estimate time delay in radio link
if(timeSource.tmNetIf == TMIF_LORA && netTimeOffset == UINT32_MAX) {
pingLoRaTimeMaster();
}
return crcReturned;
}
// Send time to LoRa broadcast and peers
crcResult sendTimeLoRa() {
crcResult result1 = CRC_NULL, result2 = CRC_NULL, result3 = CRC_NULL;
DBG("Sending time via LoRa");
SystemPacket spTimeLoRa = {.cmd = cmd_time, .param = now};
DBG("Sending time to LoRa broadcast");
result1 = transmitLoRa(&loraBroadcast, &spTimeLoRa, 1);
// Do not send to LoRa peers if their address is 0x..00
if(((LoRa1 & 0x00FF) != 0x0000) && (LoRa1 != timeSource.tmAddress)) {
DBG("Sending time to LoRa Neighbor 1");
spTimeLoRa.param = now;
// add LoRa neighbor 1
result2 = transmitLoRa(&LoRa1, &spTimeLoRa, 1);
}
if(((LoRa2 & 0x00FF) != 0x0000) && (LoRa2 != timeSource.tmAddress)) {
DBG("Sending time to LoRa Neighbor 2");
spTimeLoRa.param = now;
// add LoRa neighbor 2
result3 = transmitLoRa(&LoRa2, &spTimeLoRa, 1);
}
if(result1 != CRC_OK || result2 != CRC_OK || result3 != CRC_OK){
return CRC_BAD;
}
else {
return CRC_OK;
}
}
// Send time to LoRa node at specific address
crcResult sendTimeLoRa(uint16_t addr) {
crcResult result = CRC_NULL;
SystemPacket spTimeLoRa = {.cmd = cmd_time, .param = now};
DBG1("Sending time to LoRa address 0x" + String(addr,HEX));
result = transmitLoRa(&addr, &spTimeLoRa, 1);
return result;
}

@ -0,0 +1,853 @@
#include <RadioLib.h>
// 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_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 TXWINDOWMS 300
#define SPBUFFSIZE 10
#define DRBUFFSIZE lora_size
#define DRQUEUEEMPTY ((startIdxDR == endIdxDR) ? true: false)
#define SPQUEUEEMPTY ((startIdxSP == endIdxSP) ? true: false)
#define DRQUEUEFULL (((startIdxDR + 1) % DRBUFFSIZE) == endIdxDR ? true: false)
#define SPQUEUEFULL (((startIdxSP + 1) % SPBUFFSIZE) == endIdxSP ? true: false)
// select LoRa band configuration
#if defined(LORA_FREQUENCY)
#define FDRS_LORA_FREQUENCY LORA_FREQUENCY
#else
#define FDRS_LORA_FREQUENCY GLOBAL_LORA_FREQUENCY
#endif // LORA_FREQUENCY
// select LoRa SF configuration
#if defined(LORA_SF)
#define FDRS_LORA_SF LORA_SF
#else
#define FDRS_LORA_SF GLOBAL_LORA_SF
#endif // LORA_SF
// select LoRa ACK configuration
#if defined(LORA_ACK) || defined(GLOBAL_LORA_ACK)
#define FDRS_LORA_ACK
#endif // LORA_ACK
// select LoRa ACK Timeout configuration
#if defined(LORA_ACK_TIMEOUT)
#define FDRS_ACK_TIMEOUT LORA_ACK_TIMEOUT
#else
#define FDRS_ACK_TIMEOUT GLOBAL_ACK_TIMEOUT
#endif // LORA_ACK_TIMEOUT
// select LoRa Retry configuration
#if defined(LORA_RETRIES)
#define FDRS_LORA_RETRIES LORA_RETRIES
#else
#define FDRS_LORA_RETRIES GLOBAL_LORA_RETRIES
#endif // LORA_RETRIES
// select LoRa Tx Power configuration
#if defined(LORA_TXPWR)
#define FDRS_LORA_TXPWR LORA_TXPWR
#else
#define FDRS_LORA_TXPWR GLOBAL_LORA_TXPWR
#endif // LORA_TXPWR
// select LoRa BANDWIDTH configuration
#if defined(LORA_BANDWIDTH)
#define FDRS_LORA_BANDWIDTH LORA_BANDWIDTH
#else
#define FDRS_LORA_BANDWIDTH GLOBAL_LORA_BANDWIDTH
#endif // LORA_BANDWIDTH
// select LoRa Coding Rate configuration
#if defined(LORA_CR)
#define FDRS_LORA_CR LORA_CR
#else
#define FDRS_LORA_CR GLOBAL_LORA_CR
#endif // LORA_CR
// select LoRa SyncWord configuration
#if defined(LORA_SYNCWORD)
#define FDRS_LORA_SYNCWORD LORA_SYNCWORD
#else
#define FDRS_LORA_SYNCWORD GLOBAL_LORA_SYNCWORD
#endif // LORA_SYNCWORD
// select LoRa Release Interval configuration
#if defined(LORA_INTERVAL)
#define FDRS_LORA_INTERVAL LORA_INTERVAL
#else
#define FDRS_LORA_INTERVAL GLOBAL_LORA_INTERVAL
#endif // LORA_INTERVAL
#ifndef LORA_BUSY
#define LORA_BUSY RADIOLIB_NC
#endif
#ifdef CUSTOM_SPI
#ifdef ARDUINO_ARCH_RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI1);
#endif // RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI);
#else
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY);
#endif // CUSTOM_SPI
#ifdef LORA_ACK
bool ack = true;
#else
bool ack = false;
#endif // LORA_ACK
LoRaPing loraPing;
const uint8_t lora_size = 250 / sizeof(DataReading);
tskLoRaTaskSP loraSPBuffTx[SPBUFFSIZE];
tskLoRaTaskDR loraDRBuffTx[DRBUFFSIZE];
int loraTxState = stReady;
int loraAckState = stReady;
int startIdxDR = 0;
int endIdxDR = 0;
int startIdxSP = 0;
int endIdxSP = 0;
#ifdef FDRS_GATEWAY
#ifndef USE_ESPNOW // mac_prefix used for both ESP-NOW and LoRa - avoid redefinition warnings
const uint8_t mac_prefix[] = {MAC_PREFIX};
const uint8_t selfAddress[] = {MAC_PREFIX, UNIT_MAC};
#endif
uint16_t LoRa1 = ((mac_prefix[4] << 8) | LORA_NEIGHBOR_1); // Use 2 bytes for LoRa addressing instead of previous 3 bytes
uint16_t LoRa2 = ((mac_prefix[4] << 8) | LORA_NEIGHBOR_2);
uint16_t loraBroadcast = 0xFFFF;
uint16_t gtwyAddress = ((mac_prefix[4] << 8) | UNIT_MAC); // for a gateway this is our own address
#elif defined(FDRS_NODE)
uint8_t selfAddress[6] = {0};
uint16_t LoRa1 = 0;
uint16_t LoRa2 = 0;
uint16_t loraBroadcast = 0;
const uint8_t mac_prefix[] = {MAC_PREFIX};
uint16_t gtwyAddress = ((mac_prefix[4] << 8) | GTWY_MAC); // for a node, this is our gateway
#endif // FDRS_GATEWAY
volatile bool transmitFlag = false; // flag to indicate transmission or reception state
volatile bool enableInterrupt = true; // disable interrupt when it's not needed
volatile bool operationDone = false; // flag to indicate that a packet was sent or received
unsigned long receivedLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long ackOkLoRaMsg = 0; // Number of total LoRa packets with valid CRC
extern time_t now;
unsigned long transmitLoRaMsgwAck = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long msgOkLoRa = 0; // Number of total LoRa packets with valid CRC
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;
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void)
{
// check if the interrupt is enabled
if (!enableInterrupt)
{
return;
}
operationDone = true; // we sent or received packet, set the flag
}
// crc16_update used by both LoRa and filesystem
// CRC16 from https://github.com/4-20ma/ModbusMaster/blob/3a05ff87677a9bdd8e027d6906dc05ca15ca8ade/src/util/crc16.h#L71
/** @ingroup util_crc16
Processor-independent CRC-16 calculation.
Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)<br>
Initial value: 0xFFFF
This CRC is normally used in disk-drive controllers.
@param uint16_t crc (0x0000..0xFFFF)
@param uint8_t a (0x00..0xFF)
@return calculated CRC (0x0000..0xFFFF)
*/
static uint16_t crc16_update(uint16_t crc, uint8_t a)
{
int i;
crc ^= a;
for (i = 0; i < 8; ++i)
{
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
return crc;
}
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)
printf("\n%02d: ", i);
printf("%02X ", p[i]);
}
printf("\n");
}
// Do not call this function directly, instead call transmitLoRaAsync(...)
// Transmits Lora data by calling RadioLib library function
// Returns void becuase function is Async and does not receive any data
void transmitLoRa(uint16_t *destMac, DataReading *packet, uint8_t len)
{
uint8_t pkt[6 + (len * sizeof(DataReading))];
uint16_t calcCRC = 0x0000;
loraTxState = stInProcess;
// Building packet -- address portion - first 4 bytes
pkt[0] = (*destMac >> 8); // high byte of destination MAC
pkt[1] = (*destMac & 0x00FF); // low byte of destination MAC
pkt[2] = selfAddress[4]; // high byte of source MAC (ourselves)
pkt[3] = selfAddress[5]; // low byte of source MAC
// Building packet -- data portion - 7 bytes
memcpy(&pkt[4], packet, len * sizeof(DataReading)); // copy data portion of packet
// Calculate CRC of address and data portion of the packet
// Last 2 bytes are CRC so do not include them in the calculation itself
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
if(!ack) {
calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK
}
pkt[len * sizeof(DataReading) + 4] = (calcCRC >> 8); // Append calculated CRC to the last 2 bytes of the packet
pkt[len * sizeof(DataReading) + 5] = (calcCRC & 0x00FF);
DBG("Sending LoRa DR");
DBG1("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to destination 0x" + String(*destMac, HEX));
// printLoraPacket(pkt,sizeof(pkt));
tx_start_time = millis();
int state = radio.startTransmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state != RADIOLIB_ERR_NONE)
{
DBG("Xmit failed, code " + String(state));
while (true)
;
}
return;
}
// For now SystemPackets will not use ACK but will calculate CRC
// Returns CRC_NULL as SystemPackets do not use ACKS at current time
void transmitLoRa(uint16_t *destMac, SystemPacket *packet, uint8_t len)
{
uint8_t pkt[6 + (len * sizeof(SystemPacket))];
uint16_t calcCRC = 0x0000;
loraTxState = stInProcess;
// Building packet -- address portion - first 4 bytes
pkt[0] = (*destMac >> 8); // high byte of destination MAC
pkt[1] = (*destMac & 0x00FF); // low byte of destination MAC
pkt[2] = selfAddress[4]; // high byte of source MAC (ourselves)
pkt[3] = selfAddress[5]; // low byte of source MAC
// Building packet -- data portion - 5 bytes
memcpy(&pkt[4], packet, len * sizeof(SystemPacket)); // copy data portion of packet
// Calculate CRC of address and data portion of the packet
// Last 2 bytes are CRC so do not include them in the calculation itself
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK
// Building packet -- adding CRC - last 2 bytes
pkt[len * sizeof(SystemPacket) + 4] = (calcCRC >> 8);
pkt[len * sizeof(SystemPacket) + 5] = (calcCRC & 0x00FF);
// Packet is constructed now transmit the packet
DBG1("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to destination 0x" + String(*destMac, HEX));
// printLoraPacket(pkt,sizeof(pkt));
tx_start_time = millis();
int state = radio.startTransmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state != RADIOLIB_ERR_NONE)
{
DBG("Xmit failed, code " + String(state));
while (true)
;
}
return;
}
void begin_lora()
{
#ifdef CUSTOM_SPI
#ifdef ESP32
SPI.begin(LORA_SPI_SCK, LORA_SPI_MISO, LORA_SPI_MOSI);
#endif // ESP32
#ifdef ARDUINO_ARCH_RP2040
SPI1.setRX(LORA_SPI_MISO);
SPI1.setTX(LORA_SPI_MOSI);
SPI1.setSCK(LORA_SPI_SCK);
SPI1.begin(false);
#endif //ARDUINO_ARCH_RP2040
#endif // CUSTOM_SPI
#ifdef USE_SX126X
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR, 8, 1.6, false);
#else
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR, 8, 0);
#endif
if (state == RADIOLIB_ERR_NONE)
{
DBG("RadioLib initialization successful!");
}
else
{
DBG("RadioLib initialization failed, code " + String(state));
while (true)
;
}
#ifdef USE_SX126X
radio.setDio1Action(setFlag);
#else
radio.setDio0Action(setFlag, RISING);
#endif
radio.setCRC(false);
DBG("LoRa Initialized. Frequency: " + String(FDRS_LORA_FREQUENCY) + " Bandwidth: " + String(FDRS_LORA_BANDWIDTH) + " SF: " + String(FDRS_LORA_SF) + " CR: " + String(FDRS_LORA_CR) + " SyncWord: " + String(FDRS_LORA_SYNCWORD) + " Tx Power: " + String(FDRS_LORA_TXPWR) + "dBm");
#ifdef FDRS_NODE
selfAddress[4] = radio.randomByte();
selfAddress[5] = radio.randomByte();
DBG("LoRa node address is 0x" + String(selfAddress[4], HEX) + String(selfAddress[5], HEX));
#endif
state = radio.startReceive(); // start listening for LoRa packets
if (state != RADIOLIB_ERR_NONE)
{
DBG(" failed, code " + String(state));
while (true)
;
}
}
bool transmitLoRaAsync(uint16_t *destMAC, SystemPacket *packet, uint8_t len)
{
for(int i=0; i < len; i++)
{
//check for full buffer
if(SPQUEUEFULL) {
DBG("Lora SP Buffer Overflow!");
return false;
}
//add packet to buffer
loraSPBuffTx[startIdxSP].data = *(packet + i);
loraSPBuffTx[startIdxSP].dstAddress = *destMAC;
startIdxSP = (startIdxSP + 1) % SPBUFFSIZE;
}
DBG2("SP added to LoRa buffer. start: " + String(startIdxSP) + " end: " + String(endIdxSP));
return true;
}
// Wrapper for transmitLoRa for DataReading type packets to handle processing Receiving CRCs and retransmitting packets
bool transmitLoRaAsync(uint16_t *destMAC, DataReading *packet, uint8_t len)
{
// we need to prevent modifying index while waiting
// for an ack
if(loraAckState == stReady) {
//add packet to buffer
for(int i=0; i < len; i++)
{
if(DRQUEUEFULL)
{
DBG("Lora DR Buffer Overflow!");
return false;
}
loraDRBuffTx[startIdxDR].data = *(packet + i);
loraDRBuffTx[startIdxDR].dstAddress = *destMAC;
startIdxDR = (startIdxDR + 1) % DRBUFFSIZE;
}
DBG2("DR added to LoRa buffer. start: " + String(startIdxDR) + " end: " + String(endIdxDR));
return true;
}
else {
// JL - do we internally store the start index, check for space, and then
// adjust the index once the acks are completed???
DBG2("Unable to add DR to LoRa buffer due to pending ACK.");
return false;
}
}
// Send time to LoRa broadcast and peers
// Only used in gateways
void sendTimeLoRa() {
// Check for node or gateway. Do not send if we are a node.
if(selfAddress[0] != 0) {
DBG1("Sending time via LoRa");
SystemPacket spTimeLoRa = {.cmd = cmd_time, .param = now};
DBG1("Sending time to LoRa broadcast");
transmitLoRaAsync(&loraBroadcast, &spTimeLoRa, 1);
// Do not send to LoRa peers if their address is 0x..00
if(((LoRa1 & 0x00FF) != 0x0000) && (LoRa1 != timeSource.tmAddress)) {
DBG1("Sending time to LoRa Neighbor 1");
spTimeLoRa.param = now;
// add LoRa neighbor 1
transmitLoRaAsync(&LoRa1, &spTimeLoRa, 1);
}
if(((LoRa2 & 0x00FF) != 0x0000) && (LoRa2 != timeSource.tmAddress)) {
DBG1("Sending time to LoRa Neighbor 2");
spTimeLoRa.param = now;
// add LoRa neighbor 2
transmitLoRaAsync(&LoRa2, &spTimeLoRa, 1);
}
}
return;
}
// Send time to LoRa node at specific address
void sendTimeLoRa(uint16_t addr) {
SystemPacket spTimeLoRa = {.cmd = cmd_time, .param = now};
DBG1("Sending time to LoRa address 0x" + String(addr,HEX));
transmitLoRaAsync(&addr, &spTimeLoRa, 1);
return;
}
// FDRS Sensor pings address and listens for a defined amount of time for a reply
bool pingRequestLoRa(uint16_t address, uint32_t timeout)
{
if(loraPing.status == stReady) {
SystemPacket sys_packet = {.cmd = cmd_ping, .param = ping_request};
loraPing.timeout = timeout;
loraPing.status = stInProcess;
loraPing.address = address;
loraPing.start = millis();
if(loraTxState == stReady) {
transmitLoRa(&address,&sys_packet,1);
DBG1("LoRa ping request sent to address: 0x" + String(address, HEX));
return true;
}
else {
if(transmitLoRaAsync(&address, &sys_packet, 1))
{
DBG1("LoRa ping request queued to address: 0x" + String(address, HEX));
return true;
}
else {
DBG1("Error sending LoRa ping.");
}
}
}
return false;
}
// FDRS sends ping reply
bool pingReplyLoRa(uint16_t address)
{
SystemPacket sys_packet = {.cmd = cmd_ping, .param = ping_reply};
if(loraTxState == stReady) {
transmitLoRa(&address,&sys_packet,1);
DBG1("LoRa ping reply sent to address: 0x" + String(address, HEX));
return true;
}
else {
if(transmitLoRaAsync(&address, &sys_packet, 1))
{
DBG1("LoRa ping reply queued to address: 0x" + String(address, HEX));
return true;
}
else {
DBG1("Error sending LoRa ping.");
}
}
return false;
}
// ****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();
if ((((packetSize - 6) % sizeof(DataReading) == 0) || ((packetSize - 6) % sizeof(SystemPacket) == 0)) && packetSize > 0)
{ // packet size should be 6 bytes plus multiple of size of DataReading
uint8_t packet[packetSize];
uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet
uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet
uint16_t sourceMAC = 0x0000;
uint16_t destMAC = 0x0000;
radio.readData((uint8_t *)&packet, packetSize);
destMAC = (packet[0] << 8) | packet[1];
sourceMAC = (packet[2] << 8) | packet[3];
packetCRC = ((packet[packetSize - 2] << 8) | packet[packetSize - 1]);
// Print all packets
// DBG2("Source Address: 0x" + String(packet[2], HEX) + String(packet[3], HEX) + " Destination Address: 0x" + String(packet[0], HEX) + String(packet[1], HEX));
#ifdef FDRS_GATEWAY
// for gateway - only listen to our own address
if (destMAC == (selfAddress[4] << 8 | selfAddress[5]))
#elif defined(FDRS_NODE)
// 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));
if (receivedLoRaMsg != 0)
{ // Avoid divide by 0
DBG1("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg) + ", CRC Ok Pct " + String((float)ackOkLoRaMsg / receivedLoRaMsg * 100) + "%");
}
else
{
DBG1("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg));
}
receivedLoRaMsg++;
// Evaluate CRC
for (int i = 0; i < (packetSize - 2); i++)
{ // Last 2 bytes of packet are the CRC so do not include them in calculation
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, packet[i]);
}
if ((packetSize - 6) % sizeof(DataReading) == 0)
{ // DataReading type packet
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)");
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");
}
else
{ // 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
newData = event_clear; // do not process data as data may be corrupt
return CRC_BAD; // Exit function and do not update newData to send invalid data further on
}
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);
ackOkLoRaMsg++;
if (memcmp(&sourceMAC, &LoRa1, 2) == 0)
{ // Check if it is from a registered sender
newData = event_lora1;
return CRC_OK;
}
if (memcmp(&sourceMAC, &LoRa2, 2) == 0)
{
newData = event_lora2;
return CRC_OK;
}
newData = event_lorag;
return CRC_OK;
}
else if ((packetSize - 6) == sizeof(SystemPacket))
{
unsigned int ln = (packetSize - 6) / sizeof(SystemPacket);
SystemPacket receiveData[ln];
if ((packetCRC == calcCRC) || (packetCRC == crc16_update(calcCRC, 0xA1)))
{
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;
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_ping)
{ // We have received a ping request or reply??
if (receiveData[0].param == ping_reply)
{ // This is a reply to our ping request
loraPing.status = stCompleted;
DBG1("We have received a ping reply via LoRa from address 0x" + String(sourceMAC, HEX));
}
else if (receiveData[0].param == ping_request)
{
DBG1("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying.");
pingReplyLoRa(sourceMAC);
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_time && receiveData[0].param > MIN_TS) { // Time received
if(timeSource.tmNetIf <= TMIF_LORA) {
DBG1("Time rcv from LoRa 0x" + String(sourceMAC, HEX));
if(timeSource.tmNetIf == TMIF_NONE) {
timeSource.tmNetIf = TMIF_LORA;
timeSource.tmAddress = sourceMAC;
timeSource.tmSource = TMS_NET;
DBG1("Time source is LoRa 0x" + String(sourceMAC, HEX));
}
if(timeSource.tmAddress == sourceMAC) {
if(setTime(receiveData[0].param)) {
timeSource.tmLastTimeSet = millis();
}
}
else {
DBG2("LoRa 0x" + String(sourceMAC, HEX) + " is not time source, discarding request");
}
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_time && receiveData[0].param == 0) { // Time requested
DBG1("Received LoRa time request from 0x" + String(sourceMAC,HEX));
sendTimeLoRa(sourceMAC);
}
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));
}
ackOkLoRaMsg++;
return CRC_OK;
}
else
{
DBG2("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX));
if(loraAckState == stInProcess) {
loraAckState = stCrcMismatch;
}
return CRC_BAD;
}
}
}
else
{
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));
return CRC_NULL;
}
}
else
{
if (packetSize != 0)
{
DBG2("Incoming LoRa packet of " + String(packetSize) + " bytes not processed.");
// uint8_t packet[packetSize];
// radio.readData((uint8_t *)&packet, packetSize);
// printLoraPacket(packet,sizeof(packet));
return CRC_NULL;
}
}
return CRC_NULL;
}
// Sends packet to any node that is paired to this gateway
void broadcastLoRa()
{
DBG("Sending to LoRa broadcast buffer");
transmitLoRaAsync(&loraBroadcast,theData,ln);
}
// Sends packet to neighbor gateways
void sendLoRaNbr(uint8_t interface)
{
DBG("Sending to LoRa neighbor buffer");
switch (interface)
{
case 1:
{
transmitLoRaAsync(&LoRa1,theData,ln);
break;
}
case 2:
{
transmitLoRaAsync(&LoRa2,theData,ln);
break;
}
}
}
crcResult LoRaTxRxOperation()
{
crcResult crcReturned = CRC_NULL;
if (operationDone)
{ // the interrupt was triggered
// DBG("Interrupt triggered");
// DBG("TxFlag: " + String(transmitFlag));
// DBG("TxStatus: " + String(TxStatus));
enableInterrupt = false;
operationDone = false;
if (transmitFlag) // the previous operation was transmission
{
radio.finishTransmit();
loraTxState = stCompleted;
DBG1("LoRa airtime: " + String(millis() - tx_start_time) + "ms");
radio.startReceive(); // return to listen mode
transmitFlag = false;
// Serial.println("TxINT!");
}
else
{ // the previous operation was reception
crcReturned = receiveLoRa();
if (!transmitFlag) // return to listen if no transmission was begun
{
radio.startReceive();
}
// Serial.println("RxINT!");
}
delay(10);
enableInterrupt = true;
}
return crcReturned;
}
void handleLoRa()
{
LoRaTxRxOperation();
if(loraPing.status == stCompleted) {
loraPing.response = millis() - loraPing.start;
DBG1("LoRa Ping Returned: " + String(loraPing.response) + "ms.");
if(loraPing.address == timeSource.tmAddress) {
netTimeOffset = loraPing.response/2/1000;
adjTimeforNetDelay(netTimeOffset);
}
loraPing.status = stReady;
loraPing.start = 0;
loraPing.timeout = 0;
loraPing.address = 0;
loraPing.response = UINT32_MAX;
}
if(loraPing.status == stInProcess && (TDIFF(loraPing.start,loraPing.timeout))) {
DBG1("No LoRa ping returned within " + String(loraPing.timeout) + "ms.");
loraPing.status = stReady;
loraPing.start = 0;
loraPing.timeout = 0;
loraPing.address = 0;
loraPing.response = UINT32_MAX;
}
static int retries = FDRS_LORA_RETRIES;
static unsigned long loraAckTimeout = 0;
// Process any DR ACKs in progress
if(loraTxState == stReady && loraAckState != stReady) {
if (loraAckState == stCrcMatch)
{
DBG1("LoRa ACK Received! CRC OK");
msgOkLoRa++;
loraAckState = stReady;
// reset buffer indexes so that buffer is empty
endIdxDR = startIdxDR;
retries = FDRS_LORA_RETRIES;
if(loraTxState == stCompleted) {
loraTxState = stReady;
}
return; // we're done
}
else if(retries == 0) {
DBG2("Retries Exhausted. Data is lost.");
loraAckState = stReady;
// reset buffer indexes so that buffer is empty
endIdxDR = startIdxDR;
retries = FDRS_LORA_RETRIES;
if(loraTxState == stCompleted) {
loraTxState = stReady;
}
return;
}
else if (loraAckState == stCrcMismatch)
{
DBG1("LoRa ACK Received! CRC BAD");
// Resend original packet again if retries are available
DBG2("Retries: " + String(retries) + " DR Index: " + String(endIdxDR) + " Address: 0x" + String(loraDRBuffTx[endIdxDR].dstAddress,HEX));
loraAckTimeout = millis();
loraAckState = stInProcess;
retries--;
transmitLoRa(&loraDRBuffTx[endIdxDR].dstAddress, &loraDRBuffTx[endIdxDR].data, (startIdxDR + DRBUFFSIZE - endIdxDR) % DRBUFFSIZE);
}
else if (TDIFF(loraAckTimeout,FDRS_ACK_TIMEOUT))
{
DBG1("LoRa Timeout waiting for ACK!");
// resend original packet again if retries are available
DBG2("Retries: " + String(retries) + " DR Index: " + String(endIdxDR) + " Address: 0x" + String(loraDRBuffTx[endIdxDR].dstAddress,HEX));
loraAckTimeout = millis();
loraAckState = stInProcess;
retries--;
transmitLoRa(&loraDRBuffTx[endIdxDR].dstAddress, &loraDRBuffTx[endIdxDR].data, (startIdxDR + DRBUFFSIZE - endIdxDR) % DRBUFFSIZE);
}
return;
}
static unsigned long txWindow = 0;
// It's polite to Listen more than you talk
if(TDIFF(txWindow,TXWINDOWMS)) {
// Start Transmit data from the SystemPacket queue
if(!SPQUEUEEMPTY && (loraTxState == stReady)) {
DBG2("SP Index: start: " + String(startIdxSP) + " end: " + String(endIdxSP) + " Address: 0x" + String(loraSPBuffTx[endIdxSP].dstAddress,HEX));
transmitLoRa(&loraSPBuffTx[endIdxSP].dstAddress, &loraSPBuffTx[endIdxSP].data, 1);
endIdxSP = (endIdxSP + 1) % SPBUFFSIZE;
}
// Start Transmit data from the DataReading queue
if(!DRQUEUEEMPTY && loraTxState == stReady && loraAckState == stReady)
{
if(!ack)
{
DBG2("DR Index: start: " + String(startIdxDR) + " end: " + String(endIdxDR) + " Address: 0x" + String(loraDRBuffTx[endIdxDR].dstAddress,HEX));
transmitLoRa(&loraDRBuffTx[endIdxDR].dstAddress, &loraDRBuffTx[endIdxDR].data, (startIdxDR + DRBUFFSIZE - endIdxDR) % DRBUFFSIZE);
endIdxDR = startIdxDR;
}
else
{
DBG2("Retries: " + String(retries) + " DR Index: " + String(endIdxDR) + " Address: 0x" + String(loraDRBuffTx[endIdxDR].dstAddress,HEX));
retries--;
loraAckState = stInProcess;
loraAckTimeout = millis();
transmitLoRa(&loraDRBuffTx[endIdxDR].dstAddress, &loraDRBuffTx[endIdxDR].data, (startIdxDR + DRBUFFSIZE - endIdxDR) % DRBUFFSIZE);
// Don't reset queue indexes here. We will reset them in the routine that checks for acks
}
}
// 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();
}
}
txWindow = millis();
}
// Change to ready at the end so only one transmit happens per function call
if(loraTxState == stCompleted) {
loraTxState = stReady;
}
return;
}
// Only for use in nodes - not intended to be used in gateway
bool reqTimeLoRa() {
SystemPacket sys_packet = {.cmd = cmd_time, .param = 0};
DBG1("Requesting time from gateway 0x" + String(gtwyAddress,HEX));
if(loraTxState == stReady) {
transmitLoRa(&gtwyAddress,&sys_packet,1);
return true;
}
else {
if(transmitLoRaAsync(&gtwyAddress, &sys_packet, 1))
{
return true;
}
}
return false;
}

@ -75,7 +75,7 @@ void sendTimeSerial();
#include "fdrs_node_espnow.h"
#endif
#ifdef USE_LORA
#include "fdrs_node_lora.h"
#include "fdrs_lora.h"
#endif
void beginFDRS()

@ -1,535 +0,0 @@
#include <RadioLib.h>
// 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_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))
// select LoRa band configuration
#if defined(LORA_FREQUENCY)
#define FDRS_LORA_FREQUENCY LORA_FREQUENCY
#else
#define FDRS_LORA_FREQUENCY GLOBAL_LORA_FREQUENCY
#endif // LORA_FREQUENCY
// select LoRa SF configuration
#if defined(LORA_SF)
#define FDRS_LORA_SF LORA_SF
#else
#define FDRS_LORA_SF GLOBAL_LORA_SF
#endif // LORA_SF
// select LoRa ACK configuration
#if defined(LORA_ACK) || defined(GLOBAL_LORA_ACK)
#define FDRS_LORA_ACK
#endif // LORA_ACK
// select LoRa ACK Timeout configuration
#if defined(LORA_ACK_TIMEOUT)
#define FDRS_ACK_TIMEOUT LORA_ACK_TIMEOUT
#else
#define FDRS_ACK_TIMEOUT GLOBAL_ACK_TIMEOUT
#endif // LORA_ACK_TIMEOUT
// select LoRa Retry configuration
#if defined(LORA_RETRIES)
#define FDRS_LORA_RETRIES LORA_RETRIES
#else
#define FDRS_LORA_RETRIES GLOBAL_LORA_RETRIES
#endif // LORA_RETRIES
// select LoRa Tx Power configuration
#if defined(LORA_TXPWR)
#define FDRS_LORA_TXPWR LORA_TXPWR
#else
#define FDRS_LORA_TXPWR GLOBAL_LORA_TXPWR
#endif // LORA_TXPWR
// select LoRa BANDWIDTH configuration
#if defined(LORA_BANDWIDTH)
#define FDRS_LORA_BANDWIDTH LORA_BANDWIDTH
#else
#define FDRS_LORA_BANDWIDTH GLOBAL_LORA_BANDWIDTH
#endif // LORA_BANDWIDTH
// select LoRa Coding Rate configuration
#if defined(LORA_CR)
#define FDRS_LORA_CR LORA_CR
#else
#define FDRS_LORA_CR GLOBAL_LORA_CR
#endif // LORA_CR
// select LoRa SyncWord configuration
#if defined(LORA_SYNCWORD)
#define FDRS_LORA_SYNCWORD LORA_SYNCWORD
#else
#define FDRS_LORA_SYNCWORD GLOBAL_LORA_SYNCWORD
#endif // LORA_SYNCWORD
#ifndef LORA_BUSY
#define LORA_BUSY RADIOLIB_NC
#endif
#ifdef CUSTOM_SPI
#ifdef ARDUINO_ARCH_RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI1);
#endif // RP2040
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY, SPI);
#else
RADIOLIB_MODULE radio = new Module(LORA_SS, LORA_DIO, LORA_RST, LORA_BUSY);
#endif // CUSTOM_SPI
bool pingFlag = false;
bool transmitFlag = false; // flag to indicate transmission or reception state
volatile bool enableInterrupt = true; // disable interrupt when it's not needed
volatile bool operationDone = false; // flag to indicate that a packet was sent or received
unsigned long receivedLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long ackOkLoRaMsg = 0; // Number of total LoRa packets with valid CRC
uint16_t LoRaAddress;
unsigned long transmitLoRaMsgwAck = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long msgOkLoRa = 0; // Number of total LoRa packets with valid CRC
time_t netTimeOffset = UINT32_MAX; // One direction of LoRa Ping time in units of seconds (1/2 full ping time)
uint16_t gtwyAddress = ((gatewayAddress[4] << 8) | GTWY_MAC);
// Function prototypes
crcResult getLoRa();
void printLoraPacket(uint8_t *p, int size);
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
void setFlag(void)
{
if (!enableInterrupt)
{ // check if the interrupt is enabled
return;
}
operationDone = true; // we sent or received packet, set the flag
}
crcResult handleLoRa()
{
crcResult crcReturned = CRC_NULL;
if (operationDone)
{ // the interrupt was triggered
// DBG("Interrupt Triggered.");
enableInterrupt = false;
operationDone = false;
if (transmitFlag) // the previous operation was transmission,
{
radio.finishTransmit();
radio.startReceive(); // return to listen mode
enableInterrupt = true;
transmitFlag = false;
}
else
{ // the previous operation was reception
crcReturned = getLoRa();
if (!transmitFlag) // return to listen if no transmission was begun
{
radio.startReceive();
}
enableInterrupt = true;
}
}
return crcReturned;
}
void begin_lora()
{
#ifdef CUSTOM_SPI
#ifdef ESP32
SPI.begin(LORA_SPI_SCK, LORA_SPI_MISO, LORA_SPI_MOSI);
#endif // ESP32
#ifdef ARDUINO_ARCH_RP2040
SPI1.setRX(LORA_SPI_MISO);
SPI1.setTX(LORA_SPI_MOSI);
SPI1.setSCK(LORA_SPI_SCK);
SPI1.begin(false);
#endif //ARDUINO_ARCH_RP2040
#endif // CUSTOM_SPI
#ifdef USE_SX126X
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR, 8, 1.6, false);
#else
int state = radio.begin(FDRS_LORA_FREQUENCY, FDRS_LORA_BANDWIDTH, FDRS_LORA_SF, FDRS_LORA_CR, FDRS_LORA_SYNCWORD, FDRS_LORA_TXPWR, 8, 0);
#endif
if (state == RADIOLIB_ERR_NONE)
{
DBG("RadioLib initialization successful!");
}
else
{
DBG("RadioLib initialization failed, code " + String(state));
while (true)
;
}
DBG("LoRa Initialized. Frequency: " + String(FDRS_LORA_FREQUENCY) + " Bandwidth: " + String(FDRS_LORA_BANDWIDTH) + " SF: " + String(FDRS_LORA_SF) + " CR: " + String(FDRS_LORA_CR) + " SyncWord: " + String(FDRS_LORA_SYNCWORD) + " Tx Power: " + String(FDRS_LORA_TXPWR) + "dBm");
#ifdef USE_SX126X
radio.setDio1Action(setFlag);
#else
radio.setDio0Action(setFlag, RISING);
#endif
radio.setCRC(false);
LoRaAddress = ((radio.randomByte() << 8) | radio.randomByte());
DBG("LoRa node address is " + String(LoRaAddress, HEX) + " (hex).");
state = radio.startReceive(); // start listening for LoRa packets
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG(" failed, code " + String(state));
while (true)
;
}
}
// Transmits Lora data by calling RadioLib library function
// Returns the CRC result if ACKs are enabled otherwise returns CRC_NULL
crcResult transmitLoRa(uint16_t *destMAC, DataReading *packet, uint8_t len)
{
crcResult crcReturned = CRC_NULL;
uint8_t pkt[6 + (len * sizeof(DataReading))];
uint16_t calcCRC = 0x0000;
pkt[0] = (*destMAC >> 8);
pkt[1] = (*destMAC & 0x00FF);
pkt[2] = (LoRaAddress >> 8);
pkt[3] = (LoRaAddress & 0x00FF);
memcpy(&pkt[4], packet, len * sizeof(DataReading));
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{ // Last 2 bytes are CRC so do not include them in the calculation itself
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
#ifndef LORA_ACK
calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK
#endif // LORA_ACK
pkt[len * sizeof(DataReading) + 4] = (calcCRC >> 8);
pkt[len * sizeof(DataReading) + 5] = (calcCRC & 0x00FF);
#ifdef LORA_ACK // Wait for ACK
int retries = FDRS_LORA_RETRIES + 1;
while (retries != 0)
{
if (transmitLoRaMsgwAck != 0)
{
DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1) + ", Ack Ok " + String((float)msgOkLoRa / transmitLoRaMsgwAck * 100) + "%");
}
else
{
DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX) + ". Retries remaining: " + String(retries - 1));
}
// printLoraPacket(pkt,sizeof(pkt));
int state = radio.transmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG(" failed, code " + String(state));
while (true)
;
}
transmitLoRaMsgwAck++;
unsigned long loraAckTimeout = millis() + FDRS_ACK_TIMEOUT;
retries--;
delay(10);
while (crcReturned == CRC_NULL && (millis() < loraAckTimeout))
{
crcReturned = handleLoRa();
}
if (crcReturned == CRC_OK)
{
// DBG("LoRa ACK Received! CRC OK");
msgOkLoRa++;
return CRC_OK; // we're done
}
else if (crcReturned == CRC_BAD)
{
// DBG("LoRa ACK Received! CRC BAD");
// Resend original packet again if retries are available
}
else
{
DBG("LoRa Timeout waiting for ACK!");
// resend original packet again if retries are available
}
}
#else // Send and do not wait for ACK reply
DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to gateway 0x" + String(*destMAC, HEX));
// printLoraPacket(pkt,sizeof(pkt));
int state = radio.startTransmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG(" failed, code " + String(state));
while (true)
;
}
transmitLoRaMsgwAck++;
#endif // LORA_ACK
return crcReturned;
}
// For now SystemPackets will not use ACK but will calculate CRC
// Returns CRC_NULL ask SystemPackets do not use ACKS at current time
crcResult transmitLoRa(uint16_t *destMAC, SystemPacket *packet, uint8_t len)
{
crcResult crcReturned = CRC_NULL;
uint8_t pkt[6 + (len * sizeof(SystemPacket))];
uint16_t calcCRC = 0x0000;
// Building packet -- address portion - first 4 bytes
pkt[0] = (*destMAC >> 8);
pkt[1] = (*destMAC & 0x00FF);
pkt[2] = (LoRaAddress >> 8);
pkt[3] = (LoRaAddress & 0x00FF);
// Building packet -- data portion - 5 bytes
memcpy(&pkt[4], packet, len * sizeof(SystemPacket));
// Calculate CRC of address and data portion of the packet
// Last 2 bytes are CRC so do not include them in the calculation itself
for (int i = 0; i < (sizeof(pkt) - 2); i++)
{
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, pkt[i]);
}
calcCRC = crc16_update(calcCRC, 0xA1); // Recalculate CRC for No ACK
// Building packet -- adding CRC - last 2 bytes
pkt[len * sizeof(SystemPacket) + 4] = (calcCRC >> 8);
pkt[len * sizeof(SystemPacket) + 5] = (calcCRC & 0x00FF);
// Packet is constructed now transmit the packet
DBG("Transmitting LoRa message of size " + String(sizeof(pkt)) + " bytes with CRC 0x" + String(calcCRC, HEX) + " to destination 0x" + String(*destMAC, HEX));
// printLoraPacket(pkt,sizeof(pkt));
int state = radio.transmit(pkt, sizeof(pkt));
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE)
{
}
else
{
DBG(" failed, code " + String(state));
while (true)
;
}
return crcReturned;
}
// ****DO NOT CALL getLoRa() directly! ***** Call handleLoRa() instead!
// getLoRa 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 getLoRa()
{
int packetSize = radio.getPacketLength();
if ((((packetSize - 6) % sizeof(DataReading) == 0) || ((packetSize - 6) % sizeof(SystemPacket) == 0)) && packetSize > 0)
{ // packet size should be 6 bytes plus multiple of size of DataReading
uint8_t packet[packetSize];
uint16_t packetCRC = 0x0000; // CRC Extracted from received LoRa packet
uint16_t calcCRC = 0x0000; // CRC calculated from received LoRa packet
uint16_t sourceMAC = 0x0000;
uint16_t destMAC = 0x0000;
radio.readData((uint8_t *)&packet, packetSize);
destMAC = (packet[0] << 8) | packet[1];
sourceMAC = (packet[2] << 8) | packet[3];
packetCRC = ((packet[packetSize - 2] << 8) | packet[packetSize - 1]);
// DBG("Source Address: 0x" + String(packet[2], HEX) + String(packet[3], HEX) + " Destination Address: 0x" + String(packet[0], HEX) + String(packet[1], HEX));
if ((destMAC == LoRaAddress) || (destMAC == 0xFFFF))
{ // Check if addressed to this device or broadcast
// printLoraPacket(packet,sizeof(packet));
if (receivedLoRaMsg != 0)
{ // Avoid divide by 0
DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg) + ", CRC Ok Pct " + String((float)ackOkLoRaMsg / receivedLoRaMsg * 100) + "%");
}
else
{
DBG("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX) + ", Total LoRa received: " + String(receivedLoRaMsg));
}
receivedLoRaMsg++;
// Evaluate CRC
for (int i = 0; i < (packetSize - 2); i++)
{ // Last 2 bytes of packet are the CRC so do not include them in calculation
// printf("CRC: %02X : %d\n",calcCRC, i);
calcCRC = crc16_update(calcCRC, packet[i]);
}
if ((packetSize - 6) % sizeof(DataReading) == 0)
{ // DataReading type packet
if (calcCRC == packetCRC)
{
SystemPacket ACK = {.cmd = cmd_ack, .param = CRC_OK};
DBG("CRC Match, sending ACK packet to node 0x" + String(sourceMAC, HEX) + "(hex)");
transmitLoRa(&sourceMAC, &ACK, 1); // Send ACK back to source
}
else if (packetCRC == crc16_update(calcCRC, 0xA1))
{ // Sender does not want ACK and CRC is valid
DBG("Node address 0x" + String(sourceMAC, 16) + "(hex) does not want ACK");
}
else
{
SystemPacket NAK = {.cmd = cmd_ack, .param = CRC_BAD};
// Send NAK packet to sensor
DBG("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)");
transmitLoRa(&sourceMAC, &NAK, 1); // CRC did not match so send NAK to source
return CRC_BAD; // Exit function and do not update newData to send invalid data further on
}
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);
newData = true;
ackOkLoRaMsg++;
return CRC_OK;
}
else if ((packetSize - 6) == sizeof(SystemPacket))
{
unsigned int ln = (packetSize - 6) / sizeof(SystemPacket);
SystemPacket receiveData[ln];
if ((packetCRC == calcCRC) || (packetCRC == crc16_update(calcCRC, 0xA1)))
{
memcpy(receiveData, &packet[4], packetSize - 6); // Split off data portion of packet (N bytes)
if (ln == 1 && receiveData[0].cmd == cmd_ack)
{
DBG("ACK Received - CRC Match");
}
else if (ln == 1 && receiveData[0].cmd == cmd_ping)
{ // We have received a ping request or reply??
if (receiveData[0].param == 1)
{ // This is a reply to our ping request
pingFlag = true;
DBG("We have received a ping reply via LoRa from address " + String(sourceMAC, HEX));
}
else if (receiveData[0].param == 0)
{
DBG("We have received a ping request from 0x" + String(sourceMAC, HEX) + ", Replying.");
SystemPacket pingReply = {.cmd = cmd_ping, .param = 1};
transmitLoRa(&sourceMAC, &pingReply, 1);
}
}
else if (ln == 1 && receiveData[0].cmd == cmd_time) {
if(timeSource.tmNetIf == TMIF_NONE || timeSource.tmNetIf != TMIF_ESPNOW || (timeSource.tmNetIf == TMIF_LORA && timeSource.tmAddress == sourceMAC)) {
DBG("Time rcv from LoRa 0x" + String(sourceMAC, HEX));
if(timeSource.tmNetIf == TMIF_NONE) {
timeSource.tmNetIf = TMIF_LORA;
timeSource.tmAddress = sourceMAC;
DBG("Time master is LoRa 0x" + String(sourceMAC, HEX));
}
setTime(receiveData[0].param);
adjTimeforNetDelay(netTimeOffset);
timeSource.tmLastTimeSet = millis();
}
else {
DBG("LoRa 0x" + String(sourceMAC, HEX) + " is not time master, discarding request");
}
}
else
{ // data we have received is not yet programmed. How we handle is future enhancement.
DBG("Received some LoRa SystemPacket data that is not yet handled. To be handled in future enhancement.");
DBG("ln: " + String(ln) + "data type: " + String(receiveData[0].cmd));
}
ackOkLoRaMsg++;
return CRC_OK;
}
else
{
DBG("ACK Received CRC Mismatch! Packet CRC is 0x" + String(packetCRC, HEX) + ", Calculated CRC is 0x" + String(calcCRC, HEX));
return CRC_BAD;
}
}
}
else
{
// DBG("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));
return CRC_NULL;
}
}
else
{
if (packetSize != 0)
{
// DBG("Incoming LoRa packet of " + String(packetSize) + "bytes not processed.");
// uint8_t packet[packetSize];
// radio.readData((uint8_t *)&packet, packetSize);
// printLoraPacket(packet,sizeof(packet));
return CRC_NULL;
}
}
return CRC_NULL;
}
// FDRS Sensor pings gateway and listens for a defined amount of time for a reply
// Blocking function for timeout amount of time (up to timeout time waiting for reply)(IE no callback)
// Returns the amount of time in ms that the ping takes or predefined value if ping fails within timeout
uint32_t pingFDRSLoRa(uint16_t *address, uint32_t timeout)
{
SystemPacket sys_packet = {.cmd = cmd_ping, .param = 0};
transmitLoRa(address, &sys_packet, 1);
DBG("LoRa ping sent to address: 0x" + String(*address, HEX));
uint32_t ping_start = millis();
pingFlag = false;
while ((millis() - ping_start) <= timeout)
{
handleLoRa();
#ifdef ESP8266
yield();
#endif
if (pingFlag)
{
DBG("LoRa Ping Returned: " + String(millis() - ping_start) + "ms.");
pingFlag = false;
return (millis() - ping_start);
}
}
DBG("No LoRa ping returned within " + String(timeout) + "ms.");
return UINT32_MAX;
}
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)
printf("\n%02d: ", i);
printf("%02X ", p[i]);
}
printf("\n");
}
// Pings the LoRa time master periodically to calculate the time delay in the LoRa radio link
// Returns success or failure of the ping result
bool pingLoRaTimeMaster() {
static unsigned long lastTimeMasterPing = 0;
// ping the time master every 5 minutes
if(millis() - lastTimeMasterPing > (5*60*1000 + random(0,2000))) {
time_t pingTimeMs;
pingTimeMs = pingFDRSLoRa(&timeSource.tmAddress,4000);
if(pingTimeMs != UINT32_MAX) {
netTimeOffset = pingTimeMs/2/1000;
adjTimeforNetDelay(netTimeOffset);
lastTimeMasterPing = millis();
return true;
}
lastTimeMasterPing = millis();
}
return false;
}
// skeleton function required by fdrs_time.h
void sendTimeLoRa() {
return;
}
Loading…
Cancel
Save