Merge pull request #193 from aviateur17/timekeeping_202402

FDRS Support for timekeeping - 2024 version
main
Timm Bogner 2 weeks ago committed by GitHub
commit 6b7134fb51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (ESP32 only)
//#define RXD2 14
//#define TXD2 15
@ -78,5 +86,12 @@
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (ESP32 only)
//#define RXD2 14
//#define TXD2 15
@ -78,5 +86,12 @@
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (ESP32 only)
//#define RXD2 14
//#define TXD2 15
@ -78,5 +86,12 @@
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (ESP32 only)
//#define RXD2 14
//#define TXD2 15
@ -78,5 +86,12 @@
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -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

@ -15,11 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -15,11 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
/// 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

@ -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

@ -15,11 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -15,13 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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 LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
//#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

@ -15,13 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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 LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
//#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

@ -12,15 +12,38 @@
//#define DEEP_SLEEP
//#define POWER_CTRL 14
// #define FDRS_DEBUG // Enable USB-Serial debugging
//#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (ESP32 only)
//#define RXD2 14
//#define TXD2 15
@ -79,5 +87,12 @@
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -46,14 +46,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
// 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_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// RTC - I2C
// #define USE_RTC_DS3231
// #define RTC_ADDR 0x57
// #define USE_RTC_DS1307
// #define RTC_ADDR 0x68
// UART data interface pins (if available)
#define RXD2 14
#define TXD2 15
@ -69,3 +77,13 @@
//#define MQTT_AUTH //Enable MQTT authentication
//#define MQTT_USER "Your MQTT Username"
//#define MQTT_PASS "Your MQTT Password"
// NTP Time settings
#define USDST
// #define EUDST
#define TIME_SERVER "0.us.pool.ntp.org" // NTP time server to use. If FQDN at least one DNS server is required to resolve name
#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_FETCHNTP 60 // Time, in minutes, between fetching time from NTP server
#define TIME_PRINTTIME 15 // Time, in minutes, between printing local time to debug
#define TIME_SEND_INTERVAL 10 // Time, in minutes, between sending out time to remote devices

@ -14,13 +14,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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 LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
//#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

@ -15,18 +15,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
/// 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
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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 LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
#define LORA_ACK // Request LoRa acknowledgment.
//#define CUSTOM_SPI
#define LORA_SPI_SCK 5
#define LORA_SPI_SCK 5
#define LORA_SPI_MISO 19
#define LORA_SPI_MOSI 27
#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

@ -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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -17,11 +17,21 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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 // ESP32 SX1276 (TTGO)
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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
@ -33,8 +43,9 @@
#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_OLED
#define OLED_HEADER "FDRS"
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -10,15 +10,38 @@
//#define USE_LORA
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
#define POWER_CTRL 22
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,21 +11,38 @@
//#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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 // ESP32 SX1276 (TTGO)
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO 26
#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.
#define USE_OLED
#define OLED_HEADER "FDRS"
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
#define POWER_CTRL 4
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -14,11 +14,35 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,39 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -11,14 +11,38 @@
#define DEEP_SLEEP
//#define POWER_CTRL 14
#define FDRS_DEBUG // Enable USB-Serial debugging
#define FDRS_DEBUG
//#define DBG_LEVEL 0 // 0 for minimal messaging, 1 for troubleshooting, 2 for development
// 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

