diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ddb638a..2eae3b730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file. ## [9.1.0.1] ### 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] diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ba7b22c3e..7f801dfa2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -25,7 +25,7 @@ While fallback or downgrading is common practice it was never supported due to S ## 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. @@ -39,7 +39,7 @@ For initial configuration this release supports Webserver based **WifiManager** ## 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-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. ## 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) diff --git a/tasmota/support.ino b/tasmota/support.ino index bcaac70c4..f9591af3d 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -2076,6 +2076,10 @@ void PrepLog_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_start(arg, formatP); vsnprintf_P(TasmotaGlobal.log_data, sizeof(TasmotaGlobal.log_data), formatP, arg); diff --git a/tasmota/support_esp32.ino b/tasmota/support_esp32.ino index c1cc3dd8e..8be9f3eff 100644 --- a/tasmota/support_esp32.ino +++ b/tasmota/support_esp32.ino @@ -157,29 +157,6 @@ void ZigbeeWrite(const void *pSettings, unsigned 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 // diff --git a/tasmota/support_rtc.ino b/tasmota/support_rtc.ino index cb6539aeb..26a1a8f83 100644 --- a/tasmota/support_rtc.ino +++ b/tasmota/support_rtc.ino @@ -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))) -extern "C" { -#include "sntp.h" -} #include Ticker TickerRtc; @@ -44,13 +41,12 @@ struct RTC { uint32_t local_time = 0; uint32_t daylight_saving_time = 0; uint32_t standard_time = 0; - uint32_t ntp_time = 0; uint32_t midnight = 0; uint32_t restart_time = 0; uint32_t millis = 0; - uint32_t last_sync = 0; +// uint32_t last_sync = 0; int32_t time_timezone = 0; - uint8_t ntp_sync_minute = 0; + bool time_synced = false; bool midnight_now = false; bool user_time_entry = false; // Override NTP by user setting } Rtc; @@ -374,52 +370,39 @@ uint32_t RuleToTime(TimeRule r, int yr) void RtcSecond(void) { - TIME_T tmpTime; + static uint32_t last_sync = 0; Rtc.millis = millis(); if (!Rtc.user_time_entry) { - if (!TasmotaGlobal.global_state.network_down) { - uint8_t uptime_minute = (TasmotaGlobal.uptime / 60) % 60; // 0 .. 59 - if ((Rtc.ntp_sync_minute > 59) && (uptime_minute > 2)) { - Rtc.ntp_sync_minute = 1; // If sync prepare for a new cycle + if (Rtc.time_synced) { + Rtc.time_synced = false; + last_sync = Rtc.utc_time; + + 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 - PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: " D_UTC_TIME " %s, " D_DST_TIME " %s, " D_STD_TIME " %s"), - GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + TIME_T tmpTime; + 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); - if (Rtc.local_time < START_VALID_TIME) { // 2016-01-01 - TasmotaGlobal.rules_flag.time_init = 1; - } else { - TasmotaGlobal.rules_flag.time_set = 1; - } - } else { - Rtc.ntp_sync_minute++; // Try again in next minute - } + // 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("RTC: " D_UTC_TIME " %s, " D_DST_TIME " %s, " D_STD_TIME " %s"), + GetDateAndTime(DT_UTC).c_str(), GetDateAndTime(DT_DST).c_str(), GetDateAndTime(DT_STD).c_str()); + + if (Rtc.local_time < START_VALID_TIME) { // 2016-01-01 + TasmotaGlobal.rules_flag.time_init = 1; + } 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 - PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("NTP: Not synced")); - Rtc.last_sync = Rtc.utc_time; + PrepLog_P2(LOG_LEVEL_DEBUG, PSTR("RTC: Not synced")); + last_sync = Rtc.utc_time; } } @@ -477,9 +460,7 @@ void RtcSetTime(uint32_t epoch) if (epoch < START_VALID_TIME) { // 2016-01-01 Rtc.user_time_entry = false; TasmotaGlobal.ntp_force_sync = true; - sntp_init(); } else { - sntp_stop(); Rtc.user_time_entry = true; Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond } @@ -487,12 +468,6 @@ void RtcSetTime(uint32_t epoch) 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; BreakTime(Rtc.utc_time, RtcTime); TickerRtc.attach(1, RtcSecond); diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index afe8d4126..a319927ff 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -876,6 +876,8 @@ void PerformEverySecond(void) // Wifi keep alive to send Gratuitous ARP wifiKeepAlive(); + WifiPollNtp(); + #ifdef ESP32 if (11 == TasmotaGlobal.uptime) { // Perform one-time ESP32 houskeeping ESP_getSketchSize(); // Init sketchsize as it can take up to 2 seconds diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index f25e710e7..181ba10ef 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -701,3 +701,131 @@ void wifiKeepAlive(void) { 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; +} + +