fix: stabilise observer wifi ota and s3 telemetry

This commit is contained in:
Jared Dohrman
2026-06-07 13:54:09 +10:00
parent cbfe1cec66
commit ec88d300c1
11 changed files with 328 additions and 57 deletions
+15 -2
View File
@@ -1216,6 +1216,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_archive_neighbours_dirty = false;
_battery_sample_valid = false;
_battery_mv_cache = 0;
_web_sensor_snapshot_valid = false;
region_load_active = false;
memset(&_stats_state, 0, sizeof(_stats_state));
@@ -1332,9 +1333,11 @@ void MyMesh::begin(FILESYSTEM *fs, ArchiveStorage* archive) {
web.setCommandRunner(this);
web.setNetworkStateProvider(&network);
web.begin(_fs);
#if defined(TBEAM_SUPREME_SX1262)
if (web.isWebEnabled()) {
network.loop(true);
}
#endif
_stats_history.begin(web.isWebStatsEnabled(), _archive);
if (web.isWebStatsEnabled() && !_stats_history.isLiveOnly() && _archive != nullptr && _archive->isMounted()) {
restoreArchiveNeighbours();
@@ -1356,7 +1359,7 @@ void MyMesh::begin(FILESYSTEM *fs, ArchiveStorage* archive) {
mqtt.setNetworkStateProvider(&network);
#endif
mqtt.begin(_fs);
#if defined(ESP_PLATFORM)
#if defined(ESP_PLATFORM) && defined(TBEAM_SUPREME_SX1262)
if (mqtt.isActive()) {
network.loop(true);
}
@@ -1951,6 +1954,8 @@ void MyMesh::updateStatsHistory(unsigned long now_ms) {
if (!live_stats_headroom_low) {
const uint16_t battery_mv = getBatteryMilliVolts();
WebSensorSnapshot sensor_snapshot = collectWebSensorSnapshot(board, sensors, battery_mv);
_web_sensor_snapshot = sensor_snapshot;
_web_sensor_snapshot_valid = true;
HistorySample sample{};
sample.epoch_secs = getRTCClock()->getCurrentTime();
sample.uptime_secs = static_cast<uint32_t>(uptime_millis / 1000);
@@ -2648,7 +2653,15 @@ bool MyMesh::formatWebStatsSummaryJson(char* reply, size_t reply_size) {
const int battery_display_pct =
(battery_pct >= 0) ? std::max(0, std::min(100, battery_pct))
: clampBatteryPercentFromRange(battery_mv, battery_min_mv, battery_max_mv);
const WebSensorSnapshot sensor_snapshot = collectWebSensorSnapshot(board, sensors, battery_mv);
WebSensorSnapshot sensor_snapshot;
if (_web_sensor_snapshot_valid) {
sensor_snapshot = _web_sensor_snapshot;
sensor_snapshot.has_battery = true;
sensor_snapshot.battery_mv = battery_mv;
} else {
sensor_snapshot.has_battery = true;
sensor_snapshot.battery_mv = battery_mv;
}
const bool archive_available = (_archive != nullptr) && _archive->isMounted();
#ifdef WITH_MQTT_UPLINK
const bool mqtt_connected = mqtt.isAnyBrokerConnected();
+2
View File
@@ -137,6 +137,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks, public WebPanelComm
bool _archive_neighbours_dirty;
bool _battery_sample_valid;
uint16_t _battery_mv_cache;
bool _web_sensor_snapshot_valid;
WebSensorSnapshot _web_sensor_snapshot;
NodePrefs _prefs;
ClientACL acl;
CommonCLI _cli;
+6
View File
@@ -35,7 +35,9 @@ void setup() {
board.begin();
#if defined(TBEAM_SUPREME_SX1262)
sensors.begin();
#endif
archive.begin();
@@ -93,6 +95,10 @@ void setup() {
command[0] = 0;
#if !defined(TBEAM_SUPREME_SX1262)
sensors.begin();
#endif
the_mesh.begin(fs, &archive);
#ifdef DISPLAY_CLASS
+26 -8
View File
@@ -157,14 +157,23 @@ releases:
tag: "observer-eastmesh-v2026.6.2"
date: "2026-06-07"
previous_version: "2026.6.1"
summary: "Improves Observer boot reliability, T-Beam S3 Supreme telemetry, Wi-Fi reconnect speed, and web-panel archive maintenance."
summary: "Improves Observer boot reliability, T-Beam S3 Supreme telemetry, Wi-Fi/OTA persistence, and web-panel archive maintenance."
changes:
- type: fixed
area: board
text: "Fixed LilyGo T-Beam S3 Supreme BME280 detection and telemetry reads by using the board-detected address and a stop-based I2C read path."
text: "Fixed LilyGo T-Beam S3 Supreme BME280 detection and live telemetry by using the board-detected address, stop-based I2C reads, and board-specific I2C bus recovery before samples."
- type: fixed
area: web
text: "Changed live `/stats` sensor values to use the main-loop sensor snapshot instead of reading I2C sensors from the web request handler."
- type: fixed
area: wifi
text: "Improved boot-time Wi-Fi startup by starting Wi-Fi earlier and remembering the last connected AP channel, with automatic fallback to full scan if that channel is stale."
text: "Improved boot-time Wi-Fi startup by starting Wi-Fi earlier on T-Beam S3 Supreme, remembering the last connected AP channel, and falling back to full scan if that channel is stale."
- type: fixed
area: wifi
text: "Added NVS-backed Wi-Fi preference recovery so SSID and password survive OTA updates even if the filesystem preference file is lost."
- type: fixed
area: wifi
text: "Hardened Wi-Fi retry handling so scan-mode connects are not interrupted early and failed attempts reset the ESP32 station state before retrying."
- type: fixed
area: cli
text: "Fixed `set wifi.powersaving none` returning an error when Wi-Fi power saving was already disabled."
@@ -176,7 +185,7 @@ releases:
text: "Added a web-panel and CLI SD purge action for clearing archive contents from supported SD-card targets."
- type: fixed
area: web
text: "Hardened web-panel OTA startup when the OTA listener port is still being released."
text: "Hardened OTA startup by binding before reporting success and falling back to alternate ports when port 80 is still being released."
breaking_changes: []
- track: observer-eastmesh-bridge-espnow
@@ -248,14 +257,23 @@ releases:
tag: "observer-eastmesh-bridge-espnow-v2026.6.2"
date: "2026-06-07"
previous_version: "2026.6.1"
summary: "Applies the Observer boot, Wi-Fi, telemetry, archive, and web-panel maintenance fixes to ESP-NOW bridge builds."
summary: "Applies the Observer boot, Wi-Fi/OTA persistence, telemetry, archive, and web-panel maintenance fixes to ESP-NOW bridge builds."
changes:
- type: fixed
area: board
text: "Fixed LilyGo T-Beam S3 Supreme BME280 detection and telemetry reads by using the board-detected address and a stop-based I2C read path."
text: "Fixed LilyGo T-Beam S3 Supreme BME280 detection and live telemetry by using the board-detected address, stop-based I2C reads, and board-specific I2C bus recovery before samples."
- type: fixed
area: web
text: "Changed live `/stats` sensor values to use the main-loop sensor snapshot instead of reading I2C sensors from the web request handler."
- type: fixed
area: wifi
text: "Improved boot-time Wi-Fi startup by starting Wi-Fi earlier and remembering the last connected AP channel, with automatic fallback to full scan if that channel is stale."
text: "Improved boot-time Wi-Fi startup by starting Wi-Fi earlier on T-Beam S3 Supreme, remembering the last connected AP channel, and falling back to full scan if that channel is stale."
- type: fixed
area: wifi
text: "Added NVS-backed Wi-Fi preference recovery so SSID and password survive OTA updates even if the filesystem preference file is lost."
- type: fixed
area: wifi
text: "Hardened Wi-Fi retry handling so scan-mode connects are not interrupted early and failed attempts reset the ESP32 station state before retrying."
- type: fixed
area: cli
text: "Fixed `set wifi.powersaving none` returning an error when Wi-Fi power saving was already disabled."
@@ -267,5 +285,5 @@ releases:
text: "Added a web-panel and CLI SD purge action for clearing archive contents from supported SD-card targets."
- type: fixed
area: web
text: "Hardened web-panel OTA startup when the OTA listener port is still being released."
text: "Hardened OTA startup by binding before reporting success and falling back to alternate ports when port 80 is still being released."
breaking_changes: []
+35 -29
View File
@@ -14,18 +14,19 @@ namespace {
AsyncWebServer* ota_server = nullptr;
char ota_id_buf[60];
char ota_home_buf[90];
volatile bool ota_start_pending = false;
constexpr uint8_t kOtaStartAttempts = 60;
uint16_t ota_server_port = 0;
constexpr uint8_t kOtaPort80Attempts = 8;
constexpr uint32_t kOtaStartRetryDelayMs = 500;
bool startOTAServerNow() {
bool startOTAServerNow(uint16_t port) {
if (ota_server != nullptr) {
ota_server->end();
delete ota_server;
ota_server = nullptr;
ota_server_port = 0;
}
ota_server = new AsyncWebServer(80);
ota_server = new AsyncWebServer(port);
ota_server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", ota_home_buf);
@@ -41,26 +42,35 @@ bool startOTAServerNow() {
ota_server->end();
delete ota_server;
ota_server = nullptr;
ota_server_port = 0;
return false;
}
ota_server_port = port;
return true;
}
void startOTAServerTask(void*) {
vTaskDelay(pdMS_TO_TICKS(1500));
for (uint8_t attempt = 1; attempt <= kOtaStartAttempts; ++attempt) {
if (startOTAServerNow()) {
bool startOTAServerWithFallback() {
for (uint8_t attempt = 1; attempt <= kOtaPort80Attempts; ++attempt) {
if (startOTAServerNow(80)) {
MESH_DEBUG_PRINTLN("OTA server listening on port 80");
ota_start_pending = false;
vTaskDelete(nullptr);
return;
return true;
}
MESH_DEBUG_PRINTLN("OTA server port 80 busy, retry %u/%u", attempt, kOtaStartAttempts);
vTaskDelay(pdMS_TO_TICKS(kOtaStartRetryDelayMs));
MESH_DEBUG_PRINTLN("OTA server port 80 busy, retry %u/%u", attempt, kOtaPort80Attempts);
delay(kOtaStartRetryDelayMs);
}
MESH_DEBUG_PRINTLN("OTA server failed to bind port 80");
ota_start_pending = false;
vTaskDelete(nullptr);
static const uint16_t fallback_ports[] = {8080, 8081};
for (uint8_t i = 0; i < sizeof(fallback_ports) / sizeof(fallback_ports[0]); ++i) {
const uint16_t port = fallback_ports[i];
if (startOTAServerNow(port)) {
MESH_DEBUG_PRINTLN("OTA server listening on fallback port %u", port);
return true;
}
MESH_DEBUG_PRINTLN("OTA server fallback port %u busy", port);
}
MESH_DEBUG_PRINTLN("OTA server failed to bind any port");
return false;
}
}
@@ -79,24 +89,20 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
ota_ip = WiFi.softAPIP();
}
sprintf(reply, "Started: http://%s/update", ota_ip.toString().c_str());
MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply);
snprintf(ota_id_buf, sizeof(ota_id_buf), "%s (%s)", id, getManufacturerName());
snprintf(ota_home_buf, sizeof(ota_home_buf), "<H2>Hi! I am a MeshCore Repeater. ID: %s</H2>", id);
if (!ota_start_pending) {
ota_start_pending = true;
if (xTaskCreate(startOTAServerTask, "ota-start", 4096, nullptr, 1, nullptr) != pdPASS) {
const bool started = startOTAServerNow();
ota_start_pending = false;
if (!started) {
strcpy(reply, "Error - OTA listener start failed");
return false;
}
}
if (!startOTAServerWithFallback()) {
strcpy(reply, "Error - OTA listener start failed");
return false;
}
if (ota_server_port == 80) {
sprintf(reply, "Started: http://%s/update", ota_ip.toString().c_str());
} else {
sprintf(reply, "Started: http://%s:%u/update", ota_ip.toString().c_str(), ota_server_port);
}
MESH_DEBUG_PRINTLN("startOTAUpdate: %s", reply);
return true;
}
+69 -4
View File
@@ -3,6 +3,10 @@
#include <helpers/TxtDataHelpers.h>
#include <string.h>
#if defined(ESP_PLATFORM)
#include <Preferences.h>
#endif
namespace {
struct LegacyWebPrefsV1 {
@@ -47,6 +51,41 @@ bool loadLegacyWebWifiPrefs(FILESYSTEM* fs, NetworkPrefs& prefs) {
return true;
}
#if defined(ESP_PLATFORM)
bool loadNvsNetworkPrefs(NetworkPrefs& prefs) {
Preferences nvs;
if (!nvs.begin("eastmesh-net", true)) {
return false;
}
NetworkPrefs stored{};
const size_t read = nvs.getBytes("prefs", &stored, sizeof(stored));
nvs.end();
if (read != sizeof(stored) || stored.magic != NetworkPrefsStore::magicValue()) {
return false;
}
prefs = stored;
return true;
}
bool saveNvsNetworkPrefs(const NetworkPrefs& prefs) {
Preferences nvs;
if (!nvs.begin("eastmesh-net", false)) {
return false;
}
const size_t written = nvs.putBytes("prefs", &prefs, sizeof(prefs));
nvs.end();
return written == sizeof(prefs);
}
#else
bool loadNvsNetworkPrefs(NetworkPrefs&) {
return false;
}
bool saveNvsNetworkPrefs(const NetworkPrefs&) {
return true;
}
#endif
} // namespace
void NetworkPrefsStore::setDefaults(NetworkPrefs& prefs) {
@@ -62,10 +101,15 @@ bool NetworkPrefsStore::load(FILESYSTEM* fs, NetworkPrefs& prefs,
const char* legacy_wifi_pwd) {
setDefaults(prefs);
if (fs == nullptr) {
loadNvsNetworkPrefs(prefs);
return false;
}
if (!fs->exists(kFilename)) {
if (loadNvsNetworkPrefs(prefs)) {
save(fs, prefs);
return true;
}
prefs.wifi_powersave = legacy_wifi_powersave <= 2 ? legacy_wifi_powersave : 0;
if (legacy_wifi_ssid != nullptr) {
StrHelper::strncpy(prefs.wifi_ssid, legacy_wifi_ssid, sizeof(prefs.wifi_ssid));
@@ -95,6 +139,10 @@ bool NetworkPrefsStore::load(FILESYSTEM* fs, NetworkPrefs& prefs,
if (!ok || persisted.magic != kMagic) {
fs->remove(kFilename);
if (loadNvsNetworkPrefs(prefs)) {
save(fs, prefs);
return true;
}
save(fs, prefs);
return false;
}
@@ -106,15 +154,32 @@ bool NetworkPrefsStore::load(FILESYSTEM* fs, NetworkPrefs& prefs,
if (prefs.wifi_channel < 1 || prefs.wifi_channel > 14) {
prefs.wifi_channel = 0;
}
bool repaired = false;
if (prefs.wifi_ssid[0] == 0 && legacy_wifi_ssid != nullptr && legacy_wifi_ssid[0] != 0) {
StrHelper::strncpy(prefs.wifi_ssid, legacy_wifi_ssid, sizeof(prefs.wifi_ssid));
prefs.wifi_channel = 0;
repaired = true;
}
if (prefs.wifi_pwd[0] == 0 && legacy_wifi_pwd != nullptr && legacy_wifi_pwd[0] != 0) {
StrHelper::strncpy(prefs.wifi_pwd, legacy_wifi_pwd, sizeof(prefs.wifi_pwd));
prefs.wifi_channel = 0;
repaired = true;
}
if (repaired) {
save(fs, prefs);
} else {
saveNvsNetworkPrefs(prefs);
}
return true;
}
bool NetworkPrefsStore::save(FILESYSTEM* fs, const NetworkPrefs& prefs) {
const bool nvs_ok = saveNvsNetworkPrefs(prefs);
if (fs == nullptr) {
return false;
return nvs_ok;
}
if (fs->exists(kFilename) && !fs->remove(kFilename)) {
return false;
return nvs_ok;
}
#if defined(RP2040_PLATFORM)
File file = fs->open(kFilename, "w");
@@ -122,9 +187,9 @@ bool NetworkPrefsStore::save(FILESYSTEM* fs, const NetworkPrefs& prefs) {
File file = fs->open(kFilename, "w", true);
#endif
if (!file) {
return false;
return nvs_ok;
}
bool ok = file.write(reinterpret_cast<const uint8_t*>(&prefs), sizeof(prefs)) == sizeof(prefs);
file.close();
return ok;
return ok && nvs_ok;
}
+1
View File
@@ -20,6 +20,7 @@ public:
const char* legacy_wifi_ssid = nullptr,
const char* legacy_wifi_pwd = nullptr);
static bool save(FILESYSTEM* fs, const NetworkPrefs& prefs);
static constexpr uint32_t magicValue() { return kMagic; }
private:
static constexpr uint32_t kMagic = 0x4E455450;
+13 -1
View File
@@ -327,9 +327,21 @@ void NetworkService::ensureWifi(bool network_required) {
WiFi.disconnect(false, false);
_last_wifi_attempt = 0;
}
if (_last_wifi_attempt != 0 && status == WL_IDLE_STATUS && now_ms - _last_wifi_attempt < kWifiConnectTimeoutMillis) {
if (_last_wifi_attempt != 0 && now_ms - _last_wifi_attempt < kWifiConnectTimeoutMillis) {
return;
}
if (_last_wifi_attempt != 0) {
Serial.printf("[BOOT] wifi timeout t=%lu code=%d retry\n",
static_cast<unsigned long>(millis()),
static_cast<int>(status));
WiFi.disconnect(false, false);
WiFi.mode(WIFI_OFF);
delay(100);
_wifi_started = false;
_sntp_started = false;
_have_time_sync = false;
_last_wifi_status = -1;
}
if (now_ms - _last_wifi_attempt < kWifiRetryMillis) {
return;
}
@@ -274,6 +274,73 @@ static bool writeI2CRegister8(TwoWire* wire, uint8_t addr, uint8_t reg, uint8_t
return wire->endTransmission(true) == 0;
}
#if defined(TBEAM_SUPREME_SX1262) && ENV_INCLUDE_BME280
static void recoverBME280I2CBus(TwoWire* wire) {
if (wire == nullptr) {
return;
}
#if defined(ESP32) && defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
if (wire == &Wire) {
Wire.end();
delay(5);
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL);
Wire.setClock(100000);
delay(5);
}
#endif
}
static bool readI2CRegister8WithRetry(TwoWire* wire, uint8_t addr, uint8_t reg, uint8_t* value) {
for (uint8_t attempt = 0; attempt < 20; attempt++) {
if (readI2CRegister8(wire, addr, reg, value)) {
return true;
}
if (attempt == 4) {
recoverBME280I2CBus(wire);
}
delay(5);
}
MESH_DEBUG_PRINTLN("BME280 read reg 0x%02X failed", reg);
return false;
}
static bool readI2CBufferWithRetry(TwoWire* wire, uint8_t addr, uint8_t reg, uint8_t* buffer, size_t len) {
for (uint8_t attempt = 0; attempt < 20; attempt++) {
if (readI2CBuffer(wire, addr, reg, buffer, len)) {
return true;
}
if (attempt == 4) {
recoverBME280I2CBus(wire);
}
delay(5);
}
MESH_DEBUG_PRINTLN("BME280 read buffer reg 0x%02X len=%u failed", reg, (unsigned)len);
return false;
}
static bool writeI2CRegister8WithRetry(TwoWire* wire, uint8_t addr, uint8_t reg, uint8_t value) {
if (wire == nullptr) {
return false;
}
uint8_t last_error = 0xFF;
for (uint8_t attempt = 0; attempt < 20; attempt++) {
wire->beginTransmission(addr);
wire->write(reg);
wire->write(value);
last_error = wire->endTransmission(true);
if (last_error == 0) {
return true;
}
if (attempt == 4) {
recoverBME280I2CBus(wire);
}
delay(5);
}
MESH_DEBUG_PRINTLN("BME280 write reg 0x%02X failed err=%u", reg, last_error);
return false;
}
#endif
uint8_t boardDetectedI2CSensorAddress(const char* name) __attribute__((weak));
uint8_t boardDetectedI2CSensorAddress(const char* name) {
(void)name;
@@ -363,8 +430,8 @@ static int16_t s16le(const uint8_t* p) {
static bool read_bme280_calibration(TwoWire* wire, uint8_t addr) {
uint8_t calib1[26] = {};
uint8_t calib2[7] = {};
if (!readI2CBuffer(wire, addr, 0x88, calib1, sizeof(calib1)) ||
!readI2CBuffer(wire, addr, 0xE1, calib2, sizeof(calib2))) {
if (!readI2CBufferWithRetry(wire, addr, 0x88, calib1, sizeof(calib1)) ||
!readI2CBufferWithRetry(wire, addr, 0xE1, calib2, sizeof(calib2))) {
return false;
}
@@ -430,8 +497,13 @@ static float compensate_bme280_humidity(int32_t adc_H) {
static uint8_t init_bme280(TwoWire* wire, uint8_t addr) {
uint8_t chip_id = 0;
const bool chip_id_ok = readI2CRegister8(wire, addr, 0xD0, &chip_id);
if (!chip_id_ok || chip_id != 0x60 || !read_bme280_calibration(wire, addr)) {
const bool chip_id_ok = readI2CRegister8WithRetry(wire, addr, 0xD0, &chip_id);
if (!chip_id_ok || chip_id != 0x60) {
MESH_DEBUG_PRINTLN("BME280 init failed chip ID 0x%02X", chip_id);
return 0;
}
if (!read_bme280_calibration(wire, addr)) {
MESH_DEBUG_PRINTLN("BME280 init failed calibration read");
return 0;
}
BME280Wire = wire;
@@ -445,21 +517,57 @@ static void query_bme280(uint8_t ch, uint8_t, CayenneLPP& lpp) {
if (BME280Wire == nullptr || BME280Address == 0) {
return;
}
if (!writeI2CRegister8(BME280Wire, BME280Address, 0xF2, 0x01) ||
!writeI2CRegister8(BME280Wire, BME280Address, 0xF4, 0x25)) { // temp/pressure x1, forced mode
recoverBME280I2CBus(BME280Wire);
if (!writeI2CRegister8WithRetry(BME280Wire, BME280Address, 0xF2, 0x01) ||
!writeI2CRegister8WithRetry(BME280Wire, BME280Address, 0xF4, 0x25)) { // temp/pressure x1, forced mode
MESH_DEBUG_PRINTLN("BME280 query failed to start forced measurement");
return;
}
delay(10);
uint8_t status = 0;
bool measurement_done = false;
for (uint8_t attempt = 0; attempt < 20; attempt++) {
if (!readI2CRegister8WithRetry(BME280Wire, BME280Address, 0xF3, &status)) {
MESH_DEBUG_PRINTLN("BME280 query failed status read");
return;
}
if ((status & 0x08) == 0) {
measurement_done = true;
break;
}
delay(2);
}
if (!measurement_done) {
MESH_DEBUG_PRINTLN("BME280 query timed out waiting for measurement");
return;
}
uint8_t data[8] = {};
if (!readI2CBuffer(BME280Wire, BME280Address, 0xF7, data, sizeof(data))) {
if (!readI2CBufferWithRetry(BME280Wire, BME280Address, 0xF7, data, sizeof(data))) {
MESH_DEBUG_PRINTLN("BME280 query failed data read");
return;
}
const int32_t adc_P = ((int32_t)data[0] << 12) | ((int32_t)data[1] << 4) | (data[2] >> 4);
const int32_t adc_T = ((int32_t)data[3] << 12) | ((int32_t)data[4] << 4) | (data[5] >> 4);
const int32_t adc_H = ((int32_t)data[6] << 8) | data[7];
if (adc_P == 0 || adc_T == 0) {
MESH_DEBUG_PRINTLN("BME280 query returned invalid raw sample p=%ld t=%ld h=%ld",
(long)adc_P,
(long)adc_T,
(long)adc_H);
return;
}
const float temperature = compensate_bme280_temperature(adc_T);
const float pressure_pa = compensate_bme280_pressure(adc_P);
const float humidity = compensate_bme280_humidity(adc_H);
if (pressure_pa <= 0.0f) {
MESH_DEBUG_PRINTLN("BME280 query returned invalid pressure %.2f", pressure_pa);
return;
}
MESH_DEBUG_PRINTLN("BME280 sample temp=%.2f humidity=%.2f pressure=%.2f",
temperature,
humidity,
pressure_pa / 100.0f);
lpp.addTemperature(ch, temperature);
lpp.addRelativeHumidity(ch, humidity);
lpp.addBarometricPressure(ch, pressure_pa / 100.0f);
@@ -865,6 +973,7 @@ bool EnvironmentSensorManager::begin() {
}
}
MESH_DEBUG_PRINTLN("Environment sensors active: %d", _active_sensor_count);
return true;
}
+6 -1
View File
@@ -11,7 +11,12 @@ bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
bool SH1106Display::begin()
{
return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS);
const bool started = display.begin(DISPLAY_ADDRESS, true);
#if defined(ESP32) && defined(TBEAM_SUPREME_SX1262) && defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL);
Wire.setClock(100000);
#endif
return started && i2c_probe(Wire, DISPLAY_ADDRESS);
}
void SH1106Display::turnOn()
+38 -4
View File
@@ -2497,9 +2497,30 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
const sensors = summaryPayload && summaryPayload.sensors ? summaryPayload.sensors : null;
const gpsEnabled = !!(sensors && sensors.gps_enabled === true);
const order = ["battery", "memory", "signal", "noise_floor", "packets"];
if (sensors && Number.isFinite(sensors.supply_voltage_v)) {
order.push("voltage");
}
if (sensors && Number.isFinite(sensors.sensor_temp_c)) {
order.push("sensor_temp");
}
if (sensors && Number.isFinite(sensors.humidity_pct)) {
order.push("humidity");
}
if (sensors && Number.isFinite(sensors.pressure_hpa)) {
order.push("pressure");
}
if (sensors && Number.isFinite(sensors.pressure_altitude_m)) {
order.push("pressure_altitude");
}
if (sensors && Number.isFinite(sensors.mcu_temp_c)) {
order.push("mcu_temp");
}
if (gpsEnabled) {
order.push("gps_satellites");
}
if (sensors && Number.isFinite(sensors.gps_altitude_m)) {
order.push("gps_altitude");
}
return order;
}
function initTrendCards(seriesOrder) {
@@ -2706,10 +2727,19 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
return false;
}
try {
await fetchJson("/api/session");
const res = await fetch("/api/session", { headers:{ "X-Auth-Token": token } });
if (res.status === 401) {
redirectToLogin();
return false;
}
if (!res.ok) {
statusEl.textContent = "Web panel reconnecting...";
return null;
}
return true;
} catch (_) {
return false;
} catch (err) {
statusEl.textContent = "Web panel reconnecting...";
return null;
}
}
function getLetsmeshMode() {
@@ -3171,10 +3201,14 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
}
mqttIataLoaded = false;
refreshMqttIataWarning();
if (!await validateSession()) {
const sessionOk = await validateSession();
if (sessionOk === false) {
return;
}
showAuthedUi(true);
if (sessionOk === null) {
return;
}
if (isStatsPage) {
try {
await loadStatsPage(generation);