| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- #include "network.h"
- #include <WiFi.h>
- #include <time.h>
- #include "config.h"
- #include "portal.h"
- #include "rtc.h"
- #include "settings.h"
- namespace {
- bool g_synced = false;
- uint32_t g_lastAttemptMs = 0;
- uint32_t g_lastSyncMs = 0;
- bool g_connecting = false;
- bool g_portalStarted = false;
- bool g_suspended = false;
- void startConnect()
- {
- const settings::Config &cfg = settings::get();
- Serial.printf("[net] connecting to %s\n", cfg.ssid);
- WiFi.disconnect();
- WiFi.begin(cfg.ssid, cfg.password);
- g_lastAttemptMs = millis();
- g_connecting = true;
- }
- } // namespace
- namespace network {
- void begin()
- {
- // The timezone is applied to the system clock regardless of connectivity, so
- // the RTC-derived time renders correctly offline.
- setenv("TZ", settings::get().timezone, 1);
- tzset();
- if (!settings::hasWifi()) {
- Serial.println("[net] no Wi-Fi configured");
- return; // main() has already raised the setup portal
- }
- WiFi.mode(WIFI_STA);
- WiFi.setHostname(DEVICE_HOSTNAME);
- WiFi.setAutoReconnect(true);
- WiFi.setSleep(true); // the display is idle most of the time
- startConnect();
- }
- void loop()
- {
- if (!settings::hasWifi() || g_suspended) {
- return;
- }
- const uint32_t nowMs = millis();
- if (WiFi.status() == WL_CONNECTED) {
- if (g_connecting) {
- g_connecting = false;
- Serial.printf("[net] connected, ip=%s\n", WiFi.localIP().toString().c_str());
- syncTimeNow();
- }
- if (!g_portalStarted) {
- // Serve the settings page on the LAN so the outdoor URL and timezone can
- // be changed without re-provisioning.
- portal::startStation();
- g_portalStarted = true;
- }
- if (g_synced && nowMs - g_lastSyncMs >= NTP_RESYNC_MS) {
- syncTimeNow();
- }
- return;
- }
- // Not connected: retry, but never more often than WIFI_RETRY_MS. The device
- // deliberately does not fall back to the setup AP here — the clock and the
- // indoor reading must stay on screen through a router reboot.
- const uint32_t since = nowMs - g_lastAttemptMs;
- if (g_connecting && since < WIFI_CONNECT_TIMEOUT_MS) {
- return;
- }
- if (since >= WIFI_RETRY_MS || (g_connecting && since >= WIFI_CONNECT_TIMEOUT_MS)) {
- if (g_connecting) {
- Serial.println("[net] connect attempt timed out");
- g_connecting = false;
- g_lastAttemptMs = nowMs; // hold off for WIFI_RETRY_MS before retrying
- return;
- }
- startConnect();
- }
- }
- bool isConnected()
- {
- return WiFi.status() == WL_CONNECTED;
- }
- bool syncTimeNow()
- {
- if (!isConnected()) {
- return false;
- }
- const settings::Config &cfg = settings::get();
- configTzTime(cfg.timezone, cfg.ntpServer, NTP_SERVER_2);
- // Wait briefly for the first SNTP response. A plausible year means the
- // system clock has been set from the network rather than left at the epoch.
- struct tm local = {};
- const uint32_t deadline = millis() + 10000UL;
- bool got = false;
- while (millis() < deadline) {
- if (getLocalTime(&local, 200) && local.tm_year + 1900 >= 2024) {
- got = true;
- break;
- }
- delay(100);
- }
- if (!got) {
- Serial.println("[net] NTP sync failed");
- return false;
- }
- // Persist UTC into the RTC so timekeeping survives the next outage.
- const time_t utcNow = time(nullptr);
- struct tm utc;
- gmtime_r(&utcNow, &utc);
- if (!rtc::setTime(utc)) {
- Serial.println("[net] NTP ok but RTC write failed");
- }
- g_synced = true;
- g_lastSyncMs = millis();
- Serial.printf("[net] time synced: %04d-%02d-%02d %02d:%02d local\n",
- local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
- local.tm_hour, local.tm_min);
- return true;
- }
- bool hasSyncedTime()
- {
- return g_synced;
- }
- void suspend()
- {
- if (g_suspended || !settings::hasWifi()) {
- return;
- }
- WiFi.disconnect(true);
- WiFi.mode(WIFI_OFF);
- g_suspended = true;
- g_connecting = false;
- }
- void resume()
- {
- if (!g_suspended) {
- return;
- }
- g_suspended = false;
- WiFi.mode(WIFI_STA);
- WiFi.setHostname(DEVICE_HOSTNAME);
- WiFi.setAutoReconnect(true);
- WiFi.setSleep(true);
- // The settings page and mDNS have to be re-announced on the new association.
- g_portalStarted = false;
- startConnect();
- }
- } // namespace network
|