66 #include < WiFi.h>
77 #include < HTTPClient.h>
88 #include < WiFiClientSecure.h>
9+ #include < esp_sntp.h>
10+ #include < time.h>
911#endif
1012
1113/* ------------------------------ Config -------------------------------- */
@@ -81,6 +83,16 @@ static const uint8_t TEST_GROUP_SECRET[16] = {
8183 #define DISCORD_WEBHOOK_RETRY_DELAY_MS 2000
8284 #define DISCORD_WEBHOOK_MAX_RETRIES 2
8385 #define DISCORD_WEBHOOK_DEDUPE_TTL_MS 30000UL
86+ #define NTP_SYNC_TIMEOUT_MS 20000UL
87+ #define NTP_SYNC_RETRY_DELAY_MS 60000UL
88+ #define NTP_SYNC_INTERVAL_MS (6UL * 60UL * 60UL * 1000UL )
89+ static volatile uint32_t g_ntp_sync_epoch = 0 ;
90+
91+ static void onNtpTimeSync (struct timeval * tv) {
92+ if (tv && tv->tv_sec > 0 ) {
93+ g_ntp_sync_epoch = (uint32_t )tv->tv_sec ;
94+ }
95+ }
8496
8597static size_t jsonEscape (const char * src, char * dst, size_t dst_len) {
8698 if (dst_len == 0 ) return 0 ;
@@ -852,6 +864,10 @@ bool MyMesh::shouldQueueDiscordWebhook(const char* sender, const char* body) {
852864}
853865
854866void MyMesh::initWifiClient () {
867+ wifi_connected_last = false ;
868+ ntp_sync_in_progress = false ;
869+ next_ntp_attempt_at = 0 ;
870+ ntp_request_deadline_at = 0 ;
855871 if (_prefs.wifi_ssid [0 ] == 0 || _prefs.wifi_pwd [0 ] == 0 ) return ;
856872 WiFi.mode (WIFI_STA );
857873 WiFi.setAutoReconnect (true );
@@ -860,6 +876,106 @@ void MyMesh::initWifiClient() {
860876 next_wifi_attempt_at = futureMillis (15000 );
861877}
862878
879+ bool MyMesh::pumpWifiClient () {
880+ if (_prefs.wifi_ssid [0 ] == 0 || _prefs.wifi_pwd [0 ] == 0 ) {
881+ wifi_connected_last = false ;
882+ ntp_sync_in_progress = false ;
883+ ntp_request_deadline_at = 0 ;
884+ return false ;
885+ }
886+
887+ wl_status_t status = WiFi.status ();
888+ if (status == WL_CONNECTED ) {
889+ if (!wifi_connected_last) {
890+ String ip = WiFi.localIP ().toString ();
891+ MESH_DEBUG_PRINTLN (" WiFi: connected ip=%s" , ip.c_str ());
892+ next_wifi_attempt_at = 0 ;
893+ next_ntp_attempt_at = 0 ;
894+ }
895+ wifi_connected_last = true ;
896+ return true ;
897+ }
898+
899+ if (wifi_connected_last) {
900+ MESH_DEBUG_PRINTLN (" WiFi: disconnected status=%d" , (int )status);
901+ ntp_sync_in_progress = false ;
902+ ntp_request_deadline_at = 0 ;
903+ next_ntp_attempt_at = 0 ;
904+ }
905+ wifi_connected_last = false ;
906+
907+ if (next_wifi_attempt_at == 0 || millisHasNowPassed (next_wifi_attempt_at)) {
908+ MESH_DEBUG_PRINTLN (" WiFi: not connected, retrying AP '%s'" , _prefs.wifi_ssid );
909+ WiFi.begin (_prefs.wifi_ssid , _prefs.wifi_pwd );
910+ next_wifi_attempt_at = futureMillis (15000 );
911+ }
912+ return false ;
913+ }
914+
915+ void MyMesh::startNtpSync () {
916+ if (WiFi.status () != WL_CONNECTED ) return ;
917+ g_ntp_sync_epoch = 0 ;
918+ esp_sntp_set_sync_mode (SNTP_SYNC_MODE_IMMED );
919+ esp_sntp_set_sync_interval (NTP_SYNC_INTERVAL_MS );
920+ esp_sntp_set_time_sync_notification_cb (onNtpTimeSync);
921+ configTzTime (" UTC0" , " pool.ntp.org" , " time.nist.gov" , " time.google.com" );
922+ ntp_sync_in_progress = true ;
923+ ntp_request_deadline_at = futureMillis (NTP_SYNC_TIMEOUT_MS );
924+ MESH_DEBUG_PRINTLN (" NTP: sync requested" );
925+ }
926+
927+ void MyMesh::formatNtpStatus (char * reply, size_t reply_len) const {
928+ if (_prefs.wifi_ssid [0 ] == 0 || _prefs.wifi_pwd [0 ] == 0 ) {
929+ StrHelper::strncpy (reply, " wifi.ntp=off" , reply_len);
930+ return ;
931+ }
932+ if (ntp_sync_in_progress) {
933+ StrHelper::strncpy (reply, " wifi.ntp=syncing" , reply_len);
934+ return ;
935+ }
936+ if (last_ntp_sync_time == 0 ) {
937+ StrHelper::strncpy (reply, " wifi.ntp=pending" , reply_len);
938+ return ;
939+ }
940+
941+ DateTime dt (last_ntp_sync_time);
942+ snprintf (reply, reply_len, " wifi.ntp=ok last=%02d:%02d %d/%d/%d UTC" ,
943+ dt.hour (), dt.minute (), dt.day (), dt.month (), dt.year ());
944+ }
945+
946+ void MyMesh::pumpNtpClient () {
947+ if (WiFi.status () != WL_CONNECTED ) return ;
948+
949+ uint32_t synced_epoch = g_ntp_sync_epoch;
950+ if (synced_epoch != 0 ) {
951+ g_ntp_sync_epoch = 0 ;
952+ getRTCClock ()->setCurrentTime (synced_epoch);
953+ last_ntp_sync_time = synced_epoch;
954+ ntp_sync_in_progress = false ;
955+ ntp_request_deadline_at = 0 ;
956+ next_ntp_attempt_at = futureMillis (NTP_SYNC_INTERVAL_MS );
957+
958+ DateTime dt (synced_epoch);
959+ MESH_DEBUG_PRINTLN (" NTP: synced %02d:%02d - %d/%d/%d UTC" ,
960+ dt.hour (), dt.minute (), dt.day (), dt.month (), dt.year ());
961+ return ;
962+ }
963+
964+ if (ntp_sync_in_progress) {
965+ if (ntp_request_deadline_at && millisHasNowPassed (ntp_request_deadline_at)) {
966+ ntp_sync_in_progress = false ;
967+ ntp_request_deadline_at = 0 ;
968+ next_ntp_attempt_at = futureMillis (NTP_SYNC_RETRY_DELAY_MS );
969+ MESH_DEBUG_PRINTLN (" NTP: sync timed out" );
970+ }
971+ return ;
972+ }
973+
974+ if (next_ntp_attempt_at && !millisHasNowPassed (next_ntp_attempt_at)) return ;
975+
976+ startNtpSync ();
977+ }
978+
863979void MyMesh::queueDiscordWebhook (const char * sender, const char * body) {
864980 if (_prefs.discord_webhook_url [0 ] == 0 ) return ;
865981 if (!shouldQueueDiscordWebhook (sender, body)) {
@@ -883,15 +999,7 @@ void MyMesh::queueDiscordWebhook(const char* sender, const char* body) {
883999void MyMesh::pumpDiscordWebhook () {
8841000 if (webhook_count == 0 ) return ;
8851001
886- if (WiFi.status () != WL_CONNECTED ) {
887- if (next_wifi_attempt_at && millisHasNowPassed (next_wifi_attempt_at)) {
888- if (_prefs.wifi_ssid [0 ] == 0 || _prefs.wifi_pwd [0 ] == 0 ) return ;
889- MESH_DEBUG_PRINTLN (" WiFi: not connected, retrying AP '%s'" , _prefs.wifi_ssid );
890- WiFi.begin (_prefs.wifi_ssid , _prefs.wifi_pwd );
891- next_wifi_attempt_at = futureMillis (15000 );
892- }
893- return ;
894- }
1002+ if (WiFi.status () != WL_CONNECTED ) return ;
8951003
8961004 if (next_webhook_attempt_at && !millisHasNowPassed (next_webhook_attempt_at)) return ;
8971005
@@ -1591,7 +1699,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
15911699 pending_ping_channel_ready = false ;
15921700#if defined(ESP32)
15931701 next_wifi_attempt_at = 0 ;
1702+ next_ntp_attempt_at = 0 ;
1703+ ntp_request_deadline_at = 0 ;
15941704 next_webhook_attempt_at = 0 ;
1705+ last_ntp_sync_time = 0 ;
1706+ wifi_connected_last = false ;
1707+ ntp_sync_in_progress = false ;
15951708 webhook_head = 0 ;
15961709 webhook_tail = 0 ;
15971710 webhook_count = 0 ;
@@ -1656,7 +1769,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
16561769 _prefs.discord_webhook_url [0 ] = 0 ;
16571770 _prefs.ping_public_enabled = 0 ;
16581771 _prefs.ping_public_max_replies = 0 ;
1659- _prefs.ping_test_enabled = 0 ;
1772+ _prefs.ping_test_enabled = 1 ;
16601773 _prefs.ping_test_max_replies = 3 ;
16611774 _prefs.ping_simple_enabled = 0 ;
16621775 _prefs.hourly_status_enabled = 1 ;
@@ -2090,6 +2203,26 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
20902203 savePrefs ();
20912204 strcpy (reply, " OK" );
20922205 }
2206+ } else if (memcmp (sub, " ntp" , 3 ) == 0 ) {
2207+ const char * val = sub + 3 ;
2208+ while (*val == ' ' ) val++;
2209+ if (*val == 0 || strcmp (val, " status" ) == 0 ) {
2210+ formatNtpStatus (reply, 160 );
2211+ } else if (strcmp (val, " sync" ) == 0 || strcmp (val, " now" ) == 0 ) {
2212+ if (_prefs.wifi_ssid [0 ] == 0 || _prefs.wifi_pwd [0 ] == 0 ) {
2213+ strcpy (reply, " Err - wifi not set" );
2214+ } else {
2215+ next_ntp_attempt_at = 0 ;
2216+ ntp_sync_in_progress = false ;
2217+ ntp_request_deadline_at = 0 ;
2218+ if (WiFi.status () == WL_CONNECTED ) {
2219+ startNtpSync ();
2220+ }
2221+ strcpy (reply, " OK" );
2222+ }
2223+ } else {
2224+ strcpy (reply, " Err - ??" );
2225+ }
20932226 } else if (strcmp (sub, " connect" ) == 0 ) {
20942227 initWifiClient ();
20952228 strcpy (reply, " OK" );
@@ -2323,6 +2456,8 @@ void MyMesh::loop() {
23232456 mesh::Mesh::loop ();
23242457
23252458#if defined(ESP32)
2459+ pumpWifiClient ();
2460+ pumpNtpClient ();
23262461 pumpDiscordWebhook ();
23272462#endif
23282463
0 commit comments