Fixed NTP fallback server functionality

Fixed NTP fallback server functionality (#9739)
This commit is contained in:
Theo Arends 2020-11-06 15:22:03 +01:00
parent 9026455891
commit 488a360d5b
7 changed files with 172 additions and 77 deletions

View File

@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
## [9.1.0.1] ## [9.1.0.1]
### Changed ### Changed
- platformio compiler option `no target align` enabled for stage - Core library from v2.7.4.5 to v2.7.4.7
- Platformio compiler option `no target align` enabled (#9749)
### Fixed
- NTP fallback server functionality (#9739)
## [Released] ## [Released]

View File

@ -25,7 +25,7 @@ While fallback or downgrading is common practice it was never supported due to S
## Supported Core versions ## Supported Core versions
This release will be supported from ESP8266/Arduino library Core version **2.7.4.5** due to reported security and stability issues on previous Core version. This will also support gzipped binaries. This release will be supported from ESP8266/Arduino library Core version **2.7.4.7** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
Support of Core versions before 2.7.1 has been removed. Support of Core versions before 2.7.1 has been removed.
@ -39,7 +39,7 @@ For initial configuration this release supports Webserver based **WifiManager**
## Provided Binary Downloads ## Provided Binary Downloads
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.4.5**. The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.4.7**.
- **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY** - **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY**
- **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages. - **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages.
@ -58,4 +58,9 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota
[Complete list](BUILDS.md) of available feature and sensors. [Complete list](BUILDS.md) of available feature and sensors.
## Changelog v9.1.0.1 ## Changelog v9.1.0.1
- platformio compiler option `no target align` enabled for stage ### Changed
- Core library from v2.7.4.5 to v2.7.4.7
- Platformio compiler option `no target align` enabled (#9749)
### Fixed
- NTP fallback server functionality (#9739)

View File

@ -2076,6 +2076,10 @@ void PrepLog_P2(uint32_t loglevel, PGM_P formatP, ...)
void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...) void AddLog_P2(uint32_t loglevel, PGM_P formatP, ...)
{ {
if (TasmotaGlobal.prepped_loglevel) {
AddLog(TasmotaGlobal.prepped_loglevel);
}
va_list arg; va_list arg;
va_start(arg, formatP); va_start(arg, formatP);
vsnprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), formatP, arg); vsnprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), formatP, arg);

View File

@ -157,29 +157,6 @@ void ZigbeeWrite(const void *pSettings, unsigned nSettingsLen) {
NvmSave("zb", "zigbee", pSettings, nSettingsLen); NvmSave("zb", "zigbee", pSettings, nSettingsLen);
} }
//
// sntp emulation
//
static bool bNetIsTimeSync = false;
//
void SntpInit() {
bNetIsTimeSync = true;
}
uint32_t SntpGetCurrentTimestamp(void) {
time_t now = 0;
if (bNetIsTimeSync || TasmotaGlobal.ntp_force_sync)
{
//Serial_DebugX(("timesync configTime %d\n", TasmotaGlobal.ntp_force_sync, bNetIsTimeSync));
// init to UTC Time
configTime(0, 0, SettingsText(SET_NTPSERVER1), SettingsText(SET_NTPSERVER2), SettingsText(SET_NTPSERVER3));
bNetIsTimeSync = false;
TasmotaGlobal.ntp_force_sync = false;
}
time(&now);
return now;
}
// //
// Crash stuff // Crash stuff
// //

View File

@ -29,9 +29,6 @@ const uint32_t MINS_PER_HOUR = 60UL;
#define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400))) #define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400)))
extern "C" {
#include "sntp.h"
}
#include <Ticker.h> #include <Ticker.h>
Ticker TickerRtc; Ticker TickerRtc;
@ -44,13 +41,12 @@ struct RTC {
uint32_t local_time = 0; uint32_t local_time = 0;
uint32_t daylight_saving_time = 0; uint32_t daylight_saving_time = 0;
uint32_t standard_time = 0; uint32_t standard_time = 0;
uint32_t ntp_time = 0;
uint32_t midnight = 0; uint32_t midnight = 0;
uint32_t restart_time = 0; uint32_t restart_time = 0;
uint32_t millis = 0; uint32_t millis = 0;
uint32_t last_sync = 0; // uint32_t last_sync = 0;
int32_t time_timezone = 0; int32_t time_timezone = 0;
uint8_t ntp_sync_minute = 0; bool time_synced = false;
bool midnight_now = false; bool midnight_now = false;
bool user_time_entry = false; // Override NTP by user setting bool user_time_entry = false; // Override NTP by user setting
} Rtc; } Rtc;
@ -374,52 +370,39 @@ uint32_t RuleToTime(TimeRule r, int yr)
void RtcSecond(void) void RtcSecond(void)
{ {
TIME_T tmpTime; static uint32_t last_sync = 0;
Rtc.millis = millis(); Rtc.millis = millis();
if (!Rtc.user_time_entry) { if (!Rtc.user_time_entry) {
if (!TasmotaGlobal.global_state.network_down) { if (Rtc.time_synced) {
uint8_t uptime_minute = (TasmotaGlobal.uptime / 60) % 60; // 0 .. 59 Rtc.time_synced = false;
if ((Rtc.ntp_sync_minute > 59) && (uptime_minute > 2)) { last_sync = Rtc.utc_time;
Rtc.ntp_sync_minute = 1; // If sync prepare for a new cycle
if (Rtc.restart_time == 0) {
Rtc.restart_time = Rtc.utc_time - TasmotaGlobal.uptime; // save first synced time as restart time
} }
uint8_t offset = (TasmotaGlobal.uptime < 30) ? RtcTime.second : (((ESP_getChipId() & 0xF) * 3) + 3) ; // First try ASAP to sync. If fails try once every 60 seconds based on chip id
if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || // Never synced
(Rtc.ntp_sync_minute == uptime_minute))) || // Re-sync every hour
TasmotaGlobal.ntp_force_sync ) ) { // Forced sync
Rtc.ntp_time = sntp_get_current_timestamp();
if (Rtc.ntp_time > START_VALID_TIME) { // Fix NTP bug in core 2.4.1/SDK 2.2.1 (returns Thu Jan 01 08:00:10 1970 after power on)
TasmotaGlobal.ntp_force_sync = false;
Rtc.utc_time = Rtc.ntp_time;
Rtc.last_sync = Rtc.ntp_time;
Rtc.ntp_sync_minute = 60; // Sync so block further requests
if (Rtc.restart_time == 0) {
Rtc.restart_time = Rtc.utc_time - TasmotaGlobal.uptime; // save first ntp time as restart time
}
BreakTime(Rtc.utc_time, tmpTime);
RtcTime.year = tmpTime.year + 1970;
Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year);
Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year);
// Do not use AddLog_P2 here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9 TIME_T tmpTime;
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: " D_UTC_TIME " %s, " D_DST_TIME " %s, " D_STD_TIME " %s"), BreakTime(Rtc.utc_time, tmpTime);
GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); RtcTime.year = tmpTime.year + 1970;
Rtc.daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year);
Rtc.standard_time = RuleToTime(Settings.tflag[0], RtcTime.year);
if (Rtc.local_time < START_VALID_TIME) { // 2016-01-01 // Do not use AddLog_P2 here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9
TasmotaGlobal.rules_flag.time_init = 1; PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("RTC: " D_UTC_TIME " %s, " D_DST_TIME " %s, " D_STD_TIME " %s"),
} else { GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str());
TasmotaGlobal.rules_flag.time_set = 1;
} if (Rtc.local_time < START_VALID_TIME) { // 2016-01-01
} else { TasmotaGlobal.rules_flag.time_init = 1;
Rtc.ntp_sync_minute++; // Try again in next minute } else {
} TasmotaGlobal.rules_flag.time_set = 1;
} }
} }
if ((Rtc.utc_time > (2 * 60 * 60)) && (Rtc.last_sync < Rtc.utc_time - (2 * 60 * 60))) { // Every two hours a warning if ((Rtc.utc_time > (2 * 60 * 60)) && (last_sync < Rtc.utc_time - (2 * 60 * 60))) { // Every two hours a warning
// Do not use AddLog_P2 here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9 // Do not use AddLog_P2 here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9
PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Not synced")); PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("RTC: Not synced"));
Rtc.last_sync = Rtc.utc_time; last_sync = Rtc.utc_time;
} }
} }
@ -477,9 +460,7 @@ void RtcSetTime(uint32_t epoch)
if (epoch < START_VALID_TIME) { // 2016-01-01 if (epoch < START_VALID_TIME) { // 2016-01-01
Rtc.user_time_entry = false; Rtc.user_time_entry = false;
TasmotaGlobal.ntp_force_sync = true; TasmotaGlobal.ntp_force_sync = true;
sntp_init();
} else { } else {
sntp_stop();
Rtc.user_time_entry = true; Rtc.user_time_entry = true;
Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond
} }
@ -487,12 +468,6 @@ void RtcSetTime(uint32_t epoch)
void RtcInit(void) void RtcInit(void)
{ {
sntp_setservername(0, SettingsText(SET_NTPSERVER1));
sntp_setservername(1, SettingsText(SET_NTPSERVER2));
sntp_setservername(2, SettingsText(SET_NTPSERVER3));
sntp_stop();
sntp_set_timezone(0); // UTC time
sntp_init();
Rtc.utc_time = 0; Rtc.utc_time = 0;
BreakTime(Rtc.utc_time, RtcTime); BreakTime(Rtc.utc_time, RtcTime);
TickerRtc.attach(1, RtcSecond); TickerRtc.attach(1, RtcSecond);

View File

@ -876,6 +876,8 @@ void PerformEverySecond(void)
// Wifi keep alive to send Gratuitous ARP // Wifi keep alive to send Gratuitous ARP
wifiKeepAlive(); wifiKeepAlive();
WifiPollNtp();
#ifdef ESP32 #ifdef ESP32
if (11 == TasmotaGlobal.uptime) { // Perform one-time ESP32 houskeeping if (11 == TasmotaGlobal.uptime) { // Perform one-time ESP32 houskeeping
ESP_getSketchSize(); // Init sketchsize as it can take up to 2 seconds ESP_getSketchSize(); // Init sketchsize as it can take up to 2 seconds

View File

@ -701,3 +701,131 @@ void wifiKeepAlive(void) {
SetNextTimeInterval(wifi_timer, wifiTimerSec * 1000); SetNextTimeInterval(wifi_timer, wifiTimerSec * 1000);
} }
} }
void WifiPollNtp() {
static uint8_t ntp_sync_minute = 0;
if (TasmotaGlobal.global_state.network_down) { return; }
uint8_t uptime_minute = (TasmotaGlobal.uptime / 60) % 60; // 0 .. 59
if ((ntp_sync_minute > 59) && (uptime_minute > 2)) {
ntp_sync_minute = 1; // If sync prepare for a new cycle
}
// First try ASAP to sync. If fails try once every 60 seconds based on chip id
uint8_t offset = (TasmotaGlobal.uptime < 30) ? RtcTime.second : (((ESP_getChipId() & 0xF) * 3) + 3) ;
if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || // Never synced
(ntp_sync_minute == uptime_minute))) || // Re-sync every hour
TasmotaGlobal.ntp_force_sync ) ) { // Forced sync
TasmotaGlobal.ntp_force_sync = false;
uint32_t ntp_time = WifiGetNtp();
if (ntp_time > START_VALID_TIME) {
Rtc.utc_time = ntp_time;
ntp_sync_minute = 60; // Sync so block further requests
Rtc.time_synced = true;
RtcSecond();
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Synced"));
} else {
ntp_sync_minute++; // Try again in next minute
}
}
}
uint32_t WifiGetNtp(void) {
static uint8_t ntp_server_id = 0;
IPAddress time_server_ip;
char* ntp_server;
bool resolved_ip = false;
for (uint32_t i = 0; i < MAX_NTP_SERVERS; i++) {
ntp_server = SettingsText(SET_NTPSERVER1 + ntp_server_id);
if (strlen(ntp_server)) {
resolved_ip = (WiFi.hostByName(ntp_server, time_server_ip) == 1);
if (255 == time_server_ip[0]) { resolved_ip = false; }
yield();
if (resolved_ip) { break; }
}
ntp_server_id++;
if (ntp_server_id > 2) { ntp_server_id = 0; }
}
if (!resolved_ip) {
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: No server found"));
return 0;
}
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Name %s, IP %s"), ntp_server, time_server_ip.toString().c_str());
WiFiUDP udp;
uint32_t attempts = 3;
while (attempts > 0) {
uint32_t port = random(1025, 65535); // Create a random port for the UDP connection.
if (udp.begin(port) != 0) {
break;
}
attempts--;
}
if (0 == attempts) { return 0; }
while (udp.parsePacket() > 0) { // Discard any previously received packets
yield();
}
const uint32_t NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
uint8_t packet_buffer[NTP_PACKET_SIZE]; // Buffer to hold incoming & outgoing packets
memset(packet_buffer, 0, NTP_PACKET_SIZE);
packet_buffer[0] = 0b11100011; // LI, Version, Mode
packet_buffer[1] = 0; // Stratum, or type of clock
packet_buffer[2] = 6; // Polling Interval
packet_buffer[3] = 0xEC; // Peer Clock Precision
packet_buffer[12] = 49;
packet_buffer[13] = 0x4E;
packet_buffer[14] = 49;
packet_buffer[15] = 52;
if (udp.beginPacket(time_server_ip, 123) == 0) { // NTP requests are to port 123
ntp_server_id++;
if (ntp_server_id > 2) { ntp_server_id = 0; } // Next server next time
udp.stop();
return 0;
}
udp.write(packet_buffer, NTP_PACKET_SIZE);
udp.endPacket();
uint32_t begin_wait = millis();
while (!TimeReached(begin_wait + 1000)) { // Wait up to one second
uint32_t size = udp.parsePacket();
uint32_t remote_port = udp.remotePort();
if ((size >= NTP_PACKET_SIZE) && (remote_port == 123)) {
udp.read(packet_buffer, NTP_PACKET_SIZE); // Read packet into the buffer
udp.stop();
if ((packet_buffer[0] & 0b11000000) == 0b11000000) {
// Leap-Indicator: unknown (clock unsynchronized)
// See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: IP %s unsynched"), time_server_ip.toString().c_str());
return 0;
}
// convert four bytes starting at location 40 to a long integer
// TX time is used here.
uint32_t secs_since_1900 = (uint32_t)packet_buffer[40] << 24;
secs_since_1900 |= (uint32_t)packet_buffer[41] << 16;
secs_since_1900 |= (uint32_t)packet_buffer[42] << 8;
secs_since_1900 |= (uint32_t)packet_buffer[43];
if (0 == secs_since_1900) { // No time stamp received
return 0;
}
return secs_since_1900 - 2208988800UL;
}
delay(10);
}
// Timeout.
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: No reply"));
udp.stop();
return 0;
}