diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index ef9b6bfca4..ab7859b03e 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -229,6 +229,13 @@ void setup() { #error "need to define filesystem" #endif +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + sensors.setFileSystem(&InternalFS); +#elif defined(RP2040_PLATFORM) + sensors.setFileSystem(&LittleFS); +#elif defined(ESP32) + sensors.setFileSystem(&SPIFFS); +#endif sensors.begin(); #if ENV_INCLUDE_GPS == 1 diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 2ce056f521..d4737f8789 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -87,6 +87,7 @@ void setup() { command[0] = 0; + sensors.setFileSystem(fs); sensors.begin(); the_mesh.begin(fs); diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index a3798b2175..5f9f8447c2 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -68,6 +68,7 @@ void setup() { command[0] = 0; + sensors.setFileSystem(fs); sensors.begin(); the_mesh.begin(fs); diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index cace67a08c..0c43ecc46a 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -102,6 +102,7 @@ void setup() { command[0] = 0; + sensors.setFileSystem(fs); sensors.begin(); the_mesh.begin(fs); diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 89a174c228..cbc1ac5b79 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include // for FILESYSTEM #include "sensors/LocationProvider.h" #define TELEM_PERM_BASE 0x01 // 'base' permission includes battery @@ -23,6 +24,8 @@ class SensorManager { virtual const char* getSettingValue(int i) const { return NULL; } virtual bool setSettingValue(const char* name, const char* value) { return false; } virtual LocationProvider* getLocationProvider() { return NULL; } + // Lets implementations persist runtime config (eg. I2C address overrides) across reboots. + virtual void setFileSystem(FILESYSTEM* fs) { } // Helper functions to manage setting by keys (useful in many places ...) const char* getSettingByKey(const char* key) { diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 73842d9eeb..781ddf8a69 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -605,6 +605,78 @@ static const SensorDef SENSOR_TABLE[] = { static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TABLE[0])) - 1; +// ============================================================ +// Per-sensor I2C address overrides. +// +// Lets a sensor wired to a non-default address (eg. a BME680 +// on 0x77 instead of this firmware's default 0x76) still be +// found, via "sensor set addr_ ", persisted to +// flash so it survives reboot. Matched by name rather than +// table position so it stays valid across firmware updates +// that reorder SENSOR_TABLE. Takes effect on next begin() — +// changing it does not re-scan the bus immediately. +// ============================================================ + +#define SENSOR_ADDR_OVERRIDE_FILE "/sensor_addr.bin" +#define SENSOR_ADDR_OVERRIDE_NONE 0xFF + +struct AddrOverrideRecord { + char name[16]; + uint8_t address; +}; + +// "> 0 ? : 1" avoids a zero-length array on boards with no ENV_INCLUDE_* I2C sensors compiled in. +static uint8_t addr_override[SENSOR_TABLE_SIZE > 0 ? SENSOR_TABLE_SIZE : 1]; + +static int findSensorIndexByName(const char* name) { + for (size_t i = 0; i < SENSOR_TABLE_SIZE; i++) { + if (strcmp(SENSOR_TABLE[i].name, name) == 0) return (int)i; + } + return -1; +} + +static void loadAddrOverrides(FILESYSTEM* fs) { + for (size_t i = 0; i < SENSOR_TABLE_SIZE; i++) addr_override[i] = SENSOR_ADDR_OVERRIDE_NONE; + if (fs == NULL) return; + +#if defined(RP2040_PLATFORM) + File file = fs->open(SENSOR_ADDR_OVERRIDE_FILE, "r"); +#else + File file = fs->open(SENSOR_ADDR_OVERRIDE_FILE); +#endif + if (!file) return; + + AddrOverrideRecord rec; + while (file.read((uint8_t *)&rec, sizeof(rec)) == sizeof(rec)) { + int idx = findSensorIndexByName(rec.name); + if (idx >= 0) addr_override[idx] = rec.address; + } + file.close(); +} + +static void saveAddrOverrides(FILESYSTEM* fs) { + if (fs == NULL) return; + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + fs->remove(SENSOR_ADDR_OVERRIDE_FILE); + File file = fs->open(SENSOR_ADDR_OVERRIDE_FILE, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + File file = fs->open(SENSOR_ADDR_OVERRIDE_FILE, "w"); +#else + File file = fs->open(SENSOR_ADDR_OVERRIDE_FILE, "w", true); +#endif + if (!file) return; + + for (size_t i = 0; i < SENSOR_TABLE_SIZE; i++) { + AddrOverrideRecord rec; + memset(&rec, 0, sizeof(rec)); + strncpy(rec.name, SENSOR_TABLE[i].name, sizeof(rec.name) - 1); + rec.address = addr_override[i]; + file.write((const uint8_t *)&rec, sizeof(rec)); + } + file.close(); +} + // ============================================================ // begin() — scan the I2C bus, then initialize only what was // found. A sensor whose address does not ACK during the scan @@ -636,20 +708,23 @@ bool EnvironmentSensorManager::begin() { bool detected[128] = {}; scanI2CBus(TELEM_WIRE, detected); + loadAddrOverrides(_fs); + // Walk the sensor table and initialize only detected devices. _active_sensor_count = 0; for (size_t i = 0; i < SENSOR_TABLE_SIZE && _active_sensor_count < MAX_ACTIVE_SENSORS; i++) { const SensorDef& def = SENSOR_TABLE[i]; - if (!detected[def.address]) { - MESH_DEBUG_PRINTLN("%s not detected at I2C address %02X", def.name, def.address); + uint8_t addr = (addr_override[i] != SENSOR_ADDR_OVERRIDE_NONE) ? addr_override[i] : def.address; + if (!detected[addr]) { + MESH_DEBUG_PRINTLN("%s not detected at I2C address %02X", def.name, addr); continue; } - uint8_t n = def.init(TELEM_WIRE, def.address); + uint8_t n = def.init(TELEM_WIRE, addr); if (n == 0) { - MESH_DEBUG_PRINTLN("%s found at %02X but failed to initialize", def.name, def.address); + MESH_DEBUG_PRINTLN("%s found at %02X but failed to initialize", def.name, addr); continue; } - MESH_DEBUG_PRINTLN("Found %s at address: %02X", def.name, def.address); + MESH_DEBUG_PRINTLN("Found %s at address: %02X", def.name, addr); for (uint8_t sub = 0; sub < n && _active_sensor_count < MAX_ACTIVE_SENSORS; sub++) { _active_sensors[_active_sensor_count++] = { def.query, sub }; } @@ -687,6 +762,7 @@ int EnvironmentSensorManager::getNumSettings() const { #if ENV_INCLUDE_GPS if (gps_detected) settings++; // only show GPS setting if GPS is detected #endif + settings += (int)SENSOR_TABLE_SIZE; // one "addr_" setting per known sensor type return settings; } @@ -697,6 +773,12 @@ const char* EnvironmentSensorManager::getSettingName(int i) const { return "gps"; } #endif + int idx = i - settings; + if (idx >= 0 && idx < (int)SENSOR_TABLE_SIZE) { + static char key[24]; + sprintf(key, "addr_%s", SENSOR_TABLE[idx].name); + return key; + } return NULL; } @@ -707,6 +789,13 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const { return gps_active ? "1" : "0"; } #endif + int idx = i - settings; + if (idx >= 0 && idx < (int)SENSOR_TABLE_SIZE) { + static char val[8]; + uint8_t addr = (addr_override[idx] != SENSOR_ADDR_OVERRIDE_NONE) ? addr_override[idx] : SENSOR_TABLE[idx].address; + sprintf(val, "0x%02X", addr); + return val; + } return NULL; } @@ -726,6 +815,15 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val return true; } #endif + if (memcmp(name, "addr_", 5) == 0) { + int idx = findSensorIndexByName(name + 5); + if (idx < 0) return false; + uint8_t addr = (uint8_t)strtoul(value, NULL, 0); + if (addr < 0x08 || addr > 0x77) return false; // outside valid 7-bit I2C address range + addr_override[idx] = addr; + saveAddrOverrides(_fs); + return true; // takes effect on next begin()/reboot + } return false; // not supported } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 29147c8967..f807ea8df4 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -19,6 +19,8 @@ class EnvironmentSensorManager : public SensorManager { int _active_sensor_count = 0; uint8_t next_available_channel = TELEM_CHANNEL_SELF + 1; + FILESYSTEM* _fs = NULL; // set via setFileSystem(), used to persist I2C address overrides + bool gps_detected = false; bool gps_active = false; uint32_t gps_update_interval_sec = 1; @@ -50,4 +52,5 @@ class EnvironmentSensorManager : public SensorManager { const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; bool setSettingValue(const char* name, const char* value) override; + void setFileSystem(FILESYSTEM* fs) override { _fs = fs; } };