@ -26,13 +26,28 @@ enum crcResult {
CRC_BAD,
} returnCRC;
enum {
enum cmd_t {
cmd_clear,
cmd_ping,
cmd_add,
cmd_ack,
cmd_time
};
enum ping_t {
ping_request,
ping_reply
};
enum pingstate_t {
stReady,
stInProcess,
stCrcMismatch,
stCrcMatch,
stCompleted
};
enum
{
event_clear,
@ -46,6 +61,61 @@ enum
event_lora2,
event_internal
};
// Interface type that is the time source
enum TmNetIf {
TMIF_NONE,
TMIF_LORA,
TMIF_ESPNOW,
TMIF_SERIAL,
TMIF_LOCAL,
};
// Local time source that is setting the time
enum TmSource {
TMS_NONE,
TMS_NET,
TMS_RTC,
TMS_NTP,
TMS_GPS,
};
struct TimeSource {
TmNetIf tmNetIf;
uint16_t tmAddress;
TmSource tmSource;
unsigned long tmLastTimeSet;
};
struct DRRingBuffer {
DataReading *dr;
uint16_t *address;
uint startIdx;
uint endIdx;
uint size;
};
struct SPRingBuffer {
SystemPacket *sp;
uint16_t *address;
uint startIdx;
uint endIdx;
uint size;
};
struct Ping {
pingstate_t status = stReady;
unsigned long start;
uint timeout;
uint16_t address;
uint32_t response = __UINT32_MAX__;
};
#ifndef ESP32
typedef int esp_err_t;
#define ESP_FAIL 0
#define ESP_OK 1
#endif
#ifndef FDRS_DATA_TYPES
#define FDRS_DATA_TYPES

@ -42,6 +42,7 @@
SystemPacket theCmd;
DataReading theData[256];
TimeSource timeSource;
uint8_t ln;
uint8_t newData = event_clear;
uint8_t newCmd = cmd_clear;
@ -53,54 +54,80 @@ uint8_t data_count = 0;
void broadcastLoRa();
void sendLoRaNbr(uint8_t);
void timeFDRSLoRa(uint8_t *);
static uint16_t crc16_update(uint16_t, uint8_t);
//static uint16_t crc16_update(uint16_t, uint8_t);
void sendESPNowNbr(uint8_t);
void sendESPNowPeers();
void sendESPNow(uint8_t);
void sendTimeSerial();
void handleLoRa();
void handleMQTT();
void handleOTA();
void sendMQTT();
void sendLog();
void resendLog();
void releaseLogBuffer();
void printFDRS(DataReading*, int);
#ifdef USE_I2C
#include <Wire.h>
#endif
#ifdef USE_OLED
#include "fdrs_oled.h"
#endif
#include "fdrs_debug.h"
#include "fdrs_time.h"
#include "fdrs_gateway_serial.h"
#include "fdrs_gateway_scheduler.h"
#ifdef USE_ESPNOW
#include "fdrs_gateway_espnow.h"
#endif
#ifdef USE_LORA
#include "fdrs_gateway_lora.h"
#include "fdrs_lora.h"
#endif
#ifdef USE_WIFI
#include "fdrs_gateway_wifi.h"
#include "fdrs_gateway_mqtt.h"
#include "fdrs_gateway_ota.h"
#include "fdrs_gateway_ota.h"
#endif
#ifdef DEBUG_CONFIG
#include "fdrs_checkConfig.h"
#endif
// Print type DataReading for debugging purposes
void printFDRS(DataReading * dr, int len) {
DBG("----- printFDRS: " + String(len) + " records -----");
for(int i = 0; i < len; i++) {
DBG("Index: " + String(i) + "| id: " + String(dr[i].id) + "| type: " + String(dr[i].t) + "| data: " + String(dr[i].d));
}
DBG("----- End printFDRS -----");
}
void sendFDRS()
{
for (int i = 0; i < data_count; i++)
{
theData[i].id = fdrsData[i].id;
theData[i].t = fdrsData[i].t;
theData[i].d = fdrsData[i].d;
if(data_count > 0) {
for (int i = 0; i < data_count; i++)
{
theData[i].id = fdrsData[i].id;
theData[i].t = fdrsData[i].t;
theData[i].d = fdrsData[i].d;
}
ln = data_count;
data_count = 0;
newData = event_internal;
DBG("Entered internal data.");
}
ln = data_count;
data_count = 0;
newData = event_internal;
DBG("Entered internal data.");
}
void loadFDRS(float d, uint8_t t, uint16_t id)
{
// guard against buffer overflow
if(data_count > 253) {
sendFDRS();
}
DBG("Id: " + String(id) + " - Type: " + String(t) + " - Data loaded: " + String(d));
DataReading dr;
dr.id = id;
@ -118,6 +145,15 @@ void beginFDRS()
Serial.begin(115200);
UART_IF.begin(115200, SERIAL_8N1, RXD2, TXD2);
#endif
#ifdef USE_GPS
begin_gps();
#endif
#ifdef USE_I2C
Wire.begin(I2C_SDA, I2C_SCL);
#endif
#ifdef USE_RTC
begin_rtc();
#endif
#ifdef USE_OLED
init_oled();
@ -139,12 +175,12 @@ void beginFDRS()
#ifdef USE_LORA
begin_lora();
scheduleFDRS(asyncReleaseLoRaFirst, FDRS_LORA_INTERVAL);
#endif
#endif
#ifdef USE_WIFI
begin_wifi();
DBG("Connected.");
begin_mqtt();
begin_ntp();
begin_OTA();
#endif
#ifdef USE_ESPNOW
@ -154,7 +190,9 @@ void beginFDRS()
#ifdef USE_WIFI
client.publish(TOPIC_STATUS, "FDRS initialized");
scheduleFDRS(fetchNtpTime,1000*60*FDRS_TIME_FETCHNTP);
#endif
scheduleFDRS(printTime,1000*60*FDRS_TIME_PRINTTIME);
}
void handleCommands()
@ -174,26 +212,26 @@ void handleCommands()
#endif // USE_ESPNOW
break;
case cmd_time:
if(theCmd.param > MIN_TS) {
#ifdef USE_ESPNOW
recvTimeEspNow(theCmd.param);
#endif // USE_ESPNOW
}
else if(theCmd.param == 0) {
#ifdef USE_ESPNOW
sendTimeESPNow(incMAC);
#endif // USE_ESPNOW
}
break;
}
theCmd.cmd = cmd_clear;
theCmd.param = 0;
}
void loopFDRS()
{
handle_schedule();
handleCommands();
handleSerial();
#ifdef USE_LORA
handleLoRa();
#endif
#ifdef USE_WIFI
handleMQTT();
handleOTA();
#endif
#ifdef USE_OLED
drawPageOLED(true);
#endif
void handleActions() {
if (newData != event_clear)
{
switch (newData)
@ -230,17 +268,39 @@ void loopFDRS()
}
}
void loopFDRS()
{
handleTime();
handle_schedule();
handleCommands();
handleSerial();
handleLoRa();
handleMQTT();
handleOTA();
#ifdef USE_OLED
drawPageOLED(true);
#endif
handleActions();
}
// "Skeleton Functions related to FDRS Actions"
#ifndef USE_LORA
void broadcastLoRa() {}
void sendLoRaNbr(uint8_t address) {}
void timeFDRSLoRa(uint8_t *address) {}
void timeFDRSLoRa(uint8_t *address) {} // fdrs_gateway_lora.h
void sendTimeLoRa() { return; } // fdrs_gateway_time.h
void handleLoRa() { return; } // fdrs_gateway_lora.h
bool pingLoRaTimeSource() { return false; } //fdrs_gateway_lora.h
#endif
#ifndef USE_ESPNOW
void sendESPNowNbr(uint8_t interface) {}
void sendESPNowPeers() {}
void sendESPNow(uint8_t address) {}
void sendESPNowNbr(uint8_t interface) { }
void sendESPNowPeers() { }
void sendESPnow(uint8_t address) { }
esp_err_t sendTimeESPNow() { return ESP_OK; } // fdrs_gateway_time.h
#endif
#ifndef USE_WIFI
void sendMQTT() {}
void handleMQTT() {}
void handleOTA() {}
#endif

@ -14,6 +14,7 @@ const uint8_t espnow_size = 250 / sizeof(DataReading);
#ifdef ESP32
esp_now_peer_info_t peerInfo;
#endif
bool esp_now_sent_flag;
const uint8_t broadcast_mac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
@ -23,20 +24,22 @@ uint8_t incMAC[6];
uint8_t ESPNOW1[] = {MAC_PREFIX, ESPNOW_NEIGHBOR_1};
uint8_t ESPNOW2[] = {MAC_PREFIX, ESPNOW_NEIGHBOR_2};
extern time_t now;
// JL - pingFlagEspNow var probably to be removed
bool pingFlagEspNow = false;
// Set ESP-NOW send and receive callbacks for either ESP8266 or ESP32
#if defined(ESP8266)
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus)
{
esp_now_sent_flag = true;
esp_now_sent_flag = true;
}
void OnDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len)
{
#elif defined(ESP32)
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
esp_now_sent_flag = true;
esp_now_sent_flag = true;
}
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len)
{
@ -44,25 +47,27 @@ void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len)
memcpy(&incMAC, mac, sizeof(incMAC));
if (len < sizeof(DataReading))
{
DBG1("ESP-NOW System Packet");
DBG1("Incoming ESP-NOW System Packet from 0x" + String(incMAC[5], HEX));
memcpy(&theCmd, incomingData, sizeof(theCmd));
memcpy(&incMAC, mac, sizeof(incMAC));
return;
}
memcpy(&theData, incomingData, sizeof(theData));
DBG("Incoming ESP-NOW.");
ln = len / sizeof(DataReading);
if (memcmp(&incMAC, &ESPNOW1, 6) == 0)
{
newData = event_espnow1;
// processing is handled in the handlecommands() function in gateway.h - do not process here
return;
}
if (memcmp(&incMAC, &ESPNOW2, 6) == 0)
{
newData = event_espnow2;
return;
else {
memcpy(&theData, incomingData, sizeof(theData));
DBG("Incoming ESP-NOW DataReading from 0x" + String(incMAC[5], HEX));
ln = len / sizeof(DataReading);
if (memcmp(&incMAC, &ESPNOW1, 6) == 0)
{
newData = event_espnow1;
return;
}
if (memcmp(&incMAC, &ESPNOW2, 6) == 0)
{
newData = event_espnow2;
return;
}
newData = event_espnowg;
}
newData = event_espnowg;
}
void begin_espnow()
@ -125,7 +130,7 @@ int find_espnow_peer()
}
for (int i = 0; i < 16; i++)
{
if ((millis() - peer_list[i].last_seen) >= PEER_TIMEOUT)
if (TDIFF(peer_list[i].last_seen,PEER_TIMEOUT))
{
DBG1("Recycling peer entry " + String(i));
esp_now_del_peer(peer_list[i].mac);
@ -192,14 +197,18 @@ void add_espnow_peer()
SystemPacket sys_packet = {.cmd = cmd_add, .param = PEER_TIMEOUT};
esp_now_send(incMAC, (uint8_t *)&sys_packet, sizeof(SystemPacket));
}
if(validTimeFlag){
SystemPacket sys_packet = { .cmd = cmd_time, .param = now };
esp_now_send(incMAC, (uint8_t *)&sys_packet, sizeof(SystemPacket));
}
}
// Sends ping reply to sender
void pingback_espnow()
{
DBG("Ping back to sender");
DBG("Sending ESP-NOW Ping Reply");
SystemPacket sys_packet;
sys_packet.cmd = cmd_ping;
sys_packet = { .cmd = cmd_ping, .param = ping_reply };
if (!esp_now_is_peer_exist(incMAC))
{
#ifdef ESP8266
@ -232,7 +241,7 @@ void sendESPNowNbr(uint8_t interface)
{
case 1:
{ // These brackets are required!
DBG("Sending to ESP-NOW Neighbor #1");
DBG("Sending DR to ESP-NOW Neighbor #1");
#if defined(ESP32)
esp_now_peer_info_t peerInfo;
peerInfo.ifidx = WIFI_IF_STA;
@ -265,7 +274,7 @@ void sendESPNowNbr(uint8_t interface)
} // These brackets are required!
case 2:
{
DBG("Sending to ESP-NOW Neighbor #2");
DBG("Sending DR to ESP-NOW Neighbor #2");
#if defined(ESP32)
esp_now_peer_info_t peerInfo;
peerInfo.ifidx = WIFI_IF_STA;
@ -298,11 +307,9 @@ void sendESPNowNbr(uint8_t interface)
}
}
void sendESPNowPeers()
{
DBG("Sending to ESP-NOW peers.");
DBG("Sending DR to ESP-NOW peers.");
DataReading thePacket[ln];
int j = 0;
for (int i = 0; i < ln; i++)
@ -326,17 +333,113 @@ void sendESPNowPeers()
//DBG(millis() - clktm);
}
}
}
// Lower level function meant to be called by other functions
// Sends SystemPacket via ESP-NOW
esp_err_t sendESPNow(uint8_t *dest, SystemPacket *data) {
esp_err_t sendResult;
if (dest != nullptr && !esp_now_is_peer_exist(dest))
{
#ifdef ESP8266
sendResult = esp_now_add_peer(dest, ESP_NOW_ROLE_COMBO, 0, NULL, 0);
if (sendResult != ESP_OK)
{
DBG("Failed to add peer");
return sendResult;
}
#endif
#if defined(ESP32)
esp_now_peer_info_t peerInfo;
peerInfo.ifidx = WIFI_IF_STA;
peerInfo.channel = 0;
peerInfo.encrypt = false;
memcpy(peerInfo.peer_addr, dest, 6);
sendResult = esp_now_add_peer(&peerInfo);
if (sendResult != ESP_OK)
{
DBG("Failed to add peer");
return sendResult;
}
#endif
sendResult = esp_now_send(dest, (uint8_t *)data, sizeof(SystemPacket));
esp_now_del_peer(dest);
}
else
{
sendResult = esp_now_send(dest, (uint8_t *)data, sizeof(SystemPacket));
}
return sendResult;
}
// Lower level function meant to be called by other functions
// Sends DataReading via ESP-NOW
esp_err_t sendESPNow(uint8_t *dest, DataReading *data) {
esp_err_t sendResult;
bool tempPeerFlag = false;
if (dest != nullptr && !esp_now_is_peer_exist(dest))
{
tempPeerFlag = true;
#ifdef ESP8266
sendResult = esp_now_add_peer(dest, ESP_NOW_ROLE_COMBO, 0, NULL, 0);
if (sendResult != ESP_OK)
{
DBG("Failed to add peer");
return sendResult;
}
}
#endif
#if defined(ESP32)
esp_now_peer_info_t peerInfo;
peerInfo.ifidx = WIFI_IF_STA;
peerInfo.channel = 0;
peerInfo.encrypt = false;
memcpy(peerInfo.peer_addr, dest, 6);
sendResult = esp_now_add_peer(&peerInfo);
if (sendResult != ESP_OK)
{
DBG("Failed to add peer");
return sendResult;
}
}
#endif // ESP32
for(int i = 0; i < ln; ) {
if(ln > espnow_size) {
sendResult = esp_now_send(dest, (uint8_t *)&data[i], espnow_size * sizeof(DataReading));
if(sendResult == ESP_OK) {
i += espnow_size;
}
else {
// Send failed!
delay(10);
return sendResult;
}
}
else {
sendResult = esp_now_send(dest, (uint8_t *)&data[i], ln * sizeof(DataReading));
if(sendResult == ESP_OK) {
ln = 0;
}
else {
// Send Failed!
delay(10);
return sendResult;
}
}
}
if(tempPeerFlag) {
esp_now_del_peer(dest);
}
return sendResult;
}
void sendESPNow(uint8_t address)
{
DBG("Sending ESP-NOW.");
DBG("Sending ESP-NOW DR.");
uint8_t temp_peer[] = {MAC_PREFIX, address};
#if defined(ESP32)
esp_now_peer_info_t peerInfo;
@ -366,4 +469,62 @@ void sendESPNow(uint8_t address)
esp_now_send(temp_peer, (uint8_t *)&thePacket, j * sizeof(DataReading));
esp_now_del_peer(temp_peer);
}
void recvTimeEspNow(uint32_t t) {
// Process time if there is no time source set yet or if LoRa is the time source or if we are already the time source
if(timeSource.tmNetIf <= TMIF_ESPNOW ) {
DBG1("Received time via ESP-NOW from 0x" + String(incMAC[5], HEX));
if(timeSource.tmNetIf < TMIF_ESPNOW) {
timeSource.tmNetIf = TMIF_ESPNOW;
timeSource.tmAddress = incMAC[4] << 8 | incMAC[5];
timeSource.tmSource = TMS_NET;
DBG1("ESP-NOW time source is 0x" + String(incMAC[5], HEX));
}
if(timeSource.tmAddress == incMAC[4] << 8 | incMAC[5]) {
if(setTime(t)) {
timeSource.tmLastTimeSet = millis();
}
}
}
else {
DBG2("ESP-NOW 0x" + String(incMAC[5], HEX) + " is not time source, discarding request");
}
return;
}
// Sends time to both neighbors and all peers
esp_err_t sendTimeESPNow() {
esp_err_t result1 = ESP_OK, result2 = ESP_OK, result3 = ESP_OK;
SystemPacket sys_packet = { .cmd = cmd_time, .param = now };
if((timeSource.tmAddress != (ESPNOW1[4] << 8 | ESPNOW1[5])) && ESPNOW1[5] != 0x00) {
DBG1("Sending time to ESP-NOW Peer 1");
result1 = sendESPNow(ESPNOW1, &sys_packet);
}
if((timeSource.tmAddress != (ESPNOW2[4] << 8 | ESPNOW2[5])) && ESPNOW2[5] != 0x00) {
DBG1("Sending time to ESP-NOW Peer 2");
result2 = sendESPNow(ESPNOW2, &sys_packet);
}
DBG1("Sending time to ESP-NOW registered peers");
result3 = sendESPNow(nullptr, &sys_packet);
if(result1 != ESP_OK || result2 != ESP_OK || result3 != ESP_OK){
return ESP_FAIL;
}
else {
return ESP_OK;
}
}
// Send the time to a specific node
esp_err_t sendTimeESPNow(uint8_t *addr) {
esp_err_t result = ESP_FAIL;
SystemPacket sys_packet = { .cmd = cmd_time, .param = now };
DBG1("Sending time to ESP-NOW address 0x" + String(addr[5],HEX));
result = sendESPNow(addr, &sys_packet);
return result;
}

