Add gateway time keeping, NTP, RTC support

pull/193/head
Jeff Lehman 4 months ago
parent 4d0488b16f
commit 6ba88a52a9

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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
@ -77,5 +85,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

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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
@ -77,5 +85,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

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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
@ -77,5 +85,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

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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
@ -77,5 +85,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

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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

@ -45,14 +45,22 @@
#define FDRS_DEBUG // Enable USB-Serial debugging
// 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
@ -68,3 +76,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

@ -31,8 +31,10 @@
#define LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
#define LORA_ACK // Request LoRa acknowledgment.
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
//#define USE_OLED
#define OLED_HEADER "FDRS"
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16

@ -21,8 +21,10 @@
#define LORA_TXPWR 17 // LoRa TX power in dBm (: +2dBm - +17dBm (for SX1276-7) +20dBm (for SX1278))
#define LORA_ACK // Request LoRa acknowledgment.
// I2C - OLED or RTC
#define I2C_SDA 4
#define I2C_SCL 15
#define USE_OLED
#define OLED_HEADER "FDRS"
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16

@ -26,11 +26,12 @@ enum crcResult {
CRC_BAD,
} returnCRC;
enum {
enum cmd_t {
cmd_clear,
cmd_ping,
cmd_add,
cmd_ack,
cmd_time,
};
enum
@ -46,6 +47,26 @@ enum
event_lora2,
event_internal
};
enum TmType {
TM_NONE,
TM_SERIAL,
TM_ESPNOW,
TM_LORA,
};
typedef struct TimeMaster {
TmType tmType;
uint16_t tmAddress;
unsigned long tmLastTimeSet;
} TimeMaster;
#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];
TimeMaster timeMaster;
uint8_t ln;
uint8_t newData = event_clear;
uint8_t newCmd = cmd_clear;
@ -67,6 +68,7 @@ void releaseLogBuffer();
#include "fdrs_oled.h"
#endif
#include "fdrs_debug.h"
#include "fdrs_gateway_time.h"
#include "fdrs_gateway_serial.h"
#include "fdrs_gateway_scheduler.h"
#ifdef USE_ESPNOW
@ -118,6 +120,12 @@ void beginFDRS()
Serial.begin(115200);
UART_IF.begin(115200, SERIAL_8N1, RXD2, TXD2);
#endif
#if defined(USE_OLED) || defined(USE_DS3231) || defined(USE_RTC_DS1307)
Wire.begin(I2C_SDA, I2C_SCL);
#endif
#if defined(USE_RTC_DS3231) || defined(USE_RTC_DS1307)
begin_rtc();
#endif
#ifdef USE_OLED
init_oled();
DBG("Display initialized!");
@ -132,6 +140,7 @@ void beginFDRS()
begin_wifi();
DBG("Connected.");
begin_mqtt();
begin_ntp();
begin_OTA();
#endif
#ifdef USE_ESPNOW
@ -141,7 +150,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()
@ -161,6 +172,14 @@ void handleCommands()
#endif // USE_ESPNOW
break;
case cmd_time:
#ifdef USE_ESPNOW
recvTimeEspNow(theCmd.param);
#endif // USE_ESPNOW
break;
}
theCmd.cmd = cmd_clear;
theCmd.param = 0;
@ -168,11 +187,16 @@ void handleCommands()
void loopFDRS()
{
updateTime();
handle_schedule();
handleCommands();
handleSerial();
#ifdef USE_LORA
handleLoRa();
// Ping LoRa time master to estimate time delay in radio link
if(timeMaster.tmType == TM_LORA && netTimeOffset == UINT32_MAX) {
pingLoRaTimeMaster();
}
#endif
#ifdef USE_WIFI
handleMQTT();
@ -221,12 +245,14 @@ void loopFDRS()
#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() {} // fdrs_gateway_time.h
#endif
#ifndef USE_ESPNOW
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() {}

@ -23,7 +23,9 @@ 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)
@ -192,6 +194,10 @@ 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
@ -298,8 +304,6 @@ void sendESPNowNbr(uint8_t interface)
}
}
void sendESPNowPeers()
{
DBG("Sending to ESP-NOW peers.");
@ -330,10 +334,6 @@ void sendESPNowPeers()
}
void sendESPNow(uint8_t address)
{
DBG("Sending ESP-NOW.");
@ -366,4 +366,47 @@ 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 master set yet or if LoRa is the master or if we are already the time master
if(timeMaster.tmType == TM_NONE || timeMaster.tmType == TM_LORA || (timeMaster.tmType == TM_ESPNOW && timeMaster.tmAddress == incMAC[4] << 8 | incMAC[5])) {
DBG("Received time via ESP-NOW from 0x" + String(incMAC[5], HEX));
if(timeMaster.tmAddress == 0x0000) {
timeMaster.tmType = TM_ESPNOW;
timeMaster.tmAddress = incMAC[4] << 8 & incMAC[5];
DBG("ESP-NOW time master is 0x" + String(incMAC[5], HEX));
}
setTime(t);
timeMaster.tmLastTimeSet = millis();
}
else {
DBG("ESP-NOW 0x" + String(incMAC[5], HEX) + " is not time master, 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((timeMaster.tmAddress != ESPNOW1[4] << 8 | ESPNOW1[5]) && ESPNOW1[5] != 0x00) {
DBG("Sending time to ESP-NOW Peer 1");
result1 = sendESPNow(ESPNOW1, &sys_packet);
}
if((timeMaster.tmAddress != ESPNOW2[4] << 8 | ESPNOW2[5]) && ESPNOW2[5] != 0x00) {
DBG("Sending time to ESP-NOW Peer 2");
result2 = sendESPNow(ESPNOW2, &sys_packet);
}
DBG("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;
}
}

@ -104,6 +104,9 @@ uint16_t loraGwAddress = ((selfAddress[4] << 8) | selfAddress[5]); // last 2 byt
uint16_t loraBroadcast = 0xFFFF;
unsigned long receivedLoRaMsg = 0; // Number of total LoRa packets destined for us and of valid size
unsigned long ackOkLoRaMsg = 0; // Number of total LoRa packets with valid CRC
extern time_t now;
time_t netTimeOffset = UINT32_MAX; // One direction of LoRa Ping time in units of seconds (1/2 full ping time)
typedef struct DataBuffer
{
@ -130,6 +133,7 @@ uint32_t tx_start_time;
crcResult transmitLoRa(uint16_t *, DataReading *, uint8_t);
crcResult transmitLoRa(uint16_t *, SystemPacket *, uint8_t);
static uint16_t crc16_update(uint16_t, uint8_t);
crcResult handleLoRa();
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
@ -410,33 +414,20 @@ crcResult getLoRa()
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 (ln == 1 && receiveData[0].cmd == cmd_time) {
if(timeMaster.tmType == TM_NONE || timeMaster.tmType != TM_ESPNOW || (timeMaster.tmType == TM_LORA && timeMaster.tmAddress == sourceMAC)) {
DBG("Time rcv from LoRa 0x" + String(sourceMAC, HEX));
if(timeMaster.tmType == TM_NONE) {
timeMaster.tmType = TM_LORA;
timeMaster.tmAddress = sourceMAC;
DBG("Time master is LoRa 0x" + String(sourceMAC, HEX));
}
setTime(receiveData[0].param);
adjTimeforNetDelay(netTimeOffset);
timeMaster.tmLastTimeSet = millis();
}
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 {
DBGF("LoRa 0x" + String(sourceMAC, HEX) + " is not time master, discarding request");
}
}
else
@ -626,4 +617,72 @@ crcResult handleLoRa()
}
}
return crcReturned;
}
void sendTimeLoRa() {
DBG("Sending time via LoRa");
SystemPacket spTimeLoRa = {.cmd = cmd_time, .param = now};
DBG("Sending time to LoRa broadcast");
transmitLoRa(&loraBroadcast, &spTimeLoRa, 1);
// Do not send to LoRa peers if their address is 0x..00
if(((LoRa1 & 0x00FF) != 0x0000) && (LoRa1 != timeMaster.tmAddress)) {
DBG("Sending time to LoRa Neighbor 1");
spTimeLoRa.param = now;
// add LoRa neighbor 1
transmitLoRa(&LoRa1, &spTimeLoRa, 1);
}
if(((LoRa2 & 0x00FF) != 0x0000) && (LoRa2 != timeMaster.tmAddress)) {
DBG("Sending time to LoRa Neighbor 2");
spTimeLoRa.param = now;
// add LoRa neighbor 2
transmitLoRa(&LoRa2, &spTimeLoRa, 1);
}
}
// 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);
DBGF("LoRa ping sent to address: 0x" + String(*address, HEX));
uint32_t ping_start = millis();
pingFlagLoRa = false;
// JL - future: figure out how to handle this asynchronously so we are not taking up processor time
while ((millis() - ping_start) <= timeout)
{
handleLoRa();
#ifdef ESP8266
yield();
#endif
if (pingFlagLoRa)
{
DBGF("LoRa Ping Returned: " + String(millis() - ping_start) + "ms.");
pingFlagLoRa = false;
return (millis() - ping_start);
}
}
DBGF("No LoRa ping returned within " + String(timeout) + "ms.");
return UINT32_MAX;
}
// Pings the LoRa time master periodically to calculate the time delay in the LoRa radio link
// Returns success or failure of the ping result
bool pingLoRaTimeMaster() {
// ping the time master every 5 minutes
if(millis() - lastTimeMasterPing > (5*60*1000 + random(0,2000))) {
time_t pingTimeMs;
pingTimeMs = pingFDRSLoRa(&timeMaster.tmAddress,4000);
if(pingTimeMs != UINT32_MAX) {
netTimeOffset = pingTimeMs/2/1000;
adjTimeforNetDelay(netTimeOffset);
lastTimeMasterPing = millis();
return true;
}
lastTimeMasterPing = millis();
}
return false;
}

