feat: add SD-backed stats archive and dedicated /stats web console
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
#include "NetworkService.h"
|
||||
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
constexpr unsigned long kWifiRetryMillis = 15000;
|
||||
constexpr unsigned long kWifiConnectTimeoutMillis = 45000;
|
||||
constexpr time_t kMinSaneEpoch = 1735689600; // 2025-01-01T00:00:00Z
|
||||
|
||||
int getWifiQualityPercent(int rssi_dbm) {
|
||||
if (rssi_dbm <= -100) {
|
||||
return 0;
|
||||
}
|
||||
if (rssi_dbm >= -50) {
|
||||
return 100;
|
||||
}
|
||||
return 2 * (rssi_dbm + 100);
|
||||
}
|
||||
|
||||
const char* getWifiQualityLabel(int rssi_dbm) {
|
||||
if (rssi_dbm >= -60) {
|
||||
return "excellent";
|
||||
}
|
||||
if (rssi_dbm >= -67) {
|
||||
return "good";
|
||||
}
|
||||
if (rssi_dbm >= -75) {
|
||||
return "fair";
|
||||
}
|
||||
return "poor";
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
NetworkService::NetworkService()
|
||||
: _fs(nullptr), _prefs{}, _wifi_started(false), _sntp_started(false), _have_time_sync(false), _last_wifi_attempt(0) {
|
||||
NetworkPrefsStore::setDefaults(_prefs);
|
||||
}
|
||||
|
||||
void NetworkService::begin(FILESYSTEM* fs,
|
||||
uint8_t legacy_wifi_powersave,
|
||||
const char* legacy_wifi_ssid,
|
||||
const char* legacy_wifi_pwd) {
|
||||
_fs = fs;
|
||||
NetworkPrefsStore::load(_fs, _prefs, legacy_wifi_powersave, legacy_wifi_ssid, legacy_wifi_pwd);
|
||||
}
|
||||
|
||||
void NetworkService::end() {
|
||||
#if defined(ESP_PLATFORM)
|
||||
if (_wifi_started) {
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
#endif
|
||||
_wifi_started = false;
|
||||
_sntp_started = false;
|
||||
_have_time_sync = false;
|
||||
_last_wifi_attempt = 0;
|
||||
}
|
||||
|
||||
void NetworkService::loop(bool network_required) {
|
||||
#if defined(ESP_PLATFORM)
|
||||
ensureWifi(network_required);
|
||||
updateTimeSync();
|
||||
#else
|
||||
(void)network_required;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NetworkService::savePrefs() {
|
||||
return NetworkPrefsStore::save(_fs, _prefs);
|
||||
}
|
||||
|
||||
bool NetworkService::setWifiSSID(const char* ssid) {
|
||||
if (ssid == nullptr) {
|
||||
return false;
|
||||
}
|
||||
StrHelper::strncpy(_prefs.wifi_ssid, ssid, sizeof(_prefs.wifi_ssid));
|
||||
bool ok = savePrefs();
|
||||
reconnectWifi();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool NetworkService::setWifiPassword(const char* pwd) {
|
||||
if (pwd == nullptr) {
|
||||
return false;
|
||||
}
|
||||
StrHelper::strncpy(_prefs.wifi_pwd, pwd, sizeof(_prefs.wifi_pwd));
|
||||
bool ok = savePrefs();
|
||||
reconnectWifi();
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool NetworkService::setWifiPowerSave(const char* mode) {
|
||||
if (mode == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t next_mode;
|
||||
if (strcmp(mode, "none") == 0) {
|
||||
next_mode = 0;
|
||||
} else if (strcmp(mode, "min") == 0) {
|
||||
next_mode = 1;
|
||||
} else if (strcmp(mode, "max") == 0) {
|
||||
next_mode = 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
_prefs.wifi_powersave = next_mode;
|
||||
bool ok = savePrefs();
|
||||
#if defined(ESP_PLATFORM)
|
||||
if (_wifi_started) {
|
||||
ok = WiFi.setSleep(toEspPowerSave(_prefs.wifi_powersave)) && ok;
|
||||
}
|
||||
#endif
|
||||
return ok;
|
||||
}
|
||||
|
||||
const char* NetworkService::getWifiPowerSave() const {
|
||||
#if defined(ESP_PLATFORM)
|
||||
return getPowerSaveLabel(_prefs.wifi_powersave);
|
||||
#else
|
||||
return "unsupported";
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkService::formatWifiStatusReply(char* reply, size_t reply_size) const {
|
||||
#if defined(ESP_PLATFORM)
|
||||
const char* status = "disconnected";
|
||||
const char* state = "disconnected";
|
||||
wl_status_t wifi_status = WiFi.status();
|
||||
if (_prefs.wifi_ssid[0] == 0) {
|
||||
status = "unconfigured";
|
||||
state = "unconfigured";
|
||||
} else if (wifi_status == WL_CONNECTED) {
|
||||
status = "connected";
|
||||
state = "connected";
|
||||
} else if (_wifi_started) {
|
||||
status = "connecting";
|
||||
}
|
||||
|
||||
switch (wifi_status) {
|
||||
case WL_IDLE_STATUS:
|
||||
state = "idle";
|
||||
break;
|
||||
case WL_NO_SSID_AVAIL:
|
||||
state = "no_ssid";
|
||||
break;
|
||||
case WL_SCAN_COMPLETED:
|
||||
state = "scan_completed";
|
||||
break;
|
||||
case WL_CONNECTED:
|
||||
state = "connected";
|
||||
break;
|
||||
case WL_CONNECT_FAILED:
|
||||
state = "connect_failed";
|
||||
break;
|
||||
case WL_CONNECTION_LOST:
|
||||
state = "connection_lost";
|
||||
break;
|
||||
case WL_DISCONNECTED:
|
||||
state = "disconnected";
|
||||
break;
|
||||
default:
|
||||
state = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
if (wifi_status == WL_CONNECTED) {
|
||||
const int rssi_dbm = WiFi.RSSI();
|
||||
snprintf(reply, reply_size,
|
||||
"> ssid:%s status:%s code:%d state:%s ip:%s rssi:%d quality:%d%% signal:%s",
|
||||
_prefs.wifi_ssid, status, static_cast<int>(wifi_status), state, WiFi.localIP().toString().c_str(),
|
||||
rssi_dbm, getWifiQualityPercent(rssi_dbm), getWifiQualityLabel(rssi_dbm));
|
||||
} else {
|
||||
snprintf(reply, reply_size, "> ssid:%s status:%s code:%d state:%s", _prefs.wifi_ssid[0] ? _prefs.wifi_ssid : "-",
|
||||
status, static_cast<int>(wifi_status), state);
|
||||
}
|
||||
#else
|
||||
snprintf(reply, reply_size, "> wifi:unsupported");
|
||||
#endif
|
||||
}
|
||||
|
||||
void NetworkService::reconnectWifi() {
|
||||
#if defined(ESP_PLATFORM)
|
||||
if (_wifi_started) {
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
}
|
||||
#endif
|
||||
_wifi_started = false;
|
||||
_sntp_started = false;
|
||||
_have_time_sync = false;
|
||||
_last_wifi_attempt = 0;
|
||||
}
|
||||
|
||||
bool NetworkService::isWifiConnected() const {
|
||||
#if defined(ESP_PLATFORM)
|
||||
return _wifi_started && WiFi.status() == WL_CONNECTED;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ESP_PLATFORM)
|
||||
wifi_ps_type_t NetworkService::toEspPowerSave(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case 1:
|
||||
return WIFI_PS_MIN_MODEM;
|
||||
case 2:
|
||||
return WIFI_PS_MAX_MODEM;
|
||||
default:
|
||||
return WIFI_PS_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
const char* NetworkService::getPowerSaveLabel(uint8_t mode) {
|
||||
switch (mode) {
|
||||
case 1:
|
||||
return "min";
|
||||
case 2:
|
||||
return "max";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkService::ensureWifi(bool network_required) {
|
||||
if (!network_required) {
|
||||
if (_wifi_started) {
|
||||
WiFi.disconnect(true, true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
_wifi_started = false;
|
||||
_sntp_started = false;
|
||||
_have_time_sync = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_prefs.wifi_ssid[0] == 0) {
|
||||
reconnectWifi();
|
||||
return;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long now_ms = millis();
|
||||
wl_status_t status = WiFi.status();
|
||||
if (_wifi_started) {
|
||||
if (_last_wifi_attempt != 0 && status == WL_IDLE_STATUS && now_ms - _last_wifi_attempt < kWifiConnectTimeoutMillis) {
|
||||
return;
|
||||
}
|
||||
if (now_ms - _last_wifi_attempt < kWifiRetryMillis) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_wifi_started) {
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setSleep(toEspPowerSave(_prefs.wifi_powersave));
|
||||
_wifi_started = true;
|
||||
}
|
||||
|
||||
_last_wifi_attempt = now_ms;
|
||||
WiFi.begin(_prefs.wifi_ssid, _prefs.wifi_pwd);
|
||||
}
|
||||
|
||||
void NetworkService::updateTimeSync() {
|
||||
bool prev_have_time_sync = _have_time_sync;
|
||||
if (!_wifi_started || WiFi.status() != WL_CONNECTED) {
|
||||
_have_time_sync = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_sntp_started) {
|
||||
configTzTime("UTC0", "au.pool.ntp.org", "time.google.com", "time.cloudflare.com");
|
||||
_sntp_started = true;
|
||||
}
|
||||
|
||||
sntp_sync_status_t sync_status = sntp_get_sync_status();
|
||||
time_t now = time(nullptr);
|
||||
bool sane_time = now >= kMinSaneEpoch;
|
||||
bool sync_ready = sync_status == SNTP_SYNC_STATUS_COMPLETED || sync_status == SNTP_SYNC_STATUS_IN_PROGRESS;
|
||||
_have_time_sync = sane_time && (sync_ready || prev_have_time_sync);
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user