@ -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;
}

@ -44,6 +44,7 @@
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMqttConnectAttempt = 0;
const char *mqtt_server = FDRS_MQTT_ADDR;
const int mqtt_port = FDRS_MQTT_PORT;
@ -102,7 +103,10 @@ void handleMQTT()
{
if (!client.connected())
{
reconnect_mqtt(1, true);
if(TDIFF(lastMqttConnectAttempt,5000)) {
reconnect_mqtt(1, true);
lastMqttConnectAttempt = millis();
}
}
client.loop(); // for recieving incoming messages and maintaining connection
}
@ -119,8 +123,8 @@ void mqtt_callback(char *topic, byte *message, unsigned int length)
DeserializationError error = deserializeJson(doc, incomingString);
if (error)
{ // Test if parsing succeeds.
DBG("json parse err");
DBG1(incomingString);
DBG2("json parse err");
DBG2(incomingString);
return;
}
else

@ -1,9 +1,17 @@
#include <ArduinoJson.h>
#if defined (ESP32)
#define UART_IF Serial1
#define UART_IF Serial1
#ifdef USE_GPS
#define GPS_IF Serial2
#endif
#elif defined (ESP8266)
#define UART_IF Serial
#else
#define UART_IF Serial
#define UART_IF Serial
#ifdef USE_GPS
#define GPS_IF Serial1
#endif
#endif
#if defined(ESP32)
@ -24,22 +32,136 @@
#endif
#endif
extern time_t now;
bool gpsParse(String input) {
String _time, _date;
int pos;
struct tm gpsDateTime;
static unsigned long lastGpsTimeSet = 0;
if(timeSource.tmSource != TMS_GPS) {
timeSource.tmSource = TMS_GPS;
timeSource.tmAddress = 0xFFFF;
timeSource.tmNetIf = TMIF_LOCAL;
DBG1("Time source is now local GPS.");
}
// Prints out all incoming GPS data
// DBG2("GPS incoming: " + input);
// $GNZDA $GNZDA,154230.000,11,02,2024,00,00*4F
// $GNRMC $GNRMC,154230.000,A,,,,,,,110224,,,A,V*19
if(input.startsWith("$GNZDA") && input.length() >= 38) {
// DBG2("GPS String: " + input + " Length: " + String(input.length()));
pos = input.indexOf(",");
_time = input.substring(pos + 1,pos + 7);
// DBG2("GPS Time: " + _time + " ($GNZDA)");
pos = input.indexOf(",",pos + 5);
_date = input.substring(pos,pos + 11);
_date.replace(",","");
// DBG2("GPS Date: " + _date + " ($GNZDA)");
// Set GPS time every 10 minutes
if(lastGpsTimeSet == 0 || TDIFFMIN(lastGpsTimeSet,10)) {
lastGpsTimeSet = millis();
pos = 0;
gpsDateTime.tm_hour = _time.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_min = _time.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_sec = _time.substring(pos,pos+2).toInt();
pos = 0;
gpsDateTime.tm_mday = _date.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_mon = _date.substring(pos,pos+2).toInt() - 1;
pos+=2;
gpsDateTime.tm_year = _date.substring(pos,pos+4).toInt() - 1900;
DBG2("GPS Date & Time: " + String(gpsDateTime.tm_mon + 1) + "/" + String(gpsDateTime.tm_mday) + "/" + String(gpsDateTime.tm_year + 1900) + " " \
+ String(gpsDateTime.tm_hour) + ":" + String(gpsDateTime.tm_min) + ":" + String(gpsDateTime.tm_sec) + " UTC");
DBG1("Setting date and time via GPS: " + String(mktime(&gpsDateTime)) + " $GNZDA");
if(setTime(mktime(&gpsDateTime))) {
timeSource.tmLastTimeSet = millis();
return true;
}
}
}
else if(input.startsWith("$GNRMC") && input.length() >= 38) {
// DBG2("GPS String: " + input + " Length: " + String(input.length()));
pos = input.indexOf(",");
_time = input.substring(pos + 1,pos + 7);
// DBG2("GPS Time: " + _time + " ($GNRMC)");
for(int i = 0 ; i < 8; i++) {
pos = input.indexOf(",",pos + 1);
}
_date = input.substring(pos,pos + 9);
_date.replace(",","");
// DBG2("GPS Date: " + _date + " ($GNRMC)");
if(lastGpsTimeSet == 0 || TDIFFMIN(lastGpsTimeSet,10)) {
lastGpsTimeSet = millis();
pos = 0;
gpsDateTime.tm_hour = _time.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_min = _time.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_sec = _time.substring(pos,pos+2).toInt();
pos = 0;
gpsDateTime.tm_mday = _date.substring(pos,pos+2).toInt();
pos+=2;
gpsDateTime.tm_mon = _date.substring(pos,pos+2).toInt() - 1;
pos+=2;
gpsDateTime.tm_year = 2000 + _date.substring(pos,pos+2).toInt() - 1900;
DBG2("GPS Date & Time: " + String(gpsDateTime.tm_mon + 1) + "/" + String(gpsDateTime.tm_mday) + "/" + String(gpsDateTime.tm_year + 1900) + " " \
+ String(gpsDateTime.tm_hour) + ":" + String(gpsDateTime.tm_min) + ":" + String(gpsDateTime.tm_sec) + " UTC");
DBG1("Setting date and time via GPS: " + String(mktime(&gpsDateTime)) + " $GNRMC");
if(setTime(mktime(&gpsDateTime))) {
timeSource.tmLastTimeSet = millis();
return true;
}
}
}
return false;
}
void getSerial() {
String incomingString;
if (UART_IF.available()){
incomingString = UART_IF.readStringUntil('\n');
}
else if (Serial.available()){
incomingString = Serial.readStringUntil('\n');
}
#ifdef GPS_IF
if (GPS_IF.available()){
// Data is coming in every second from the GPS, let's minimize the processing power
// required by only parsing periodically - maybe every 60 seconds.
static unsigned long lastGpsParse = 0;
if(lastGpsParse == 0 || TDIFFSEC(lastGpsParse,60)) {
lastGpsParse = millis();
for(int i=0; i < 20; i++) {
incomingString = GPS_IF.readStringUntil('\n');
if(gpsParse(incomingString)) {
return;
}
}
}
return;
}
#endif // GPS_IF
JsonDocument doc;
DeserializationError error = deserializeJson(doc, incomingString);
if (error) { // Test if parsing succeeds.
DBG("json parse err");
DBG1(incomingString);
DBG2("json parse err");
DBG2(incomingString);
return;
} else {
int s = doc.size();
JsonObject obj = doc[0].as<JsonObject>();
if(obj.containsKey("type")) { // DataReading
//UART_IF.println(s);
for (int i = 0; i < s; i++) {
theData[i].id = doc[i]["id"];
@ -48,19 +170,62 @@ void getSerial() {
}
ln = s;
newData = event_serial;
DBG("Incoming Serial.");
DBG("Incoming Serial");
String data;
serializeJson(doc, data);
DBG1("DR data: " + data);
}
else if(obj.containsKey("cmd")) { // SystemPacket
cmd_t c = doc[0]["cmd"];
uint32_t p = doc[0]["param"];
if(c == cmd_time && p > MIN_TS) {
if(timeSource.tmNetIf < TMIF_SERIAL) {
timeSource.tmNetIf = TMIF_SERIAL;
timeSource.tmSource = TMS_NET;
timeSource.tmAddress = 0xFFFF;
DBG1("Time source is now Serial peer");
}
if(timeSource.tmNetIf == TMIF_SERIAL) {
DBG1("Incoming Serial: time");
if(setTime(doc[0]["param"])) {
timeSource.tmLastTimeSet = millis();
}
else {
// Set time failed for some reason
}
}
else {
// There is a local time source so we do not accept serial
DBG2("Did not set time from incoming serial.");
}
}
else if(c == cmd_time && p == 0) {
// Received a request for us to send time via serial -- not implemented yet.
}
else {
DBG2("Incoming Serial: unknown cmd: " + String(c));
}
}
else { // Who Knows???
DBG2("Incoming Serial: unknown: " + incomingString);
}
}
}
void sendSerial() {
DBG("Sending Serial.");
JsonDocument doc;
for (int i = 0; i < ln; i++) {
doc[i]["id"] = theData[i].id;
doc[i]["type"] = theData[i].t;
doc[i]["data"] = theData[i].d;
}
DBG("Sending Serial.");
// String data;
// serializeJson(doc, data);
// DBG("Serial data: " + data);
serializeJson(doc, UART_IF);
UART_IF.println();
@ -71,8 +236,39 @@ void sendSerial() {
}
void handleSerial(){
#ifdef GPS_IF
while (UART_IF.available() || Serial.available() || GPS_IF.available())
#else
while (UART_IF.available() || Serial.available())
#endif
{
getSerial();
}
}
void sendTimeSerial() {
JsonDocument SysPacket;
SysPacket[0]["cmd"] = cmd_time;
SysPacket[0]["param"] = now;
serializeJson(SysPacket, UART_IF);
UART_IF.println();
DBG("Sending Time via Serial.");
// String serialData;
// DBG2("Serial data: " + serializeJson(SysPacket, serialData));
#ifndef ESP8266
// serializeJson(SysPacket, Serial);
// Serial.println();
#endif
}
void begin_gps() {
#ifdef GPS_IF
#ifdef ARDUINO_ARCH_SAMD
GPS_IF.begin(9600);
#else
GPS_IF.begin(9600, SERIAL_8N1, GPS_RXD, GPS_TXD);
#endif
#endif
}

@ -70,8 +70,38 @@
#else
#endif // DNS2_IPADDRESS
// select NTP Time Server configuration
#if defined(TIME_SERVER)
#define FDRS_TIME_SERVER TIME_SERVER
#else
#define FDRS_TIME_SERVER GLOBAL_TIME_SERVER
#endif // TIME_SERVER
// select Time, in minutes, between NTP time server queries configuration
#if defined(TIME_FETCHNTP)
#define FDRS_TIME_FETCHNTP TIME_FETCHNTP
#else
#define FDRS_TIME_FETCHNTP GLOBAL_TIME_FETCHNTP
#endif // TIME_FETCHNTP
WiFiUDP FDRSNtp;
unsigned int localPort = 8888; // local port to listen for UDP packets
const char timeServer[] = FDRS_TIME_SERVER; // NTP server
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
uint NTPFetchFail = 0; // consecutive NTP fetch failures
extern time_t now;
const char *ssid = FDRS_WIFI_SSID;
const char *password = FDRS_WIFI_PASS;
#ifdef USE_STATIC_IPADDRESS
uint8_t hostIpAddress[4], gatewayAddress[4], subnetAddress[4], dns1Address[4], dns2Address[4];
#endif
#ifdef USE_ETHERNET
static bool eth_connected = false;
void WiFiEvent(WiFiEvent_t event)
{
switch (event) {
@ -110,12 +140,6 @@ void WiFiEvent(WiFiEvent_t event)
}
#endif // USE_ETHERNET
const char *ssid = FDRS_WIFI_SSID;
const char *password = FDRS_WIFI_PASS;
#ifdef USE_STATIC_IPADDRESS
uint8_t hostIpAddress[4], gatewayAddress[4], subnetAddress[4], dns2Address[4];
#endif
uint8_t dns1Address[4];
// Convert IP Addresses from strings to byte arrays of 4 bytes
void stringToByteArray(const char* str, char sep, byte* bytes, int maxBytes, int base) {
@ -151,11 +175,103 @@ void begin_wifi()
WiFi.config(hostIpAddress, gatewayAddress, subnetAddress, dns1Address, dns2Address);
#endif
WiFi.begin(ssid, password);
int connectTries = 0;
while (WiFi.status() != WL_CONNECTED)
{
DBG("Connecting to WiFi...");
DBG(FDRS_WIFI_SSID);
delay(500);
connectTries++;
DBG("Connecting to WiFi SSID: " + String(FDRS_WIFI_SSID) + " try number " + String(connectTries));
delay(1000);
WiFi.reconnect();
if(connectTries >= 15) {
DBG("Restarting ESP32: WiFi issues\n");
delay(5000);
ESP.restart();
}
}
#endif // USE_ETHERNET
}
// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
FDRSNtp.beginPacket(address, 123); // NTP requests are to port 123
FDRSNtp.write(packetBuffer, NTP_PACKET_SIZE);
FDRSNtp.endPacket();
}
void fetchNtpTime() {
//DBG("GetTime Function");
if(timeSource.tmSource <= TMS_NTP) {
#ifdef USE_ETHERNET
if(eth_connected) {
#elif defined(USE_WIFI)
if(WiFi.status() == WL_CONNECTED) {
#endif
//DBG("Calling .begin function");
FDRSNtp.begin(localPort);
sendNTPpacket(timeServer); // send an NTP packet to a time server
uint i = 0;
for(i = 0; i < 800; i++) {
if(FDRSNtp.parsePacket())
break;
delay(10);
}
if(i < 800) {
DBG2("Took " + String(i * 10) + "ms to get NTP response from " + String(timeServer) + ".");
NTPFetchFail = 0;
// We've received a packet, read the data from it
FDRSNtp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
// the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
//DBG("Seconds since Jan 1 1900 = " + String(secsSince1900));
// now convert NTP time into everyday time:
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
// now is epoch format - seconds since Jan 1 1970
now = secsSince1900 - seventyYears;
if(setTime(now)) {
timeSource.tmNetIf = TMIF_LOCAL;
timeSource.tmAddress = 0xFFFF;
timeSource.tmSource = TMS_NTP;
timeSource.tmLastTimeSet = millis();
DBG1("Time source is now local NTP");
} // UTC time
}
else {
DBG1("Timeout getting a NTP response.");
}
}
}
return;
}
void begin_ntp() {
fetchNtpTime();
handleTime();
printTime();
}

@ -8,21 +8,32 @@
#define GLOBAL_DBG_LEVEL 0
#define GLOBAL_WIFI_SSID "Your SSID"
#define GLOBAL_WIFI_PASS "Password"
#define GLOBAL_WIFI_SSID "Your SSID"
#define GLOBAL_WIFI_PASS "Password"
#define GLOBAL_DNS1_IPADDRESS "8.8.8.8" // Default to Google Primary DNS
#define GLOBAL_DNS2_IPADDRESS "8.8.4.4" // Default to Google Secondary DNS
#define GLOBAL_MQTT_ADDR "192.168.0.8"
#define GLOBAL_MQTT_PORT 1883
//#define GLOBAL_MQTT_AUTH //uncomment to enable MQTT authentication
//#define GLOBAL_MQTT_AUTH //uncomment to enable MQTT authentication
#define GLOBAL_MQTT_USER "Your MQTT Username"
#define GLOBAL_MQTT_PASS "Your MQTT Password"
// MQTT Topics
#define TOPIC_DATA "fdrs/data"
#define TOPIC_STATUS "fdrs/status"
#define TOPIC_COMMAND "fdrs/command"
#define TOPIC_DATA_BACKLOG "fdrs/databacklog" // Used in filesystem module
// NTP Time Server
#define GLOBAL_TIME_SERVER "0.us.pool.ntp.org"
#define GLOBAL_LOCAL_OFFSET (-5) // Time in hours between local time and UTC
#define GLOBAL_TIME_FETCHNTP 60 // Time in minutes between fetching time from NTP server
#define GLOBAL_TIME_PRINTTIME 15 // Time in minutes between printing local time
#define GLOBAL_TIME_SEND_INTERVAL 60 // Time in minutes between sending out time
#define GLOBAL_LORA_FREQUENCY 915.0 // Carrier frequency in MHz. Allowed values range from 137.0 MHz to 1020.0 MHz (varies by chip).
#define GLOBAL_LORA_SF 7 // LoRa link spreading factor. Allowed values range from 6 to 12.
@ -33,4 +44,25 @@
#define MAC_PREFIX 0xAA, 0xBB, 0xCC, 0xDD, 0xEE // MAC address prefix. Can be used to distinguish different ESP-NOW networks.
#if defined(USE_OLED) || defined(USE_RTC_DS3231) || defined(USE_RTC_DS1307)
#define USE_I2C
#endif
#if defined(USE_RTC_DS3231) || defined(USE_RTC_DS1307)
#define USE_RTC
#endif
#if defined(USE_GPS) && defined(ESP8266)
#error "For ESP8266 only one UART has both Tx and Rx capabilities. GPS not supported for ESP8266"
#endif
#if defined(USE_ETHERNET) && !defined(ESP32)
#error "Ethernet only supported for ESP32."
#endif
#if defined(USE_OLED) && (!defined(ESP32) && !defined(ESP8266))
#warning "OLED current supported for only ESP32 or ESP8266."
#undef USE_OLED
#endif
#endif // __FDRS_GLOBALS_h__

@ -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(&gtwyAddress,&sys_packet,1);
return true;
}
else {
if(transmitLoRaAsync(&gtwyAddress, &sys_packet, 1))
{
return true;
}
}
return false;
}

@ -8,39 +8,10 @@
#include <fdrs_globals.h>
#define FDRS_NODE
// 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;
}
bool is_controller = false;
SystemPacket theCmd;
DataReading theData[256];
uint8_t ln;
bool newData;
uint8_t newData = event_clear;
uint8_t gatewayAddress[] = {MAC_PREFIX, GTWY_MAC};
const uint16_t espnow_size = 250 / sizeof(DataReading);
crcResult crcReturned = CRC_NULL;
@ -48,34 +19,44 @@ crcResult crcReturned = CRC_NULL;
uint8_t incMAC[6];
DataReading fdrsData[espnow_size];
DataReading incData[espnow_size];
TimeSource timeSource;
uint8_t data_count = 0;
void (*callback_ptr)(DataReading);
uint16_t subscription_list[256] = {};
bool active_subs[256] = {};
unsigned long lastTimePrint = 0;
#include "fdrs_debug.h"
#ifdef DEBUG_CONFIG
// #include "fdrs_checkConfig.h"
void sendTimeSerial();
#ifdef USE_I2C
#include <Wire.h>
#endif
#ifdef USE_OLED
#include "fdrs_oled.h"
#endif
#include "fdrs_debug.h"
#include "fdrs_time.h"
#ifdef DEBUG_CONFIG
// #include "fdrs_checkConfig.h"
#endif
#ifdef USE_ESPNOW
#include "fdrs_node_espnow.h"
#endif
#ifdef USE_LORA
#include "fdrs_node_lora.h"
#include "fdrs_lora.h"
#endif
void beginFDRS()
{
#ifdef FDRS_DEBUG
Serial.begin(115200);
// // find out the reset reason
// esp_reset_reason_t resetReason;
// resetReason = esp_reset_reason();
#ifdef USE_I2C
Wire.begin(I2C_SDA, I2C_SCL);
#endif
#ifdef USE_OLED
init_oled();
@ -167,10 +148,10 @@ void beginFDRS()
void handleIncoming()
{
if (newData)
if (newData != event_clear)
{
newData = false;
newData = event_clear;
for (int i = 0; i < ln; i++)
{ // Cycle through array of incoming DataReadings for any we are subbed to
for (int j = 0; j < 255; j++)
@ -215,7 +196,7 @@ bool sendFDRS()
}
#endif
#ifdef USE_LORA
crcReturned = transmitLoRa(&gtwyAddress, fdrsData, data_count);
transmitLoRaAsync(&gtwyAddress, fdrsData, data_count);
// DBG(" LoRa sent.");
#ifdef LORA_ACK
if(crcReturned == CRC_OK) {
@ -274,26 +255,32 @@ void sleepFDRS(uint32_t sleep_time)
#endif
#endif
DBG(" Delaying.");
delay(sleep_time * 1000);
delay(sleep_time * 1000);
}
void loopFDRS()
{
handleTime();
#ifdef USE_LORA
handleLoRa();
#endif
if (is_controller){
handleIncoming();
#ifdef USE_ESPNOW
if ((millis() - last_refresh) >= gtwy_timeout)
if (TDIFF(last_refresh,gtwy_timeout))
{
refresh_registration();
last_refresh = millis();
}
#endif
}
// Output time to display if time is valid
if(TDIFFMIN(lastTimePrint,FDRS_TIME_PRINTTIME)) {
lastTimePrint = millis();
printTime();
}
}
bool addFDRS(void (*new_cb_ptr)(DataReading))
@ -302,10 +289,10 @@ bool addFDRS(void (*new_cb_ptr)(DataReading))
callback_ptr = new_cb_ptr;
#ifdef USE_ESPNOW
SystemPacket sys_packet = {.cmd = cmd_add, .param = 0};
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
DBG("ESP-NOW peer registration request submitted to " + String(gatewayAddress[5]));
DBG("ESP-NOW peer registration request submitted to 0x" + String(gatewayAddress[5],HEX));
uint32_t add_start = millis();
is_added = false;
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
while ((millis() - add_start) <= 1000) // 1000ms timeout
{
yield();
@ -328,10 +315,10 @@ bool addFDRS(int timeout, void (*new_cb_ptr)(DataReading))
callback_ptr = new_cb_ptr;
#ifdef USE_ESPNOW
SystemPacket sys_packet = {.cmd = cmd_add, .param = 0};
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
DBG("ESP-NOW peer registration request submitted to " + String(gatewayAddress[5]));
DBG("ESP-NOW peer registration request submitted to 0x" + String(gatewayAddress[5],HEX));
uint32_t add_start = millis();
is_added = false;
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
while ((millis() - add_start) <= timeout)
{
yield();
@ -388,15 +375,33 @@ bool unsubscribeFDRS(uint16_t sub_id)
uint32_t pingFDRS(uint32_t timeout)
int pingFDRS(uint32_t timeout)
{
int pingResult = -1;
#ifdef USE_ESPNOW
uint32_t pingResponseMs = pingFDRSEspNow(gatewayAddress, timeout);
return pingResponseMs;
pingResult = pingFDRSEspNow(gatewayAddress, timeout);
#endif
#ifdef USE_LORA
uint32_t pingResponseMs = pingFDRSLoRa(&gtwyAddress, timeout);
return pingResponseMs;
pingResult = pingRequestLoRa(gtwyAddress, timeout);
#endif
return pingResult;
}
bool reqTimeFDRS() {
#ifdef USE_ESPNOW
return reqTimeEspNow();
#endif
#ifdef USE_LORA
return reqTimeLoRa();
#endif
}
// Skeleton Functions related to function calls to files that are not included
#ifndef USE_LORA
void sendTimeLoRa() {}
bool reqTimeLoRa() { return false; }
#endif
#ifndef USE_ESPNOW
esp_err_t sendTimeESPNow() { return ESP_OK; } // fdrs_gateway_time.h
#endif
void sendTimeSerial() { }

@ -7,12 +7,51 @@
#include <esp_wifi.h>
#endif
uint8_t broadcast_mac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
const uint8_t broadcast_mac[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
crcResult esp_now_ack_flag;
bool is_added = false;
bool pingFlag = false;
uint32_t last_refresh = 0;
uint32_t gtwy_timeout = 300000;
bool pingFlag = false;
// Request time from gateway - Optionally used in sensors
bool reqTimeEspNow() {
unsigned long pingStart = millis();
SystemPacket sys_packet = {.cmd = cmd_time, .param = 0};
DBG1("Requesting time from gateway 0x" + String(gatewayAddress[5],HEX));
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
while(timeSource.tmNetIf < TMIF_ESPNOW && (millis() - pingStart < 300)) {
// wait for time to be set
// magic happens here :)
yield();
delay(0);
}
if(timeSource.tmNetIf == TMIF_ESPNOW) {
return true;
}
else {
return false;
}
}
void recvTimeEspNow(uint32_t t) {
// Process time if there is no master set yet or if LoRa is the master or if we are already the time master
if(timeSource.tmNetIf < TMIF_ESPNOW || (timeSource.tmNetIf == TMIF_ESPNOW && timeSource.tmAddress == (incMAC[4] << 8 | incMAC[5]))) {
DBG1("Received time via ESP-NOW from 0x" + String(incMAC[5], HEX));
if(timeSource.tmNetIf < TMIF_ESPNOW) {
timeSource.tmNetIf = TMIF_ESPNOW;
timeSource.tmSource = TMS_NET;
timeSource.tmAddress = (incMAC[4] << 8 | incMAC[5]);
DBG1("ESP-NOW time source is now 0x" + String(incMAC[5], HEX));
}
setTime(t);
timeSource.tmLastTimeSet = millis();
}
else {
DBG2("ESP-NOW 0x" + String(incMAC[5], HEX) + " is not our time source, discarding request");
}
return;
}
// Set ESP-NOW send and receive callbacks for either ESP8266 or ESP32
#if defined(ESP8266)
@ -44,50 +83,69 @@ void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len)
{
#endif
if (len < sizeof(DataReading))
memcpy(&incMAC, mac, sizeof(incMAC));
if (len == sizeof(SystemPacket))
{
SystemPacket command;
memcpy(&command, incomingData, sizeof(command));
DBG2("Incoming ESP-NOW System Packet from 0x" + String(incMAC[5], HEX));
switch (command.cmd)
{
case cmd_ping:
pingFlag = true;
if(command.param == ping_reply) {
pingFlag = true;
}
break;
case cmd_add:
is_added = true;
gtwy_timeout = command.param;
break;
case cmd_time:
if(command.param > MIN_TS) {
recvTimeEspNow(command.param);
}
break;
}
}
else
else if((len == sizeof(DataReading)))
{
memcpy(&theData, incomingData, len);
ln = len / sizeof(DataReading);
newData = true;
DBG2("Incoming ESP-NOW DataReading from 0x" + String(incMAC[5], HEX));
newData = event_espnowg;
// Processing done by handleIncoming() in fdrs_node.h
}
else {
DBG2("Incoming ESP-NOW Data from 0x" + String(incMAC[5], HEX) + " of unexpected size " + String(len));
}
}
// FDRS node 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 pingFDRSEspNow(uint8_t *address, uint32_t timeout) {
SystemPacket sys_packet = {.cmd = cmd_ping, .param = 0};
esp_now_send(address, (uint8_t *)&sys_packet, sizeof(SystemPacket));
DBG(" ESP-NOW ping sent.");
uint32_t ping_start = millis();
// ESP-NOW is on the order of 10 milliseconds so happens very quickly.
int pingFDRSEspNow(uint8_t *dstaddr, uint32_t timeout) {
SystemPacket sys_packet = {.cmd = cmd_ping, .param = ping_request};
unsigned long pingTime = 0;
pingFlag = false;
while ((millis() - ping_start) <= timeout)
{
yield(); // do I need to yield or does it automatically?
if (pingFlag)
{
DBG("ESP-NOW Ping Reply in " + String(millis() - ping_start) + "ms from " + String(address[0], HEX) + ":" + String(address[1], HEX) + ":" + String(address[2], HEX) + ":" + String(address[3], HEX) + ":" + String(address[4], HEX) + ":" + String(address[5], HEX));
return (millis() - ping_start);
}
pingTime = millis();
DBG1("ESP-NOW ping sent to 0x" + String(*(dstaddr + 5),HEX));
esp_now_send(dstaddr, (uint8_t *)&sys_packet, sizeof(SystemPacket));
while(pingFlag == false && (millis() - pingTime < timeout)) {
yield();
delay(0);
}
if(pingFlag == true) {
pingTime = millis() - pingTime;
DBG1("ESP-NOW Ping Reply in " + String(pingTime) + "ms from 0x" + String(*(dstaddr + 5), HEX));
}
DBG("No ESP-NOW ping returned within " + String(timeout) + "ms.");
return UINT32_MAX;
else {
DBG1("No ESP-NOW ping returned within " + String(timeout) + "ms.");
pingTime = -1;
}
pingFlag = false;
return pingTime;
}
bool refresh_registration()
@ -95,7 +153,7 @@ bool refresh_registration()
#ifdef USE_ESPNOW
SystemPacket sys_packet = {.cmd = cmd_add, .param = 0};
esp_now_send(gatewayAddress, (uint8_t *)&sys_packet, sizeof(SystemPacket));
DBG1("Refreshing registration to " + String(gatewayAddress[5]));
DBG1("Refreshing registration to 0x" + String(gatewayAddress[5],HEX));
uint32_t add_start = millis();
is_added = false;
while ((millis() - add_start) <= 1000) // 1000ms timeout
@ -114,3 +172,6 @@ bool refresh_registration()
return true;
}
esp_err_t sendTimeESPNow() {
return ESP_OK;
}

@ -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");
}

@ -4,7 +4,7 @@
#define DISPLAY_PAGES 4
String debug_buffer[5] = {"", "", "", "", ""};
SSD1306Wire display(0x3c, OLED_SDA, OLED_SCL); // ADDRESS, SDA, SCL
SSD1306Wire display(0x3c, I2C_SDA, I2C_SCL); // ADDRESS, SDA, SCL
unsigned long displayEvent = 0;
uint8_t displayPage = 0;
@ -137,7 +137,6 @@ void init_oled(){
digitalWrite(OLED_RST, LOW);
delay(30);
digitalWrite(OLED_RST, HIGH);
Wire.begin(OLED_SDA, OLED_SCL);
display.init();
display.flipScreenVertically();
draw_OLED_header();

@ -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…
Cancel
Save