@ -24,6 +24,8 @@
#endif
#endif
extern time_t now;
void getSerial() {
String incomingString;
if (UART_IF.available()){
@ -40,6 +42,8 @@ void getSerial() {
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 +52,48 @@ void getSerial() {
}
ln = s;
newData = event_serial;
DBG("Incoming Serial.");
DBG("Incoming Serial: DR");
String data;
serializeJson(doc, data);
DBG("Serial data: " + data);
}
else if(obj.containsKey("cmd")) { // SystemPacket
cmd_t c = doc[0]["cmd"];
if(c == cmd_time) {
// Serial time master takes precedence over all others
if(timeMaster.tmType == TM_NONE) {
timeMaster.tmType = TM_SERIAL;
timeMaster.tmAddress = 0x0000;
DBG("Time master is now Serial peer");
}
setTime(doc[0]["param"]);
DBG("Incoming Serial: time");
timeMaster.tmLastTimeSet = millis();
}
else {
DBG("Incoming Serial: unknown cmd: " + String(c));
}
}
else { // Who Knows???
DBG("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();
@ -75,4 +108,19 @@ void handleSerial(){
{
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.");
#ifndef ESP8266
// serializeJson(SysPacket, Serial);
// Serial.println();
#endif
}

@ -0,0 +1,342 @@
#include <sys/time.h>
// 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?
time_t lastNTPFetchSuccess = 0; // Last time that a successful NTP fetch was made
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;
// 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
#if defined(USE_RTC_DS3231) || defined(USE_RTC_DS1307)
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
DBGF("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) {
DBGF("RTC was not actively running, starting now. Err: " + String(err));
rtc.SetIsRunning(true);
validRtcFlag = false;
}
}
if(validRtcFlag) {
// Set date and time on the system
DBGF("Using Date and Time from RTC.");
setTime(rtc.GetDateTime().Unix32Time());
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);
}
#endif // USE_RTC_DS3231 || USE_RTC_DS1307
bool validTime() {
if(now < 1672000000 || (millis() - lastNTPFetchSuccess > (24*60*60*1000))) {
if(validTimeFlag) {
DBGF("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:
// //DBG("Unix time = " + String(now));
// localtime_r(&now, &timeinfo);
// strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
// DBG("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() && ((time(NULL) - lastDstCheck > 5) || lastDstCheck == 0)) {
lastDstCheck = time(NULL);
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);
// DBG("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);
// DBG("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);
// DBGFST("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);
// DBGFST("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;
DBGF("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
DBGF("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
DBGF("Sending out time");
// Only send via Serial interface if WiFi is enabled to prevent loops
#if defined(USE_WIFI) || defined (USE_RTC_DS3231) || defined(USE_RTC_DS1307) // do not remove this line
sendTimeSerial();
#endif // do not remove this line
sendTimeLoRa();
sendTimeESPNow();
}
}
bool setTime(time_t currentTime) {
slewSecs = 0;
time_t previousTime = now;
if(currentTime != 0) {
now = currentTime;
slewSecs = now - previousTime;
DBGF("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
#if defined(USE_RTC_DS3231) || defined(USE_RTC_DS1307)
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()) {
lastNTPFetchSuccess = millis();
if(TIME_SEND_INTERVAL == 0 && ((millis() - lastTimeSend > 5000) || lastTimeSend == 0)) { // avoid sending twice on start with RTC and WiFi
lastTimeSend = millis();
sendTime();
}
return true;
}
else {
return false;
}
}
void updateTime() {
static unsigned long lastUpdate = 0;
if(millis() - lastUpdate > 500) {
time(&now);
localtime_r(&now, &timeinfo);
tv.tv_sec = now;
tv.tv_usec = 0;
validTime();
checkDST();
lastUpdate = millis();
}
// Send out time to other devices if we have exceeded the time send interval
if(validTimeFlag && (TIME_SEND_INTERVAL != 0) && (millis() - lastTimeSend) > (1000 * 60 * TIME_SEND_INTERVAL)) {
lastTimeSend = millis();
sendTime();
}
if(millis() - timeMaster.tmLastTimeSet > (1000*60*60)) { // Reset time master to default if not heard anything for one hour
timeMaster.tmType = TM_NONE;
timeMaster.tmAddress = 0x0000;
timeMaster.tmLastTimeSet = millis();
}
}
void adjTimeforNetDelay(time_t newOffset) {
static time_t previousOffset = 0;
updateTime();
// check to see if offset and current time are valid
if(newOffset < UINT32_MAX && validTimeFlag) {
now = now + newOffset - previousOffset;
previousOffset = newOffset;
DBGF("Time adj by " + String(newOffset) + " secs");
}
}

@ -70,8 +70,32 @@
#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;
#ifdef USE_ETHERNET
static bool eth_connected = false;
void WiFiEvent(WiFiEvent_t event)
{
switch (event) {
@ -158,4 +182,86 @@ void begin_wifi()
delay(500);
}
#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");
#ifdef USE_ETHERNET
if(eth_connected) {
#else
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) {
DBG("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;
setTime(now); // UTC time
}
else {
NTPFetchFail++;
DBG("Timeout getting a NTP response. " + String(NTPFetchFail) + " consecutive failures.");
// If unable to Update the time after N tries then set the time to be not valid.
if(NTPFetchFail > 5) {
validTimeFlag = false;
DBG("Time no longer reliable.");
}
}
}
}
void begin_ntp() {
fetchNtpTime();
updateTime();
printTime();
}

@ -22,6 +22,12 @@
#define TOPIC_STATUS "fdrs/status"
#define TOPIC_COMMAND "fdrs/command"
// 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_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.
#define GLOBAL_LORA_BANDWIDTH 125.0 // LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz.

@ -78,6 +78,7 @@ void beginFDRS()
// resetReason = esp_reset_reason();
#endif
#ifdef USE_OLED
Wire.begin(I2C_SDA, I2C_SCL);
init_oled();
DBG("Display initialized!");
DBG("Hello, World!");

@ -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();

Loading…
Cancel
Save