Merge pull request #193 from aviateur17/timekeeping_202402
FDRS Support for timekeeping - 2024 versionmain
commit
6b7134fb51
@ -0,0 +1,149 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// Example sketch to provide several examples to obtain and use time
|
||||
// from a gateway
|
||||
// This sketch retrieves the time from a nearby FDRS gateway using ESP-NOW.
|
||||
// After retrieving the time, at 60 second intervals, it prints out each field of the time
|
||||
// in two different methods.
|
||||
//
|
||||
// Developed by Timm Bogner (timmbogner@gmail.com) in Urbana, Illinois, USA
|
||||
// FDRS Timekeeping was developed and contributed by Jeff Lehman (aviateur17)
|
||||
//
|
||||
//
|
||||
|
||||
#include "fdrs_node_config.h"
|
||||
#include <fdrs_node.h>
|
||||
|
||||
unsigned long printJob = 0;
|
||||
struct tm timeData;
|
||||
char strftime_buf[80];
|
||||
|
||||
static char dayNames[][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
|
||||
static char monthNames[][12] = { "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December" };
|
||||
bool isPM = false;
|
||||
|
||||
void setup() {
|
||||
DBG("FDRS ControllerTime example.");
|
||||
|
||||
beginFDRS();
|
||||
|
||||
// Request that the gateway send us the current time
|
||||
DBG("Requesting time from gateway");
|
||||
reqTimeFDRS();
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
loopFDRS();
|
||||
|
||||
// If time is valid, prints time out every 60 seconds (60,000 ms)
|
||||
if(millis() - printJob > (1000 * 60) || printJob == 0)
|
||||
{
|
||||
// Check to see that we have a valid time
|
||||
if(validTime())
|
||||
{
|
||||
// time is valid
|
||||
|
||||
DBG("");
|
||||
DBG("Configured Standard Time offset from UTC: " + String(STD_OFFSET) + " hours.");
|
||||
DBG("Configured Daylight Savings Time offset from UTC: " + String(DST_OFFSET) + " hours.");
|
||||
|
||||
// Option 1 - use strftime function
|
||||
// Local time
|
||||
// time is stored in UTC so add offset to convert to local time
|
||||
time_t localt = time(NULL) + (isDST?dstOffset:stdOffset);
|
||||
// convert from time_t format to struct tm format
|
||||
localtime_r(&localt, &timeData);
|
||||
// Store the date/time in a character array
|
||||
// https://www.tutorialspoint.com/c_standard_library/c_function_strftime.htm
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeData);
|
||||
// Print out the character array to display the local date/time
|
||||
DBG("---- strftime() function output ----");
|
||||
DBG("Local date/time is: " + String(strftime_buf) + (isDST?" DST":" STD"));
|
||||
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%Y", &timeData);
|
||||
DBG("Year: " + String(strftime_buf)); // %Y - year in 4 digits
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%m", &timeData);
|
||||
DBG("Month: " + String(strftime_buf)); // %m - Month as a decimal number
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%B", &timeData);
|
||||
DBG("Month Name: " + String(strftime_buf)); // %B - Month Full Name
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%d", &timeData);
|
||||
DBG("Day of Month: " + String(strftime_buf)); // %d - day of the month (1 - 31)
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%w", &timeData);
|
||||
DBG("Day of Week: " + String(strftime_buf)); // %w - Days since Sunday = 0
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%A", &timeData);
|
||||
DBG("Day Name: " + String(strftime_buf)); // %A - Full weekday name
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%j", &timeData);
|
||||
DBG("Day of Year: " + String(strftime_buf)); // %j - days since January 1st = 0
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%H", &timeData);
|
||||
DBG("Hour (24 hour format): " + String(strftime_buf)); // %H - Hours since mindnight in 24 hour format
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%I", &timeData);
|
||||
DBG("Hour (12 hour format): " + String(strftime_buf)); // %I - Hours since mindnight in 12 hour format
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%M", &timeData);
|
||||
DBG("Minute: " + String(strftime_buf)); // %M - Minutes
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%S", &timeData);
|
||||
DBG("Second: " + String(strftime_buf)); // %S - Seconds
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%p", &timeData);
|
||||
DBG("AM/PM: " + String(strftime_buf)); // %p - AM/PM
|
||||
DBG("Daylight Savings: " + String((isDST ? " DST (yes)" : " STD (no)")));
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%A, %m/%d/%Y %H:%M:%S %p", &timeData);
|
||||
DBG("US format: " + String(strftime_buf) + String((isDST ? " DST" : " STD"))); // Day, MM/DD/YYYY HH:MM:SS AM/PM DST/STD
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%A, %d/%m/%Y %I:%M:%S", &timeData);
|
||||
DBG("European format: " + String(strftime_buf) + String((isDST ? " DST" : " STD"))); // Day, DD/MM/YYYY HH:MM:SS DST/STD
|
||||
|
||||
|
||||
|
||||
// Option 2 - read directly from the tm struct
|
||||
// Local time
|
||||
// time is stored in UTC so add offset to convert to local time
|
||||
localt = time(NULL) + (isDST?dstOffset:stdOffset);
|
||||
// convert from time_t format to struct tm format
|
||||
localtime_r(&localt, &timeData);
|
||||
// Struct tm provides the date/time elements split out
|
||||
// https://www.tutorialspoint.com/c_standard_library/c_function_strftime.htm
|
||||
DBG("---- struct tm output ----");
|
||||
DBG("Year: " + String(timeData.tm_year + 1900)); // tm_year returns number of years since 1900 so we add 1900
|
||||
DBG("Month: " + String(timeData.tm_mon + 1)); // Jan = 0 so add 1 to (1 - 12)
|
||||
DBG("Month Name: " + String(monthNames[timeData.tm_mon]));
|
||||
DBG("Day of Month: " + String(timeData.tm_mday)); // day of the month (1 - 31)
|
||||
DBG("Day of Week: " + String(timeData.tm_wday)); // Days since Sunday = 0
|
||||
DBG("Day Name: " + String(dayNames[timeData.tm_wday]));
|
||||
DBG("Day of Year: " + String(timeData.tm_yday + 1)); // days since January 1st = 0
|
||||
DBG("Hour (24 hour format): " + String(timeData.tm_hour)); // Hours since mindnight in 24 hour format
|
||||
if(timeData.tm_hour >= 12 && timeData.tm_hour <= 23)
|
||||
{
|
||||
isPM = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isPM = false;
|
||||
}
|
||||
if(timeData.tm_hour >= 13)
|
||||
{
|
||||
timeData.tm_hour = timeData.tm_hour - 12;
|
||||
}
|
||||
DBG("Hour (12 hour format): " + String(timeData.tm_hour) + " " + String((isPM ? "PM" : "AM"))); // Hours since mindnight in 12 hour format
|
||||
DBG("Minute: " + String(timeData.tm_min));
|
||||
DBG("Second: " + String(timeData.tm_sec));
|
||||
DBG("Daylight Savings: " + String((isDST ? " DST (yes)" : " STD (no)")));
|
||||
DBG("Local Time: " + String(dayNames[timeData.tm_wday]) + ", " + String(timeData.tm_mon + 1) + "/" + String(timeData.tm_mday) + "/"
|
||||
+ String(timeData.tm_year + 1900) + " " + String(timeData.tm_hour) + ":" + String(timeData.tm_min) + ":" + String(timeData.tm_sec)
|
||||
+ String((isPM ? " PM" : " AM")) + String((isDST ? " DST" : " STD")));
|
||||
DBG("");
|
||||
DBG("");
|
||||
|
||||
printJob = millis();
|
||||
}
|
||||
else // Time is not valid. Request time from the gateway
|
||||
{
|
||||
DBG("Time not valid.");
|
||||
DBG("Check gateway is running and has valid time.");
|
||||
DBG("");
|
||||
reqTimeFDRS();
|
||||
|
||||
// Try again in 20 seconds
|
||||
printJob = printJob + 20000;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// Sensor Configuration
|
||||
|
||||
#include <fdrs_globals.h>
|
||||
|
||||
#define READING_ID 1 //Unique ID for this sensor
|
||||
#define GTWY_MAC 0x01 //Address of the nearest gateway
|
||||
|
||||
#define USE_ESPNOW
|
||||
//#define USE_LORA
|
||||
#define DEEP_SLEEP
|
||||
//#define POWER_CTRL 14
|
||||
#define FDRS_DEBUG
|
||||
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
|
||||
|
||||
// LoRa Configuration
|
||||
#define RADIOLIB_MODULE SX1276
|
||||
#define LORA_SS 18
|
||||
#define LORA_RST 14
|
||||
#define LORA_DIO 26
|
||||
#define LORA_BUSY 33
|
||||
//#define USE_SX126X
|
||||
|
||||
#define LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
|
||||
#define LORA_ACK // Request LoRa acknowledgment.
|
||||
|
||||
//#define USE_LR // Use ESP-NOW LR mode (ESP32 only)
|
||||
|
||||
// Time settings
|
||||
#define USDST
|
||||
// #define EUDST
|
||||
#define STD_OFFSET (-6) // Local standard time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define DST_OFFSET (STD_OFFSET + 1) // Local savings time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define TIME_PRINTTIME 99 // Time, in minutes, between printing local time to debug
|
@ -0,0 +1,70 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// LilyGo T-Watch 2020 (v1) Example
|
||||
// This sketch retrieves the time from a nearby FDRS gateway using ESP-NOW.
|
||||
//
|
||||
// Developed by Timm Bogner (timmbogner@gmail.com) in Urbana, Illinois, USA
|
||||
// FDRS Timekeeping was developed and contributed by Jeff Lehman (aviateur17)
|
||||
//
|
||||
// Gratitude to Lewis He and LilyGo for building and supporting the T-Watch.
|
||||
//
|
||||
|
||||
#define UPTIME 3 // Seconds to remain on before sleeping
|
||||
#define LILYGO_WATCH_2020_V1
|
||||
|
||||
#include <LilyGoWatch.h>
|
||||
#include "fdrs_node_config.h"
|
||||
#include <fdrs_node.h>
|
||||
|
||||
TTGOClass *ttgo;
|
||||
TFT_eSPI *tft;
|
||||
PCF8563_Class *rtc;
|
||||
AXP20X_Class *power;
|
||||
|
||||
uint32_t interval = 0;
|
||||
|
||||
void fdrs_recv_cb(DataReading theData) {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
ttgo = TTGOClass::getWatch();
|
||||
ttgo->begin();
|
||||
ttgo->openBL();
|
||||
rtc = ttgo->rtc;
|
||||
tft = ttgo->tft;
|
||||
power = ttgo->power;
|
||||
|
||||
tft->setTextColor(TFT_RED, TFT_BLACK);
|
||||
//Draw voltage in top right corner
|
||||
tft->drawString(String(power->getBattVoltage() / 1000.0), 180, 0, 4);
|
||||
|
||||
beginFDRS();
|
||||
uint32_t contact = pingFDRS(100);
|
||||
if (contact != UINT32_MAX) {
|
||||
// Draw ping response time (if any) in top left
|
||||
tft->drawString(String(contact), 0, 0, 4);
|
||||
addFDRS(fdrs_recv_cb);
|
||||
delay(100);
|
||||
rtc->syncToRtc();
|
||||
}
|
||||
|
||||
char strftime_buf[64];
|
||||
time_t local = time(NULL) + (isDST ? dstOffset : stdOffset);
|
||||
localtime_r(&local, &timeinfo);
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%m-%d-%Y", &timeinfo); // generate MM-DD-YYYY local time character array
|
||||
tft->setTextColor(TFT_BLUE, TFT_BLACK);
|
||||
tft->drawString(strftime_buf, 50, 200, 4);
|
||||
tft->setTextColor(TFT_GREEN, TFT_BLACK);
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%I:%M:%S", &timeinfo); // generate HH:MM:SS local time character array
|
||||
tft->drawString(strftime_buf, 5, 75, 7);
|
||||
delay(UPTIME * 1000);
|
||||
|
||||
power->clearIRQ();
|
||||
ttgo->displaySleep();
|
||||
ttgo->powerOff();
|
||||
esp_sleep_enable_ext1_wakeup(GPIO_SEL_35, ESP_EXT1_WAKEUP_ALL_LOW);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// Sensor Configuration
|
||||
|
||||
#include <fdrs_globals.h>
|
||||
|
||||
#define READING_ID 1 //Unique ID for this sensor
|
||||
#define GTWY_MAC 0x01 //Address of the nearest gateway
|
||||
|
||||
#define USE_ESPNOW
|
||||
//#define USE_LORA
|
||||
#define DEEP_SLEEP
|
||||
//#define POWER_CTRL 14
|
||||
#define FDRS_DEBUG
|
||||
|
||||
// LoRa Configuration
|
||||
#define RADIOLIB_MODULE SX1276
|
||||
#define LORA_SS 18
|
||||
#define LORA_RST 14
|
||||
#define LORA_DIO 26
|
||||
#define LORA_BUSY 33
|
||||
//#define USE_SX126X
|
||||
|
||||
#define LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
|
||||
#define LORA_ACK // Request LoRa acknowledgment.
|
||||
|
||||
//#define USE_LR // Use ESP-NOW LR mode (ESP32 only)
|
||||
|
||||
// Time settings
|
||||
#define USDST
|
||||
// #define EUDST
|
||||
#define STD_OFFSET (-6) // Local standard time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define DST_OFFSET (STD_OFFSET + 1) // Local savings time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define TIME_PRINTTIME 10 // Time, in minutes, between printing local time to debug
|
@ -0,0 +1,161 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// LoRa Stress Tester or "Spammer"
|
||||
//
|
||||
//
|
||||
|
||||
#include "fdrs_node_config.h"
|
||||
#include <fdrs_node.h>
|
||||
|
||||
#define PING_TIME_DELAY 300
|
||||
#define REQ_TIME_DELAY 300
|
||||
#define LOAD_TIME_DELAY 1000
|
||||
#define CYCLES 15
|
||||
unsigned long lastruntime = 0;
|
||||
int test = 0;
|
||||
unsigned long lastCycle = 0;
|
||||
|
||||
void setup() {
|
||||
beginFDRS();
|
||||
}
|
||||
void loop() {
|
||||
loopFDRS();
|
||||
|
||||
switch (test) {
|
||||
case 0:
|
||||
DBG("Sending pings in quick succession.");
|
||||
delay(4000);
|
||||
for(int i=1; i < CYCLES + 1; i) {
|
||||
if(TDIFF(lastruntime,PING_TIME_DELAY)) {
|
||||
lastruntime = millis();
|
||||
Serial.println();
|
||||
DBG("--- Test #" + String(i) + " ---");
|
||||
Serial.println();
|
||||
pingFDRS(1000);
|
||||
i++;
|
||||
}
|
||||
loopFDRS();
|
||||
}
|
||||
while(millis()-lastruntime < 1000) {
|
||||
loopFDRS();
|
||||
}
|
||||
test++;
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
DBG("Sending time requests in quick succession.");
|
||||
delay(4000);
|
||||
for(int i=1; i < CYCLES + 1; i) {
|
||||
if(TDIFF(lastruntime,REQ_TIME_DELAY)) {
|
||||
lastruntime = millis();
|
||||
Serial.println();
|
||||
DBG("--- Test #" + String(i) + " ---");
|
||||
Serial.println();
|
||||
reqTimeFDRS();
|
||||
i++;
|
||||
}
|
||||
loopFDRS();
|
||||
}
|
||||
while(millis()-lastruntime < 1000) {
|
||||
loopFDRS();
|
||||
}
|
||||
test++;
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
DBG("No ACK - data cycles in quick succession.");
|
||||
ack = false;
|
||||
delay(4000);
|
||||
for(int i=1; i < CYCLES + 1; i) {
|
||||
if(TDIFF(lastruntime,LOAD_TIME_DELAY)) {
|
||||
lastruntime = millis();
|
||||
Serial.println();
|
||||
DBG("--- Test #" + String(i) + " ---");
|
||||
Serial.println();
|
||||
for(int j=0; j < i; j++) {
|
||||
loadFDRS(READING_ID + j, STATUS_T);
|
||||
}
|
||||
if(sendFDRS()){
|
||||
DBG("Big Success!");
|
||||
} else {
|
||||
DBG("Nope, not so much.");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
loopFDRS();
|
||||
}
|
||||
while(millis()-lastruntime < 1000) {
|
||||
loopFDRS();
|
||||
}
|
||||
test++;
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
DBG("ACK - data cycles in quick succession.");
|
||||
ack = true;
|
||||
delay(4000);
|
||||
for(int i=1; i < CYCLES + 1; i) {
|
||||
if(TDIFF(lastruntime,LOAD_TIME_DELAY)) {
|
||||
lastruntime = millis();
|
||||
Serial.println();
|
||||
DBG("--- Test #" + String(i) + " ---");
|
||||
Serial.println();
|
||||
for(int j=0; j < i; j++) {
|
||||
loadFDRS(READING_ID + j, STATUS_T);
|
||||
}
|
||||
if(sendFDRS()){
|
||||
DBG("Big Success!");
|
||||
} else {
|
||||
DBG("Nope, not so much.");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
loopFDRS();
|
||||
}
|
||||
while(millis()-lastruntime < 1000) {
|
||||
loopFDRS();
|
||||
}
|
||||
test++;
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
DBG("Combination of commands in quick succession.");
|
||||
delay(4000);
|
||||
for(int i=1; i < CYCLES + 1; i) {
|
||||
if(TDIFF(lastruntime,5000)) {
|
||||
lastruntime = millis();
|
||||
Serial.println();
|
||||
DBG("--- Test #" + String(i) + " ---");
|
||||
Serial.println();
|
||||
pingFDRS(1000);
|
||||
reqTimeFDRS();
|
||||
subscribeFDRS(READING_ID);
|
||||
i++;
|
||||
}
|
||||
loopFDRS();
|
||||
}
|
||||
while(millis()-lastruntime < 2000) {
|
||||
loopFDRS();
|
||||
}
|
||||
test++;
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
break;
|
||||
|
||||
default:
|
||||
DBG("DONE! Starting over again soon.");
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
delay(4000);
|
||||
test = 0;
|
||||
break;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
// FARM DATA RELAY SYSTEM
|
||||
//
|
||||
// Sensor Configuration
|
||||
|
||||
#include <fdrs_globals.h>
|
||||
|
||||
#define READING_ID 5 //Unique ID for this sensor
|
||||
#define GTWY_MAC 0x01 //Address of the nearest gateway
|
||||
|
||||
// #define USE_ESPNOW
|
||||
#define USE_LORA
|
||||
//#define DEEP_SLEEP
|
||||
//#define POWER_CTRL 14
|
||||
#define FDRS_DEBUG
|
||||
//
|
||||
// I2C - OLED or RTC
|
||||
#define I2C_SDA 5
|
||||
#define I2C_SCL 6
|
||||
|
||||
// OLED -- Displays console debugging messages on an SSD1306 I²C OLED
|
||||
// #define USE_OLED
|
||||
#define OLED_HEADER "FDRS"
|
||||
#define OLED_PAGE_SECS 30
|
||||
#define OLED_RST -1
|
||||
|
||||
// LoRa Configuration
|
||||
#define RADIOLIB_MODULE SX1276 //Tested on SX1276
|
||||
#define LORA_SS 18
|
||||
#define LORA_RST 14
|
||||
#define LORA_DIO 26
|
||||
#define LORA_BUSY 33
|
||||
//#define USE_SX126X
|
||||
|
||||
//#define CUSTOM_SPI
|
||||
#define LORA_SPI_SCK 5
|
||||
#define LORA_SPI_MISO 19
|
||||
#define LORA_SPI_MOSI 27
|
||||
|
||||
#define LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
|
||||
#define LORA_ACK // Request LoRa acknowledgment.
|
||||
|
||||
// Time settings
|
||||
#define USDST
|
||||
// #define EUDST
|
||||
#define STD_OFFSET (-6) // Local standard time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define DST_OFFSET (STD_OFFSET + 1) // Local savings time offset in hours from UTC - if unsure, check https://time.is
|
||||
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
|
@ -1,629 +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
|
||||
|
||||
|
||||
#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 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
|
||||
|
||||
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
|
||||
|
||||
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);
|
||||
|
||||
#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(" 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 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(" 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(" 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("Sensor 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 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];
|
||||
|
||||
if (calcCRC == packetCRC)
|
||||
{
|
||||
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
|
||||
{ // 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 if (packetCRC == crc16_update(calcCRC, 0xA1))
|
||||
{ // Sender does not want ACK and CRC is valid
|
||||
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
|
||||
{ // 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return crcReturned;
|
||||
}
|
@ -0,0 +1,926 @@
|
||||
|
||||
#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 TXDELAYMS 300
|
||||
#define SPBUFFSIZE 10
|
||||
#define LORASIZE (250 / sizeof(DataReading))
|
||||
#define DRBUFFSIZE 100
|
||||
#define ISBUFFEMPTY(buff) ((buff.endIdx == buff.startIdx) ? true: false)
|
||||
#define ISBUFFFULL(buff) (((buff.endIdx + 1) % buff.size) == buff.startIdx ? true: false)
|
||||
#define BUFFINCSTART(buff) (buff.startIdx = (buff.startIdx + 1) % buff.size)
|
||||
#define BUFFINCEND(buff) (buff.endIdx = (buff.endIdx + 1) % buff.size)
|
||||
#define BUFFCLEAR(buff) (buff.startIdx = buff.endIdx)
|
||||
|
||||
// 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 FDRS_LORA_ACK
|
||||
bool ack = true;
|
||||
#else
|
||||
bool ack = false;
|
||||
#endif // LORA_ACK
|
||||
|
||||
Ping loraPing;
|
||||
|
||||
DRRingBuffer drBuff = {.dr = (DataReading*)calloc(DRBUFFSIZE,sizeof(DataReading)), \
|
||||
.address = (uint16_t*)calloc(DRBUFFSIZE,sizeof(uint16_t)), .startIdx = 0, .endIdx = 0, .size = DRBUFFSIZE};
|
||||
SPRingBuffer spBuff = {.sp = (SystemPacket*)calloc(SPBUFFSIZE,sizeof(SystemPacket)), \
|
||||
.address = (uint16_t*)calloc(SPBUFFSIZE,sizeof(uint16_t)), .startIdx = 0, .endIdx = 0, .size = SPBUFFSIZE};
|
||||
|
||||
int loraTxState = stReady;
|
||||
int loraAckState = stReady;
|
||||
|
||||
|
||||
#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 loraAckTimeout = 0;
|
||||
unsigned long rxCountDR = 0; // Number of total LoRa DR packets destined for us and of valid size
|
||||
unsigned long rxCountSP = 0; // Number of total LoRa SP packets destined for us and of valid size
|
||||
unsigned long rxCountCrcOk = 0; // Number of total Lora packets with valid CRC
|
||||
unsigned long txCountDR = 0; // Number of total LoRa DR packets transmitted
|
||||
unsigned long txCountSP = 0; // Number of total LoRa SP packets transmitted
|
||||
extern time_t now;
|
||||
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)
|
||||
;
|
||||
}
|
||||
txCountDR++;
|
||||
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)
|
||||
;
|
||||
}
|
||||
txCountSP++;
|
||||
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 *destAddr, SystemPacket *sp, uint8_t len)
|
||||
{
|
||||
for(int i=0; i < len; i++)
|
||||
{
|
||||
//check for full buffer
|
||||
if(ISBUFFFULL(spBuff)) {
|
||||
DBG("Lora SP Buffer Overflow!");
|
||||
BUFFINCSTART(spBuff);
|
||||
}
|
||||
|
||||
//add packet to buffer
|
||||
*(spBuff.sp + spBuff.endIdx) = *(sp + i);
|
||||
*(spBuff.address + spBuff.endIdx) = *destAddr;
|
||||
BUFFINCEND(spBuff);
|
||||
}
|
||||
DBG2("SP added to LoRa buffer. start: " + String(spBuff.startIdx) + " end: " + String(spBuff.endIdx));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wrapper for transmitLoRa for DataReading type packets to handle processing Receiving CRCs and retransmitting packets
|
||||
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
|
||||
//add packet to buffer
|
||||
for(int i=0; i < len; i++)
|
||||
{
|
||||
if(ISBUFFFULL(drBuff))
|
||||
{
|
||||
DBG("Lora DR Buffer Overflow!");
|
||||
// We just lost one reading - the oldest reading
|
||||
BUFFINCSTART(drBuff);
|
||||
}
|
||||
*(drBuff.dr + drBuff.endIdx) = *(dr + i);
|
||||
*(drBuff.address + drBuff.endIdx) = *destAddr;
|
||||
BUFFINCEND(drBuff);
|
||||
}
|
||||
// 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, *(drBuff.address + drBuff.startIdx));
|
||||
// }
|
||||
DBG2("DR added to LoRa buffer. start: " + String(drBuff.startIdx) + " end: " + String(drBuff.endIdx));
|
||||
return true;
|
||||
}
|
||||
|
||||
// return the number of consecutive DRs in the DR Queue that have the same destination address
|
||||
uint transmitSameAddrLoRa() {
|
||||
uint count = 0;
|
||||
|
||||
for(int i=drBuff.startIdx; i!=drBuff.endIdx; i = (i + 1) % drBuff.size) {
|
||||
if(*(drBuff.address + i) == *(drBuff.address + drBuff.startIdx))
|
||||
count++;
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check for data size greater than what can be sent via LoRa packets
|
||||
if(count > LORASIZE) {
|
||||
return LORASIZE;
|
||||
}
|
||||
else {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 - gateway
|
||||
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 sends ping reply - gateway
|
||||
bool pingReplyLoRa(uint16_t address)
|
||||
{
|
||||
SystemPacket sys_packet = {.cmd = cmd_ping, .param = ping_reply};
|
||||
|
||||
if(loraTxState == stReady) {
|
||||
DBG1("LoRa ping reply sent to address: 0x" + String(address, HEX));
|
||||
transmitLoRa(&address,&sys_packet,1);
|
||||
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));
|
||||
DBG1("Incoming LoRa. Size: " + String(packetSize) + " Bytes, RSSI: " + String(radio.getRSSI()) + "dBm, SNR: " + String(radio.getSNR()) + "dB, PacketCRC: 0x" + String(packetCRC, HEX));
|
||||
// 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
|
||||
rxCountDR++;
|
||||
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
|
||||
}
|
||||
rxCountCrcOk++;
|
||||
memcpy(&theData, &packet[4], packetSize - 6); // Split off data portion of packet (N - 6 bytes (6 bytes for headers and CRC))
|
||||
ln = (packetSize - 6) / sizeof(DataReading);
|
||||
if (memcmp(&sourceMAC, &LoRa1, 2) == 0)
|
||||
{ // Check if it is from a registered sender
|
||||
newData = event_lora1;
|
||||
return CRC_OK;
|
||||
}
|
||||
else if (memcmp(&sourceMAC, &LoRa2, 2) == 0)
|
||||
{
|
||||
newData = event_lora2;
|
||||
return CRC_OK;
|
||||
}
|
||||
else {
|
||||
newData = event_lorag;
|
||||
return CRC_OK;
|
||||
}
|
||||
}
|
||||
else if ((packetSize - 6) == sizeof(SystemPacket))
|
||||
{
|
||||
unsigned int ln = (packetSize - 6) / sizeof(SystemPacket);
|
||||
SystemPacket receiveData[ln];
|
||||
rxCountSP++;
|
||||
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("Ping reply via LoRa from address 0x" + String(sourceMAC, HEX));
|
||||
}
|
||||
else if (receiveData[0].param == ping_request)
|
||||
{
|
||||
DBG1("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));
|
||||
}
|
||||
rxCountCrcOk++;
|
||||
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
|
||||
{ // Uncommenting below will print out packets from other LoRa controllers being sent.
|
||||
// DBG2("Incoming LoRa packet of " + String(packetSize) + " bytes received from address 0x" + String(sourceMAC, HEX) + " destined for node address 0x" + String(destMAC, HEX));
|
||||
// printLoraPacket(packet,sizeof(packet));
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
// Start ACK timeout after transmission is completed.
|
||||
if(loraAckState == stInProcess) {
|
||||
loraAckTimeout = millis();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// FDRS Sensor pings address and listens for a defined amount of time for a reply if no tx in process
|
||||
// otherwise queues up a ping in the SP Buffer.
|
||||
int pingRequestLoRa(uint16_t address, uint32_t timeout)
|
||||
{
|
||||
int pingResult = -1;
|
||||
|
||||
// Check if a previous ping is already in process
|
||||
if(loraPing.status == stReady) {
|
||||
SystemPacket sys_packet = {.cmd = cmd_ping, .param = ping_request};
|
||||
|
||||
loraPing.timeout = timeout;
|
||||
loraPing.address = address;
|
||||
// Perform blocking ping if nothing else is in process
|
||||
if(loraTxState == stReady) {
|
||||
loraPing.status = stInProcess;
|
||||
loraPing.start = millis();
|
||||
DBG1("LoRa ping request sent to address: 0x" + String(address, HEX));
|
||||
transmitLoRa(&address,&sys_packet,1);
|
||||
while(loraPing.status == stInProcess && (millis() - loraPing.start < loraPing.timeout)) {
|
||||
LoRaTxRxOperation();
|
||||
}
|
||||
if(loraPing.status == stCompleted) {
|
||||
loraPing.response = millis() - loraPing.start;
|
||||
pingResult = loraPing.response;
|
||||
DBG("LoRa Ping Returned: " + String(loraPing.response) + "ms.");
|
||||
if(loraPing.address == timeSource.tmAddress) {
|
||||
netTimeOffset = loraPing.response/2/1000;
|
||||
adjTimeforNetDelay(netTimeOffset);
|
||||
}
|
||||
}
|
||||
else {
|
||||
DBG("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;
|
||||
}
|
||||
// Something else is in process, Most likely LoRa ACK, so queue up the ping
|
||||
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 pingResult;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleLoRa()
|
||||
{
|
||||
static uint8_t len = 0;
|
||||
static DataReading *data;
|
||||
static uint16_t address;
|
||||
static unsigned long lastTxtime = 0;
|
||||
static unsigned long statsTime = 0;
|
||||
|
||||
LoRaTxRxOperation();
|
||||
|
||||
// check for result of any ongoing async ping operations
|
||||
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;
|
||||
|
||||
// Process any DR ACKs in progress
|
||||
if(loraTxState == stReady && loraAckState != stReady) {
|
||||
if (loraAckState == stCrcMatch)
|
||||
{
|
||||
DBG1("LoRa ACK Received! CRC OK");
|
||||
free(data);
|
||||
data = NULL;
|
||||
retries = FDRS_LORA_RETRIES;
|
||||
len = 0;
|
||||
loraAckState = stReady;
|
||||
}
|
||||
else if(retries < 0) {
|
||||
DBG1("Retries Exhausted.");
|
||||
retries = FDRS_LORA_RETRIES;
|
||||
if(ISBUFFFULL(drBuff)) {
|
||||
len = 0;
|
||||
free(data);
|
||||
data = NULL;
|
||||
}
|
||||
// do we transmit ourselves to death or just drop the data?
|
||||
// here we drop the data so we don't keep transmitting to death
|
||||
len = 0;
|
||||
free(data);
|
||||
data = NULL;
|
||||
loraAckState = stReady;
|
||||
}
|
||||
else if (loraAckState == stCrcMismatch)
|
||||
{
|
||||
DBG1("LoRa ACK Received! CRC BAD");
|
||||
// Resend original packet again if retries are available
|
||||
loraAckState = stReady;
|
||||
}
|
||||
else if (TDIFF(loraAckTimeout,FDRS_ACK_TIMEOUT)) {
|
||||
DBG1("LoRa Timeout waiting for ACK!");
|
||||
// resend original packet again if retries are available
|
||||
loraAckState = stReady;
|
||||
}
|
||||
if(loraTxState == stCompleted) {
|
||||
loraTxState = stReady;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Start Transmit data from the SystemPacket queue
|
||||
if(!ISBUFFEMPTY(spBuff) && (loraTxState == stReady)) {
|
||||
DBG2("SP Index: start: " + String(spBuff.startIdx) + " end: " + String(spBuff.endIdx) + " Address: 0x" + String(*(spBuff.address + spBuff.startIdx),HEX) + " Cmd: " + String(spBuff.sp->cmd));
|
||||
// Lora ping request stuff here
|
||||
if((spBuff.sp + spBuff.startIdx)->cmd == cmd_ping && (spBuff.sp + spBuff.startIdx)->param == ping_request) {
|
||||
loraPing.status = stInProcess;
|
||||
loraPing.start = millis();
|
||||
DBG1("LoRa ping request sent to address: 0x" + String(*(spBuff.address + spBuff.startIdx), HEX));
|
||||
}
|
||||
transmitLoRa((spBuff.address + spBuff.startIdx), (spBuff.sp + spBuff.startIdx), 1);
|
||||
BUFFINCSTART(spBuff);
|
||||
}
|
||||
|
||||
|
||||
// It's polite to Listen more than you talk
|
||||
if(TDIFF(lastTxtime,(TXDELAYMS + random(0,50)))) {
|
||||
// Start Transmit data from the DataReading queue
|
||||
if((!ISBUFFEMPTY(drBuff) || (data != NULL)) && loraTxState == stReady && loraAckState == stReady)
|
||||
{
|
||||
// for(int i=drBuff.startIdx; i!=drBuff.endIdx; i = (i + 1) % drBuff.size) {
|
||||
// printf("id: %d, type: %d data: %f address: %X\n",(drBuff.dr + i)->id, (drBuff.dr + i)->t, (drBuff.dr + i)->d, *(spBuff.address + spBuff.startIdx));
|
||||
// }
|
||||
|
||||
// data memory is not freed when retries are exhausted and DataReading queue is not full
|
||||
if(data == NULL) {
|
||||
// Get number of DRs going to same destination address
|
||||
len = transmitSameAddrLoRa();
|
||||
// TransmitLoRa cannot handle a circular buffer so need data in one contiguous segment of memory
|
||||
data = (DataReading *)malloc(len * sizeof(DataReading));
|
||||
// Transfer data readings from the ring buffer to our local buffer
|
||||
for(int i=0; i < len; i++) {
|
||||
*(data + i) = *(drBuff.dr + ((drBuff.startIdx + i) % drBuff.size));
|
||||
}
|
||||
address = *(drBuff.address + drBuff.startIdx);
|
||||
// now we have the data, we can release it from the ring buffer
|
||||
drBuff.startIdx = (drBuff.startIdx + len) % drBuff.size;
|
||||
}
|
||||
DBG2("Length: " + String(len) + " Address: 0x" + String(address,HEX) + " Data:");
|
||||
// for(int i=0; i< len; i++) {
|
||||
// printf("id: %d, type: %d data: %f\n",(data + i)->id, (data + i)->t, (data + i)->d);
|
||||
// }
|
||||
if(!ack && data != NULL)
|
||||
{
|
||||
transmitLoRa(&address, data, len);
|
||||
free(data);
|
||||
data = NULL;
|
||||
len = 0;
|
||||
}
|
||||
else if(data != NULL)
|
||||
{
|
||||
retries--;
|
||||
loraAckState = stInProcess;
|
||||
transmitLoRa(&address, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
lastTxtime = millis();
|
||||
}
|
||||
// Print LoRa statistics
|
||||
if(TDIFFSEC(statsTime,65) && (rxCountDR + rxCountSP) > 0) {
|
||||
statsTime = millis();
|
||||
DBG1("LoRa Stats - Rx DR:" + String(rxCountDR) + " SP:" + String(rxCountSP) + " Tx DR:" + String(txCountDR) + " SP:" + String(txCountSP) + " CRC OK: " + String(rxCountCrcOk/(rxCountDR + rxCountSP) * 100) + "%");
|
||||
}
|
||||
|
||||
// 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(>wyAddress,&sys_packet,1);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if(transmitLoRaAsync(>wyAddress, &sys_packet, 1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
@ -1,525 +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
|
||||
void printLoraPacket(uint8_t *p, int size);
|
||||
|
||||
uint16_t gtwyAddress = ((gatewayAddress[4] << 8) | GTWY_MAC);
|
||||
|
||||
// Function prototypes
|
||||
crcResult getLoRa();
|
||||
|
||||
#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 (calcCRC == packetCRC)
|
||||
{
|
||||
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
|
||||
{ // 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 if (packetCRC == crc16_update(calcCRC, 0xA1))
|
||||
{ // Sender does not want ACK and CRC is valid
|
||||
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
|
||||
{ // 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");
|
||||
}
|
@ -0,0 +1,387 @@
|
||||
#include <sys/time.h>
|
||||
|
||||
#define MIN_TS 1714000000 // Time in Unit timestamp format should be greater than this number to be valid
|
||||
#define MAX_TS 3318000000 // time in Unit timestamp format should be less than this number to be valid
|
||||
#define VALID_TS(_unixts) ( (_unixts > MIN_TS && _unixts < MAX_TS) ? true : false )
|
||||
|
||||
#define TDIFF(prevMs,durationMs) (millis() - prevMs > durationMs)
|
||||
#define TDIFFRAND(prevMs,durationMs) (millis() - prevMs > (durationMs + random(0,10000)))
|
||||
#define TDIFFSEC(prevMs,durationSec) (millis() - prevMs > (durationSec * 1000))
|
||||
#define TDIFFMIN(prevMs,durationMin) (millis() - prevMs > (durationMin * 60 * 1000))
|
||||
|
||||
// select Time, in minutes, between sending out time
|
||||
#if defined(TIME_SEND_INTERVAL)
|
||||
#define FDRS_TIME_SEND_INTERVAL TIME_SEND_INTERVAL
|
||||
#else
|
||||
#define FDRS_TIME_SEND_INTERVAL GLOBAL_TIME_SEND_INTERVAL
|
||||
#endif // TIME_SEND_INTERVAL
|
||||
|
||||
// select Time, in minutes, between time printed configuration
|
||||
#if defined(TIME_PRINTTIME)
|
||||
#define FDRS_TIME_PRINTTIME TIME_PRINTTIME
|
||||
#else
|
||||
#define FDRS_TIME_PRINTTIME GLOBAL_TIME_PRINTTIME
|
||||
#endif // TIME_PRINTTIME
|
||||
|
||||
// select Local Standard time Offset from UTC configuration
|
||||
#if defined(STD_OFFSET)
|
||||
#define FDRS_STD_OFFSET STD_OFFSET
|
||||
#else
|
||||
#define FDRS_STD_OFFSET GLOBAL_STD_OFFSET
|
||||
#endif // STD_OFFSET
|
||||
|
||||
// select Local savings time Offset from UTC configuration
|
||||
#if defined(DST_OFFSET)
|
||||
#define FDRS_DST_OFFSET DST_OFFSET
|
||||
#else
|
||||
#define FDRS_DST_OFFSET GLOBAL_DST_OFFSET
|
||||
#endif // DST_OFFSET
|
||||
|
||||
// US DST Start - 2nd Sunday in March - 02:00 local time
|
||||
// US DST End - 1st Sunday in November - 02:00 local time
|
||||
|
||||
// EU DST Start - last Sunday in March - 01:00 UTC
|
||||
// EU DST End - last Sunday in October - 01:00 UTC
|
||||
|
||||
time_t now; // Current time in UTC - number of seconds since Jan 1 1970 (epoch)
|
||||
struct tm timeinfo; // Structure containing time elements
|
||||
struct timeval tv;
|
||||
bool validTimeFlag = false; // Indicate whether we have reliable time
|
||||
bool validRtcFlag = false; // Is RTC date and time valid?
|
||||
bool isDST; // Keeps track of Daylight Savings Time vs Standard Time
|
||||
long slewSecs = 0; // When time is set this is the number of seconds the time changes
|
||||
double stdOffset = (FDRS_STD_OFFSET * 60 * 60); // UTC -> Local time, in Seconds, offset from UTC in Standard Time
|
||||
double dstOffset = (FDRS_DST_OFFSET * 60 * 60); // -1 hour for DST offset from standard time (in seconds)
|
||||
time_t lastDstCheck = 0;
|
||||
unsigned long lastTimeSend = 0;
|
||||
unsigned long lastRtcCheck = 0;
|
||||
unsigned long lastRtcTimeSetMin = 0;
|
||||
|
||||
// function prototypes
|
||||
void sendTimeLoRa();
|
||||
void printTime();
|
||||
esp_err_t sendTimeESPNow();
|
||||
bool setTime(time_t);
|
||||
|
||||
#ifdef USE_RTC_DS3231
|
||||
#include <RtcDS3231.h>
|
||||
RtcDS3231<TwoWire> rtc(Wire);
|
||||
#elif defined(USE_RTC_DS1307)
|
||||
#include <RtcDS3231.h>
|
||||
RtcDS3231<TwoWire> rtc(Wire);
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTC
|
||||
void begin_rtc() {
|
||||
DBG("Starting RTC");
|
||||
rtc.Begin();
|
||||
|
||||
// Is Date and time valid?
|
||||
if(!rtc.IsDateTimeValid()) {
|
||||
uint8_t err = rtc.LastError();
|
||||
if(err != 0) {
|
||||
// Common Causes:
|
||||
// 1) first time you ran and the device wasn't running yet
|
||||
// 2) the battery on the device is low or even missing
|
||||
|
||||
DBG1("RTC error: Date and Time not valid! Err: " + String(err));
|
||||
validRtcFlag = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
validRtcFlag = true;
|
||||
}
|
||||
// Is RTC running?
|
||||
if(!rtc.GetIsRunning()) {
|
||||
uint8_t err = rtc.LastError();
|
||||
if(err != 0) {
|
||||
DBG1("RTC was not actively running, starting now. Err: " + String(err));
|
||||
rtc.SetIsRunning(true);
|
||||
validRtcFlag = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(validRtcFlag && timeSource.tmSource <= TMS_RTC && validRtcFlag) {
|
||||
if(timeSource.tmSource < TMS_RTC) {
|
||||
timeSource.tmSource = TMS_RTC;
|
||||
timeSource.tmNetIf = TMIF_LOCAL;
|
||||
timeSource.tmAddress = 0xFFFF;
|
||||
DBG1("Time source is now local RTC");
|
||||
}
|
||||
|
||||
// Set date and time on the system
|
||||
DBG1("Using Date and Time from RTC.");
|
||||
if(setTime(rtc.GetDateTime().Unix32Time())) {
|
||||
timeSource.tmLastTimeSet = millis();
|
||||
printTime();
|
||||
}
|
||||
}
|
||||
|
||||
// never assume the Rtc was last configured by you, so
|
||||
// just clear them to your needed state
|
||||
rtc.Enable32kHzPin(false);
|
||||
rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
|
||||
|
||||
lastRtcCheck = millis();
|
||||
}
|
||||
#endif // USE_RTC
|
||||
|
||||
bool validTime() {
|
||||
if(!VALID_TS(now)) {
|
||||
if(validTimeFlag) {
|
||||
DBG1("Time no longer reliable.");
|
||||
validTimeFlag = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if(!validTimeFlag) {
|
||||
validTimeFlag = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void printTime() {
|
||||
if(validTime()) {
|
||||
char strftime_buf[64];
|
||||
|
||||
// UTC Time
|
||||
// // print Unix time:
|
||||
// //DBG2("Unix time = " + String(now));
|
||||
// localtime_r(&now, &timeinfo);
|
||||
// strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
|
||||
// DBG2("The current UTC date/time is: " + String(strftime_buf));
|
||||
|
||||
// Local time
|
||||
time_t local = time(NULL) + (isDST?dstOffset:stdOffset);
|
||||
localtime_r(&local, &timeinfo);
|
||||
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
|
||||
DBG("Local date/time is: " + String(strftime_buf) + (isDST?" DST":" STD"));
|
||||
}
|
||||
}
|
||||
|
||||
void checkDST() {
|
||||
if(validTime() && (TDIFF(lastDstCheck,5000) || lastDstCheck == 0)) {
|
||||
lastDstCheck = millis();
|
||||
int dstFlag = -1;
|
||||
localtime_r(&now, &timeinfo);
|
||||
if(timeinfo.tm_mon == 2) {
|
||||
struct tm dstBegin;
|
||||
dstBegin.tm_year = timeinfo.tm_year;
|
||||
dstBegin.tm_mon = 2;
|
||||
#ifdef USDST
|
||||
dstBegin.tm_mday = 8;
|
||||
dstBegin.tm_hour = 2;
|
||||
dstBegin.tm_min = 0;
|
||||
dstBegin.tm_sec = 0;
|
||||
mktime(&dstBegin); // calculate tm_wday
|
||||
dstBegin.tm_mday = dstBegin.tm_mday + ((7 - dstBegin.tm_wday) % 7);
|
||||
// mktime(&dstBegin); // recalculate tm_wday
|
||||
// strftime(buf, sizeof(buf), "%c", &dstBegin);
|
||||
// DBG2("DST Begins: " + String(buf) + " local");
|
||||
time_t tdstBegin = mktime(&dstBegin) - stdOffset;
|
||||
#endif // USDST
|
||||
#ifdef EUDST
|
||||
dstBegin.tm_mday = 25;
|
||||
dstBegin.tm_hour = 1;
|
||||
dstBegin.tm_min = 0;
|
||||
dstBegin.tm_sec = 0;
|
||||
mktime(&dstBegin); // calculate tm_wday
|
||||
dstBegin.tm_mday = dstBegin.tm_mday + ((7 - dstBegin.tm_wday) % 7);
|
||||
// mktime(&dstBegin); // recalculate tm_wday
|
||||
// strftime(buf, sizeof(buf), "%c", &dstBegin);
|
||||
// DBG2("DST Begins: " + String(buf) + " local");
|
||||
time_t tdstBegin = mktime(&dstBegin);
|
||||
#endif // EUDST
|
||||
if(tdstBegin != -1 && (time(NULL) - tdstBegin >= 0) && isDST == false) { // STD -> DST
|
||||
dstFlag = 1;
|
||||
}
|
||||
else if(tdstBegin != -1 && (time(NULL) - tdstBegin < 0) && isDST == true) { // DST -> STD
|
||||
dstFlag = 0;
|
||||
}
|
||||
}
|
||||
else if(timeinfo.tm_mon == 9) {
|
||||
#ifdef EUDST
|
||||
struct tm dstEnd;
|
||||
dstEnd.tm_year = timeinfo.tm_year;
|
||||
dstEnd.tm_mon = 9;
|
||||
dstEnd.tm_mday = 25;
|
||||
dstEnd.tm_hour = 1;
|
||||
dstEnd.tm_min = 0;
|
||||
dstEnd.tm_sec = 0;
|
||||
mktime(&dstEnd); // calculate tm_dow
|
||||
dstEnd.tm_mday = dstEnd.tm_mday + ((7 - dstEnd.tm_wday) % 7);
|
||||
// mktime(&dstEnd); // recalculate tm_dow
|
||||
// strftime(buf, sizeof(buf), "%c", &dstEnd);
|
||||
// DBG2("DST Ends: " + String(buf) + " local");
|
||||
time_t tdstEnd = mktime(&dstEnd);
|
||||
if(tdstEnd != -1 && (time(NULL) - tdstEnd >= 0) && isDST == true) { // DST -> STD
|
||||
dstFlag = 0;
|
||||
}
|
||||
else if(tdstEnd != -1 && (time(NULL) - tdstEnd < 0) && isDST == false) { // STD -> DST
|
||||
dstFlag = 1;
|
||||
}
|
||||
#endif //EUDST
|
||||
#ifdef USDST
|
||||
if(isDST == false) {
|
||||
dstFlag = 1;
|
||||
}
|
||||
#endif // USDST
|
||||
}
|
||||
else if(timeinfo.tm_mon == 10) {
|
||||
#ifdef USDST
|
||||
struct tm dstEnd;
|
||||
dstEnd.tm_year = timeinfo.tm_year;
|
||||
dstEnd.tm_mon = 10;
|
||||
dstEnd.tm_mday = 1;
|
||||
dstEnd.tm_hour = 2;
|
||||
dstEnd.tm_min = 0;
|
||||
dstEnd.tm_sec = 0;
|
||||
mktime(&dstEnd); // calculate tm_dow
|
||||
dstEnd.tm_mday = dstEnd.tm_mday + ((7 - dstEnd.tm_wday) % 7);
|
||||
// mktime(&dstEnd); // recalculate tm_dow
|
||||
// strftime(buf, sizeof(buf), "%c", &dstEnd);
|
||||
// DBG2("DST Ends: " + String(buf) + " local");
|
||||
time_t tdstEnd = mktime(&dstEnd) - dstOffset;
|
||||
if(tdstEnd != -1 && (time(NULL) - tdstEnd >= 0) && isDST == true) { // DST -> STD
|
||||
dstFlag = 0;
|
||||
}
|
||||
else if(tdstEnd != -1 && (time(NULL) - tdstEnd < 0) && isDST == false) { // STD -> DST
|
||||
dstFlag = 1;
|
||||
}
|
||||
#endif //USDST
|
||||
#ifdef EUDST
|
||||
if(isDST == true) {
|
||||
dstFlag = 0;
|
||||
}
|
||||
#endif // EUDST
|
||||
}
|
||||
else if((timeinfo.tm_mon == 11 || timeinfo.tm_mon == 0 || timeinfo.tm_mon == 1) && isDST == true) {
|
||||
dstFlag = 0;
|
||||
}
|
||||
else if(timeinfo.tm_mon >= 3 && timeinfo.tm_mon <= 8 && isDST == false) {
|
||||
dstFlag = 1;
|
||||
}
|
||||
if(dstFlag == 1) {
|
||||
isDST = true;
|
||||
DBG1("Time change from STD -> DST");
|
||||
}
|
||||
else if(dstFlag == 0) {
|
||||
isDST = false;
|
||||
// Since we are potentially moving back an hour we need to prevent flip flopping back and forth
|
||||
// 2AM -> 1AM, wait 70 minutes -> 2:10AM then start DST checks again.
|
||||
lastDstCheck += ((65-timeinfo.tm_min) * 60); // skip checks until after beginning of next hour
|
||||
DBG1("Time change from DST -> STD");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Periodically send time to ESP-NOW or LoRa nodes associated with this gateway/controller
|
||||
void sendTime() {
|
||||
if(validTime()) { // Only send time if it is valid
|
||||
DBG1("Sending out time");
|
||||
#if defined(USE_WIFI) || defined(USE_ETHERNET)
|
||||
sendTimeSerial();
|
||||
#endif
|
||||
sendTimeLoRa();
|
||||
sendTimeESPNow();
|
||||
}
|
||||
}
|
||||
|
||||
// time parameter is in Unix Time format UTC time zone
|
||||
bool setTime(time_t currentTime) {
|
||||
slewSecs = 0;
|
||||
time_t previousTime = now;
|
||||
|
||||
if(!VALID_TS(currentTime)) {
|
||||
return false;
|
||||
}
|
||||
now = currentTime;
|
||||
slewSecs = now - previousTime;
|
||||
if(slewSecs > 2) {
|
||||
DBG1("Time adjust " + String(slewSecs) + " secs");
|
||||
}
|
||||
|
||||
// time(&now);
|
||||
localtime_r(&now, &timeinfo); // write to timeinfo struct
|
||||
mktime(&timeinfo); // set tm_isdst flag
|
||||
// Check for DST/STD time and adjust accordingly
|
||||
checkDST();
|
||||
tv.tv_sec = now;
|
||||
#if defined(ESP32) || defined(ESP8266) // settimeofday may only work with Espressif chips
|
||||
settimeofday(&tv,NULL); // set the RTC time
|
||||
#endif
|
||||
#ifdef USE_RTC
|
||||
// Only set the RTC time every 60 minutes in order to prevent flash wear
|
||||
if(TDIFFMIN(lastRtcTimeSetMin,60)) {
|
||||
RtcDateTime rtcNow;
|
||||
rtcNow.InitWithUnix32Time(now);
|
||||
rtc.SetDateTime(rtcNow);
|
||||
}
|
||||
#endif
|
||||
// Uncomment below to send time and slew rate to the MQTT server
|
||||
// loadFDRS(now, TIME_T, 111);
|
||||
// loadFDRS(slewSecs, TIME_T, 111);
|
||||
// Do not call sendFDRS here. It will not work for some reason.
|
||||
if(validTime()) {
|
||||
if(FDRS_TIME_SEND_INTERVAL == 0 && (TDIFF(lastTimeSend,5000) || lastTimeSend == 0)) { // avoid sending twice on start with RTC and WiFi
|
||||
sendTime();
|
||||
lastTimeSend = millis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleTime() {
|
||||
static unsigned long lastUpdate = 0;
|
||||
|
||||
if(TDIFF(lastUpdate,500)) {
|
||||
time(&now);
|
||||
localtime_r(&now, &timeinfo);
|
||||
tv.tv_sec = now;
|
||||
tv.tv_usec = 0;
|
||||
validTime();
|
||||
checkDST();
|
||||
lastUpdate = millis();
|
||||
}
|
||||
|
||||
#ifdef USE_RTC
|
||||
// If RTC was not running or had the incorrect date on startup recheck periodically.
|
||||
if(!validRtcFlag && (TDIFFMIN(lastRtcCheck,60))) {
|
||||
begin_rtc();
|
||||
lastRtcCheck = millis();
|
||||
}
|
||||
#endif // USE_RTC
|
||||
|
||||
// Send out time to other devices if we have exceeded the time send interval
|
||||
if(validTimeFlag && (FDRS_TIME_SEND_INTERVAL != 0) && TDIFFMIN(lastTimeSend,FDRS_TIME_SEND_INTERVAL)) {
|
||||
lastTimeSend = millis();
|
||||
sendTime();
|
||||
}
|
||||
if(timeSource.tmNetIf < TMIF_LOCAL && TDIFFMIN(timeSource.tmLastTimeSet,120)) { // Reset time source to default if not heard anything for two hours
|
||||
timeSource.tmNetIf = TMIF_NONE;
|
||||
timeSource.tmAddress = 0x0000;
|
||||
timeSource.tmLastTimeSet = millis();
|
||||
timeSource.tmSource = TMS_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
void adjTimeforNetDelay(time_t newOffset) {
|
||||
static time_t previousOffset = 0;
|
||||
|
||||
// check to see if offset and current time are valid
|
||||
if(newOffset < UINT32_MAX && validTime()) {
|
||||
now = now + newOffset - previousOffset;
|
||||
previousOffset = newOffset;
|
||||
if(newOffset > 2) {
|
||||
DBG1("Time adj by " + String(newOffset) + " secs");
|
||||
}
|
||||
}
|
||||
if(timeSource.tmSource == TMS_NET && newOffset > 10) {
|
||||
DBG("Time off by more than 10 seconds!");
|
||||
// loadFDRS();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue