diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 30dec205a..49b2ba8e8 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -9,10 +9,10 @@ plugins:
lint:
enabled:
- checkov@3.2.497
- - renovate@42.78.2
+ - renovate@42.81.2
- prettier@3.7.4
- trufflehog@3.92.4
- - yamllint@1.37.1
+ - yamllint@1.38.0
- bandit@1.9.2
- trivy@0.68.2
- taplo@0.10.0
diff --git a/boards/t-watch-s3.json b/boards/t-watch-s3.json
index bae4f47b0..f3c0bea8e 100644
--- a/boards/t-watch-s3.json
+++ b/boards/t-watch-s3.json
@@ -9,7 +9,7 @@
"-DBOARD_HAS_PSRAM",
"-DT_WATCH_S3",
"-DARDUINO_USB_CDC_ON_BOOT=1",
- "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
diff --git a/platformio.ini b/platformio.ini
index 6923c65b9..4c19136af 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
- https://github.com/meshtastic/device-ui/archive/12f8cddc1e2908e1988da21e3500c695668e8d92.zip
+ https://github.com/meshtastic/device-ui/archive/5a870c623a4e9ab7a7abe3d02950536f107d1a31.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
- adafruit/Adafruit BMP280 Library@2.6.8
+ adafruit/Adafruit BMP280 Library@3.0.0
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
adafruit/Adafruit BMP085 Library@1.2.4
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
@@ -142,8 +142,6 @@ lib_deps =
adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
- # renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
- adafruit/Adafruit PM25 AQI Sensor@2.0.0
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.6
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
diff --git a/protobufs b/protobufs
index 61219de74..4b9f104a1 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 61219de7480ac8ddf27256f405667d2f416ee1bd
+Subproject commit 4b9f104a18ea43b1b2091ee2b48899fe43ad8a0b
diff --git a/src/Power.cpp b/src/Power.cpp
index c7d7c5d8b..e320f0557 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -476,7 +476,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
}
#endif
-#ifdef EXT_CHRG_DETECT
+#if defined(ELECROW_ThinkNode_M6)
+ return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
+#elif EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
diff --git a/src/SafeFile.cpp b/src/SafeFile.cpp
index 45b96ad07..39436f18e 100644
--- a/src/SafeFile.cpp
+++ b/src/SafeFile.cpp
@@ -54,7 +54,7 @@ size_t SafeFile::write(const uint8_t *buffer, size_t size)
}
/**
- * Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
+ * Atomically close the file (overwriting any old version) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
@@ -73,15 +73,7 @@ bool SafeFile::close()
if (!testReadback())
return false;
- { // Scope for lock
- concurrency::LockGuard g(spiLock);
- // brief window of risk here ;-)
- if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
- LOG_ERROR("Can't remove old pref file");
- return false;
- }
- }
-
+ // Rename or overwrite (atomic operation)
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
diff --git a/src/configuration.h b/src/configuration.h
index ec1b9acc2..cbadedf3f 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -176,7 +176,8 @@ along with this program. If not, see .
#define SSD1306_ADDRESS 0x3D
#define USE_SH1106
#else
-#define SSD1306_ADDRESS 0x3C
+#define SSD1306_ADDRESS_L 0x3C //Addr = 0
+#define SSD1306_ADDRESS_H 0x3D //Addr = 1
#endif
#define ST7567_ADDRESS 0x3F
@@ -205,7 +206,7 @@ along with this program. If not, see .
#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
#define MAX1704X_ADDR 0x36
-#define QMC6310_ADDR 0x1C
+#define QMC6310U_ADDR 0x1C
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
@@ -214,7 +215,7 @@ along with this program. If not, see .
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
-#define PMSA0031_ADDR 0x12
+#define PMSA003I_ADDR 0x12
#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
#define RCWL9620_ADDR 0x57
@@ -480,6 +481,7 @@ along with this program. If not, see .
#define MESHTASTIC_EXCLUDE_AUDIO 1
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
+#define MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR 1
#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index 83a455de7..4795d2abc 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
- ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
+ ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
return firstOfOrNONE(2, types);
}
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 3a79d97c5..dffcd8fb6 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -35,11 +35,12 @@ class ScanI2C
SHT4X,
SHTC3,
LPS22HB,
- QMC6310,
+ QMC6310U,
+ QMC6310N,
QMI8658,
QMC5883L,
HMC5883L,
- PMSA0031,
+ PMSA003I,
QMA6100P,
MPU6050,
LIS3DH,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 2be9212cf..7a263cd52 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -63,6 +63,10 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
if (i2cBus->available()) {
r = i2cBus->read();
}
+ if(r == 0x80){
+ LOG_INFO("QMC6310N found at address 0x%02X", addr.address);
+ return ScanI2C::DeviceType::QMC6310N;
+ }
r &= 0x0f;
if (r == 0x08 || r == 0x00) {
@@ -106,7 +110,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
if (i2cBus->available())
i2cBus->read();
}
- LOG_DEBUG("Register value: 0x%x", value);
+ LOG_DEBUG("Register value from 0x%x: 0x%x", registerLocation.i2cAddress.address, value);
return value;
}
@@ -175,7 +179,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = NONE;
if (err == 0) {
switch (addr.address) {
- case SSD1306_ADDRESS:
+ case SSD1306_ADDRESS_H:
+ case SSD1306_ADDRESS_L:
type = probeOLED(addr);
break;
@@ -382,11 +387,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
- if (registerValue == 0x5449) {
+ if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
- } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
+ } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) != 0) { // unique SHT4x serial number (6 bytes inc. CRC)
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
@@ -412,7 +416,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case LPS22HB_ADDR_ALT:
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
- SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310", (uint8_t)addr.address)
+ SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
case QMI8658_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
@@ -442,7 +446,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef HAS_QMA6100P
SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address)
#else
- SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031", (uint8_t)addr.address)
+ SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
#endif
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
diff --git a/src/detect/reClockI2C.h b/src/detect/reClockI2C.h
new file mode 100644
index 000000000..689e88d6f
--- /dev/null
+++ b/src/detect/reClockI2C.h
@@ -0,0 +1,41 @@
+#ifdef CAN_RECLOCK_I2C
+#include "ScanI2CTwoWire.h"
+
+uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus)
+{
+
+ uint32_t currentClock;
+
+ /* See https://github.com/arduino/Arduino/issues/11457
+ Currently, only ESP32 can getClock()
+ While all cores can setClock()
+ https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
+ https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
+ https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
+ For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
+ we need to reclock I2C and set it back to the previous desired speed.
+ Only for cases where we can know OR predefine the speed, we can do this.
+ */
+
+#ifdef ARCH_ESP32
+ currentClock = i2cBus->getClock();
+#elif defined(ARCH_NRF52)
+ // TODO add getClock function or return a predefined clock speed per variant?
+ return 0;
+#elif defined(ARCH_RP2040)
+ // TODO add getClock function or return a predefined clock speed per variant
+ return 0;
+#elif defined(ARCH_STM32WL)
+ // TODO add getClock function or return a predefined clock speed per variant
+ return 0;
+#else
+ return 0;
+#endif
+
+ if (currentClock != desiredClock) {
+ LOG_DEBUG("Changing I2C clock to %u", desiredClock);
+ i2cBus->setClock(desiredClock);
+ }
+ return currentClock;
+}
+#endif
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index 9975527aa..f5418b069 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -9,6 +9,15 @@
#include "GxEPD2Multi.h"
#endif
+// Limit how often we push a full E-Ink refresh. T-Deck Pro needs faster updates for typing.
+#ifndef EINK_FORCE_DISPLAY_THROTTLE_MS
+#if defined(T_DECK_PRO)
+#define EINK_FORCE_DISPLAY_THROTTLE_MS 200
+#else
+#define EINK_FORCE_DISPLAY_THROTTLE_MS 1000
+#endif
+#endif
+
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
@@ -42,7 +51,7 @@ class EInkDisplay : public OLEDDisplay
*
* @return true if we did draw the screen
*/
- virtual bool forceDisplay(uint32_t msecLimit = 1000);
+ virtual bool forceDisplay(uint32_t msecLimit = EINK_FORCE_DISPLAY_THROTTLE_MS);
/**
* Run any code needed to complete an update, after the physical refresh has completed.
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 28f17f962..8bf69b7a0 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -825,7 +825,7 @@ int32_t Screen::runOnce()
#endif
}
#endif
- if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
+ if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0 && !suppressRebootBanner) {
showSimpleBanner("Rebooting...", 0);
}
diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index 13e7d0dd2..5c459d984 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -2374,7 +2374,8 @@ void menuHandler::FrameToggles_menu()
lora,
clock,
show_favorites,
- show_telemetry,
+ show_env_telemetry,
+ show_aq_telemetry,
show_power,
enumEnd
};
@@ -2419,8 +2420,11 @@ void menuHandler::FrameToggles_menu()
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
- optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
- optionsEnumArray[options++] = show_telemetry;
+ optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Env. Telemetry" : "Show Env. Telemetry";
+ optionsEnumArray[options++] = show_env_telemetry;
+
+ optionsArray[options] = moduleConfig.telemetry.air_quality_screen_enabled ? "Hide AQ Telemetry" : "Show AQ Telemetry";
+ optionsEnumArray[options++] = show_aq_telemetry;
optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
optionsEnumArray[options++] = show_power;
@@ -2483,10 +2487,14 @@ void menuHandler::FrameToggles_menu()
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
- } else if (selected == show_telemetry) {
+ } else if (selected == show_env_telemetry) {
moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
+ } else if (selected == show_aq_telemetry) {
+ moduleConfig.telemetry.air_quality_screen_enabled = !moduleConfig.telemetry.air_quality_screen_enabled;
+ menuHandler::menuQueue = menuHandler::FrameToggles;
+ screen->runNow();
} else if (selected == show_power) {
moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index c315f23d9..80ac08175 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -93,6 +93,8 @@ int32_t RotaryEncoderInterruptBase::runOnce()
if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
+ } else if (now - pressStartTime < LONG_PRESS_DURATION) {
+ return (20); // keep checking for long/short until time expires
}
return INT32_MAX;
diff --git a/src/main.cpp b/src/main.cpp
index d77767736..2961f6041 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -574,6 +574,7 @@ void setup()
Wire.setSCL(I2C_SCL);
Wire.begin();
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
+ LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL);
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
@@ -758,11 +759,12 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
+ scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310U, meshtastic_TelemetrySensorType_QMC6310);
+ //TODO: Types need to be added meshtastic_TelemetrySensorType_QMC6310N
+ // scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310N, meshtastic_TelemetrySensorType_QMC6310N);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
@@ -1541,8 +1543,9 @@ void setup()
}
#endif
-uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
-uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
+uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
+uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
+bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for OTA handoff)
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
// This will suppress the current delay and instead try to run ASAP.
diff --git a/src/main.h b/src/main.h
index 7ca14d825..c3528a63d 100644
--- a/src/main.h
+++ b/src/main.h
@@ -81,6 +81,7 @@ extern uint32_t timeLastPowered;
extern uint32_t rebootAtMsec;
extern uint32_t shutdownAtMsec;
+extern bool suppressRebootBanner;
extern uint32_t serialSinceMsec;
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index c51c184c0..375bc76e3 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -53,7 +53,7 @@
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
-#include
+#include
#endif
NodeDB *nodeDB = nullptr;
@@ -756,8 +756,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.compass_orientation = COMPASS_ORIENTATION;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
- if (WiFiOTA::isUpdated()) {
- WiFiOTA::recoverConfig(&config.network);
+ if (MeshtasticOTA::isUpdated()) {
+ MeshtasticOTA::recoverConfig(&config.network);
}
#endif
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index d4ef5bee4..d93f6fafa 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -66,7 +66,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops. */
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
- /* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
+ /* Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index e0dd9c58b..68552ede5 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -298,6 +298,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_MESHSTICK_1262 = 121,
/* LilyGo T-Beam 1W */
meshtastic_HardwareModel_TBEAM_1_WATT = 122,
+ /* LilyGo T5 S3 ePaper Pro (V1 and V2) */
+ meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 5eac64a62..990ca0f46 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -9,11 +9,8 @@
#include "meshUtils.h"
#include
#include // for better whitespace handling
-#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
-#include "BleOta.h"
-#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
-#include "WiFiOTA.h"
+#include "MeshtasticOTA.h"
#endif
#include "Router.h"
#include "configuration.h"
@@ -236,26 +233,27 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
reboot(r->reboot_seconds);
break;
}
- case meshtastic_AdminMessage_reboot_ota_seconds_tag: {
- int32_t s = r->reboot_ota_seconds;
+ case meshtastic_AdminMessage_ota_request_tag: {
#if defined(ARCH_ESP32)
-#if !MESHTASTIC_EXCLUDE_BLUETOOTH
- if (!BleOta::getOtaAppVersion().isEmpty()) {
- if (screen)
- screen->startFirmwareUpdateScreen();
- BleOta::switchToOtaApp();
- LOG_INFO("Rebooting to BLE OTA");
+ if (r->ota_request.ota_hash.size != 32) {
+ suppressRebootBanner = true;
+ LOG_INFO("OTA Failed: Invalid `ota_hash` provided");
+ break;
}
-#endif
-#if !MESHTASTIC_EXCLUDE_WIFI
- if (WiFiOTA::trySwitchToOTA()) {
+
+ meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode;
+ if (MeshtasticOTA::trySwitchToOTA()) {
+ LOG_INFO("OTA Requested");
+ suppressRebootBanner = true;
if (screen)
screen->startFirmwareUpdateScreen();
- WiFiOTA::saveConfig(&config.network);
+ MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes);
LOG_INFO("Rebooting to WiFi OTA");
+ } else {
+ LOG_INFO("WIFI OTA Failed");
}
#endif
-#endif
+ int s = 1; // Reboot in 1 second, hard coded
LOG_INFO("Reboot in %d seconds", s);
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
break;
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index 63392f7e4..e17868baf 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -252,9 +252,9 @@ void setupModules()
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
-#if __has_include("Adafruit_PM25AQI.h")
- if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
- nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
+#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+ if (moduleConfig.has_telemetry &&
+ (moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled)) {
new AirQualityTelemetryModule();
}
#endif
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
index 21a563b9d..01f5da2c6 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "AirQualityTelemetry.h"
@@ -10,27 +10,54 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
-#include "detect/ScanI2CTwoWire.h"
+#include "Sensor/AddI2CSensorTemplate.h"
+#include "UnitConversions.h"
+#include "graphics/ScreenFonts.h"
+#include "graphics/SharedUIDisplay.h"
+#include "graphics/images.h"
#include "main.h"
+#include "sleep.h"
#include
-#ifndef PMSA003I_WARMUP_MS
-// from the PMSA003I datasheet:
-// "Stable data should be got at least 30 seconds after the sensor wakeup
-// from the sleep mode because of the fan’s performance."
-#define PMSA003I_WARMUP_MS 30000
-#endif
+// Sensors
+#include "Sensor/PMSA003ISensor.h"
-int32_t AirQualityTelemetryModule::runOnce()
+void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
+ if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
+ return;
+ }
+ LOG_INFO("Air Quality Telemetry adding I2C devices...");
+
/*
Uncomment the preferences below if you want to use the module
without having to configure it from the PythonAPI or WebUI.
+ Note: this was previously on runOnce, which didnt take effect
+ as other modules already had already been initialized (screen)
*/
// moduleConfig.telemetry.air_quality_enabled = 1;
+ // moduleConfig.telemetry.air_quality_screen_enabled = 1;
+ // moduleConfig.telemetry.air_quality_interval = 15;
- if (!(moduleConfig.telemetry.air_quality_enabled)) {
+ // order by priority of metrics/values (low top, high bottom)
+ addSensor(i2cScanner, ScanI2C::DeviceType::PMSA003I);
+}
+
+int32_t AirQualityTelemetryModule::runOnce()
+{
+ if (sleepOnNextExecution == true) {
+ sleepOnNextExecution = false;
+ uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
+ default_telemetry_broadcast_interval_secs);
+ LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs);
+ doDeepSleep(nightyNightMs, true, false);
+ }
+
+ uint32_t result = UINT32_MAX;
+
+ if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled ||
+ AIR_QUALITY_TELEMETRY_MODULE_ENABLE)) {
// If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
return disable();
}
@@ -42,82 +69,152 @@ int32_t AirQualityTelemetryModule::runOnce()
if (moduleConfig.telemetry.air_quality_enabled) {
LOG_INFO("Air quality Telemetry: init");
-#ifdef PMSA003I_ENABLE_PIN
- // put the sensor to sleep on startup
- pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
- digitalWrite(PMSA003I_ENABLE_PIN, LOW);
-#endif /* PMSA003I_ENABLE_PIN */
-
- if (!aqi.begin_I2C()) {
-#ifndef I2C_NO_RESCAN
- LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan");
- // rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty.
- uint8_t i2caddr_scan[] = {PMSA0031_ADDR};
- uint8_t i2caddr_asize = 1;
- auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire());
-#if defined(I2C_SDA1)
- i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
-#endif
- i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
- auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031);
- if (found.type != ScanI2C::DeviceType::NONE) {
- nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
- nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
- i2cScanner->fetchI2CBus(found.address);
- return setStartDelay();
- }
-#endif
- return disable();
+ // check if we have at least one sensor
+ if (!sensors.empty()) {
+ result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
- return setStartDelay();
}
- return disable();
+
+ // it's possible to have this module enabled, only for displaying values on the screen.
+ // therefore, we should only enable the sensor loop if measurement is also enabled
+ return result == UINT32_MAX ? disable() : setStartDelay();
} else {
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
- if (!moduleConfig.telemetry.air_quality_enabled)
- return disable();
-
- switch (state) {
-#ifdef PMSA003I_ENABLE_PIN
- case State::IDLE:
- // sensor is in standby; fire it up and sleep
- LOG_DEBUG("runOnce(): state = idle");
- digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
- state = State::ACTIVE;
-
- return PMSA003I_WARMUP_MS;
-#endif /* PMSA003I_ENABLE_PIN */
- case State::ACTIVE:
- // sensor is already warmed up; grab telemetry and send it
- LOG_DEBUG("runOnce(): state = active");
-
- if (((lastSentToMesh == 0) ||
- !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
- moduleConfig.telemetry.air_quality_interval,
- default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
- airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
- airTime->isTxAllowedAirUtil()) {
- sendTelemetry();
- lastSentToMesh = millis();
- } else if (service->isToPhoneQueueEmpty()) {
- // Just send to phone when it's not our time to send to mesh yet
- // Only send while queue is empty (phone assumed connected)
- sendTelemetry(NODENUM_BROADCAST, true);
- }
-
-#ifdef PMSA003I_ENABLE_PIN
- // put sensor back to sleep
- digitalWrite(PMSA003I_ENABLE_PIN, LOW);
- state = State::IDLE;
-#endif /* PMSA003I_ENABLE_PIN */
-
- return sendToPhoneIntervalMs;
- default:
+ if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
return disable();
}
+
+ // Wake up the sensors that need it
+ LOG_INFO("Waking up sensors");
+ for (TelemetrySensor *sensor : sensors) {
+ if (!sensor->isActive()) {
+ return sensor->wakeUp();
+ }
+ }
+
+ if (((lastSentToMesh == 0) ||
+ !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
+ moduleConfig.telemetry.air_quality_interval,
+ default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
+ airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
+ airTime->isTxAllowedAirUtil()) {
+ sendTelemetry();
+ lastSentToMesh = millis();
+ } else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
+ (service->isToPhoneQueueEmpty())) {
+ // Just send to phone when it's not our time to send to mesh yet
+ // Only send while queue is empty (phone assumed connected)
+ sendTelemetry(NODENUM_BROADCAST, true);
+ lastSentToPhone = millis();
+ }
+
+ // Send to sleep sensors that consume power
+ LOG_INFO("Sending sensors to sleep");
+ for (TelemetrySensor *sensor : sensors) {
+ sensor->sleep();
+ }
}
+ return min(sendToPhoneIntervalMs, result);
}
+bool AirQualityTelemetryModule::wantUIFrame()
+{
+ return moduleConfig.telemetry.air_quality_screen_enabled;
+}
+
+#if HAS_SCREEN
+void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ // === Setup display ===
+ display->clear();
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+ int line = 1;
+
+ // === Set Title
+ const char *titleStr = (graphics::currentResolution == graphics::ScreenResolution::High) ? "Air Quality" : "AQ.";
+
+ // === Header ===
+ graphics::drawCommonHeader(display, x, y, titleStr);
+
+ // === Row spacing setup ===
+ const int rowHeight = FONT_HEIGHT_SMALL - 4;
+ int currentY = graphics::getTextPositions(display)[line++];
+
+ // === Show "No Telemetry" if no data available ===
+ if (!lastMeasurementPacket) {
+ display->drawString(x, currentY, "No Telemetry");
+ return;
+ }
+
+ // Decode the telemetry message from the latest received packet
+ const meshtastic_Data &p = lastMeasurementPacket->decoded;
+ meshtastic_Telemetry telemetry;
+ if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &telemetry)) {
+ display->drawString(x, currentY, "No Telemetry");
+ return;
+ }
+
+ const auto &m = telemetry.variant.air_quality_metrics;
+
+ // Check if any telemetry field has valid data
+ bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental ||
+ m.has_pm25_environmental || m.has_pm100_environmental;
+
+ if (!hasAny) {
+ display->drawString(x, currentY, "No Telemetry");
+ return;
+ }
+
+ // === First line: Show sender name + time since received (left), and first metric (right) ===
+ const char *sender = getSenderShortName(*lastMeasurementPacket);
+ uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
+ String agoStr = (agoSecs > 864000) ? "?"
+ : (agoSecs > 3600) ? String(agoSecs / 3600) + "h"
+ : (agoSecs > 60) ? String(agoSecs / 60) + "m"
+ : String(agoSecs) + "s";
+
+ String leftStr = String(sender) + " (" + agoStr + ")";
+ display->drawString(x, currentY, leftStr); // Left side: who and when
+
+ // === Collect sensor readings as label strings (no icons) ===
+ std::vector entries;
+
+ if (m.has_pm10_standard)
+ entries.push_back("PM1: " + String(m.pm10_standard) + "ug/m3");
+ if (m.has_pm25_standard)
+ entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3");
+ if (m.has_pm100_standard)
+ entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
+
+ // === Show first available metric on top-right of first line ===
+ if (!entries.empty()) {
+ String valueStr = entries.front();
+ int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr);
+ display->drawString(rightX, currentY, valueStr);
+ entries.erase(entries.begin()); // Remove from queue
+ }
+
+ // === Advance to next line for remaining telemetry entries ===
+ currentY += rowHeight;
+
+ // === Draw remaining entries in 2-column format (left and right) ===
+ for (size_t i = 0; i < entries.size(); i += 2) {
+ // Left column
+ display->drawString(x, currentY, entries[i]);
+
+ // Right column if it exists
+ if (i + 1 < entries.size()) {
+ int rightX = SCREEN_WIDTH / 2;
+ display->drawString(rightX, currentY, entries[i + 1]);
+ }
+
+ currentY += rowHeight;
+ }
+ graphics::drawCommonFooter(display, x, y);
+}
+#endif
+
bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
@@ -144,35 +241,21 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
{
- if (!aqi.read(&data)) {
- LOG_WARN("Skip send measurements. Could not read AQIn");
- return false;
- }
-
+ bool valid = true;
+ bool hasSensor = false;
m->time = getTime();
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
- m->variant.air_quality_metrics.has_pm10_standard = true;
- m->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
- m->variant.air_quality_metrics.has_pm25_standard = true;
- m->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
- m->variant.air_quality_metrics.has_pm100_standard = true;
- m->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
+ m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
- m->variant.air_quality_metrics.has_pm10_environmental = true;
- m->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
- m->variant.air_quality_metrics.has_pm25_environmental = true;
- m->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
- m->variant.air_quality_metrics.has_pm100_environmental = true;
- m->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
+ // TODO - Should we check for sensor state here?
+ // If a sensor is sleeping, we should know and check to wake it up
+ for (TelemetrySensor *sensor : sensors) {
+ LOG_INFO("Reading AQ sensors");
+ valid = valid && sensor->getMetrics(m);
+ hasSensor = true;
+ }
- LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", m->variant.air_quality_metrics.pm10_standard,
- m->variant.air_quality_metrics.pm25_standard, m->variant.air_quality_metrics.pm100_standard);
-
- LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
- m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental,
- m->variant.air_quality_metrics.pm100_environmental);
-
- return true;
+ return valid && hasSensor;
}
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
@@ -206,7 +289,15 @@ meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
+ m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
+ m.time = getTime();
if (getAirQualityTelemetry(&m)) {
+ LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, \
+ pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u",
+ m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,
+ m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental,
+ m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental);
+
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
@@ -221,16 +312,44 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
lastMeasurementPacket = packetPool.allocCopy(*p);
if (phoneOnly) {
- LOG_INFO("Send packet to phone");
+ LOG_INFO("Sending packet to phone");
service->sendToPhone(p);
} else {
- LOG_INFO("Send packet to mesh");
+ LOG_INFO("Sending packet to mesh");
service->sendToMesh(p, RX_SRC_LOCAL, true);
+
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
+ meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
+ notification->level = meshtastic_LogRecord_Level_INFO;
+ notification->time = getValidTime(RTCQualityFromNet);
+ sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
+ Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
+ default_telemetry_broadcast_interval_secs) /
+ 1000U);
+ service->sendClientNotification(notification);
+ sleepOnNextExecution = true;
+ LOG_DEBUG("Start next execution in 5s, then sleep");
+ setIntervalFromNow(FIVE_SECONDS_MS);
+ }
}
return true;
}
-
return false;
}
+AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
+ meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response)
+{
+ AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
+
+ for (TelemetrySensor *sensor : sensors) {
+ result = sensor->handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+
+ return result;
+}
+
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h
index 0142ee686..2b88b74ba 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.h
+++ b/src/modules/Telemetry/AirQualityTelemetry.h
@@ -1,14 +1,23 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#pragma once
+
+#ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE
+#define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0
+#endif
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "Adafruit_PM25AQI.h"
#include "NodeDB.h"
#include "ProtobufModule.h"
+#include "detect/ScanI2CConsumer.h"
+#include
+#include
-class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule
+class AirQualityTelemetryModule : private concurrency::OSThread,
+ public ScanI2CConsumer,
+ public ProtobufModule
{
CallbackObserver nodeStatusObserver =
CallbackObserver(this,
@@ -16,22 +25,19 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
public:
AirQualityTelemetryModule()
- : concurrency::OSThread("AirQualityTelemetry"),
+ : concurrency::OSThread("AirQualityTelemetry"), ScanI2CConsumer(),
ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{
lastMeasurementPacket = nullptr;
- setIntervalFromNow(10 * 1000);
- aqi = Adafruit_PM25AQI();
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
-
-#ifdef PMSA003I_ENABLE_PIN
- // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking
- // a reading
- state = State::IDLE;
-#else
- state = State::ACTIVE;
-#endif
+ setIntervalFromNow(10 * 1000);
}
+ virtual bool wantUIFrame() override;
+#if !HAS_SCREEN
+ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+#else
+ virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
+#endif
protected:
/** Called to handle a particular incoming message
@@ -49,19 +55,17 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
*/
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
- private:
- enum State {
- IDLE = 0,
- ACTIVE = 1,
- };
+ virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
+ meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response) override;
+ void i2cScanFinished(ScanI2C *i2cScanner);
- State state;
- Adafruit_PM25AQI aqi;
- PM25_AQI_Data data = {0};
+ private:
bool firstTime = true;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
+ uint32_t lastSentToPhone = 0;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 843d7b8d5..ec6fe4799 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -141,37 +141,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
+#include "Sensor/AddI2CSensorTemplate.h"
#include "graphics/ScreenFonts.h"
#include
-#include
-
-static std::forward_list sensors;
-
-template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
-{
- ScanI2C::FoundDevice dev = i2cScanner->find(type);
- if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
- TelemetrySensor *sensor = new T();
-#if WIRE_INTERFACES_COUNT > 1
- TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
- if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
- // This sensor only works on Wire (Wire1 is not supported)
- delete sensor;
- return;
- }
-#else
- TwoWire *bus = &Wire;
-#endif
- if (sensor->initDevice(bus, &dev)) {
- sensors.push_front(sensor);
- return;
- }
- // destroy sensor
- delete sensor;
- }
-}
-
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
@@ -642,8 +615,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature,
m.variant.environment_metrics.soil_moisture);
- sensor_read_error_count = 0;
-
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h
index 6e4ce82e7..049ed6b77 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.h
+++ b/src/modules/Telemetry/EnvironmentTelemetry.h
@@ -67,7 +67,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
- uint32_t sensor_read_error_count = 0;
};
#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h
new file mode 100644
index 000000000..37d909d71
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h
@@ -0,0 +1,34 @@
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+
+#include "TelemetrySensor.h"
+#include "detect/ScanI2C.h"
+#include "detect/ScanI2CTwoWire.h"
+#include
+#include
+
+static std::forward_list sensors;
+
+template void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
+{
+ ScanI2C::FoundDevice dev = i2cScanner->find(type);
+ if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
+ TelemetrySensor *sensor = new T();
+#if WIRE_INTERFACES_COUNT > 1
+ TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
+ if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
+ // This sensor only works on Wire (Wire1 is not supported)
+ delete sensor;
+ return;
+ }
+#else
+ TwoWire *bus = &Wire;
+#endif
+ if (sensor->initDevice(bus, &dev)) {
+ sensors.push_front(sensor);
+ return;
+ }
+ // destroy sensor
+ delete sensor;
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
new file mode 100644
index 000000000..2225a4d87
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
@@ -0,0 +1,158 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+
+#include "../detect/reClockI2C.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "PMSA003ISensor.h"
+#include "TelemetrySensor.h"
+
+#include
+
+PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {}
+
+bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+ LOG_INFO("Init sensor: %s", sensorName);
+#ifdef PMSA003I_ENABLE_PIN
+ pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
+#endif
+
+ _bus = bus;
+ _address = dev->address.address;
+
+#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
+ if (!currentClock) {
+ LOG_WARN("PMSA003I can't be used at this clock speed");
+ return false;
+ }
+#endif
+
+ _bus->beginTransmission(_address);
+ if (_bus->endTransmission() != 0) {
+ LOG_WARN("PMSA003I not found on I2C at 0x12");
+ return false;
+ }
+
+#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus);
+#endif
+
+ status = 1;
+ LOG_INFO("PMSA003I Enabled");
+
+ initI2CSensor();
+ return true;
+}
+
+bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ if (!isActive()) {
+ LOG_WARN("PMSA003I is not active");
+ return false;
+ }
+
+#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
+#endif
+
+ _bus->requestFrom(_address, PMSA003I_FRAME_LENGTH);
+ if (_bus->available() < PMSA003I_FRAME_LENGTH) {
+ LOG_WARN("PMSA003I read failed: incomplete data (%d bytes)", _bus->available());
+ return false;
+ }
+
+#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus);
+#endif
+
+ for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH; i++) {
+ buffer[i] = _bus->read();
+ }
+
+ if (buffer[0] != 0x42 || buffer[1] != 0x4D) {
+ LOG_WARN("PMSA003I frame header invalid: 0x%02X 0x%02X", buffer[0], buffer[1]);
+ return false;
+ }
+
+ auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; };
+
+ computedChecksum = 0;
+
+ for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH - 2; i++) {
+ computedChecksum += buffer[i];
+ }
+ receivedChecksum = read16(buffer, PMSA003I_FRAME_LENGTH - 2);
+
+ if (computedChecksum != receivedChecksum) {
+ LOG_WARN("PMSA003I checksum failed: computed 0x%04X, received 0x%04X", computedChecksum, receivedChecksum);
+ return false;
+ }
+
+ measurement->variant.air_quality_metrics.has_pm10_standard = true;
+ measurement->variant.air_quality_metrics.pm10_standard = read16(buffer, 4);
+
+ measurement->variant.air_quality_metrics.has_pm25_standard = true;
+ measurement->variant.air_quality_metrics.pm25_standard = read16(buffer, 6);
+
+ measurement->variant.air_quality_metrics.has_pm100_standard = true;
+ measurement->variant.air_quality_metrics.pm100_standard = read16(buffer, 8);
+
+ // TODO - Add admin command to remove environmental metrics to save protobuf space
+ measurement->variant.air_quality_metrics.has_pm10_environmental = true;
+ measurement->variant.air_quality_metrics.pm10_environmental = read16(buffer, 10);
+
+ measurement->variant.air_quality_metrics.has_pm25_environmental = true;
+ measurement->variant.air_quality_metrics.pm25_environmental = read16(buffer, 12);
+
+ measurement->variant.air_quality_metrics.has_pm100_environmental = true;
+ measurement->variant.air_quality_metrics.pm100_environmental = read16(buffer, 14);
+
+ // TODO - Add admin command to remove PN to save protobuf space
+ measurement->variant.air_quality_metrics.has_particles_03um = true;
+ measurement->variant.air_quality_metrics.particles_03um = read16(buffer, 16);
+
+ measurement->variant.air_quality_metrics.has_particles_05um = true;
+ measurement->variant.air_quality_metrics.particles_05um = read16(buffer, 18);
+
+ measurement->variant.air_quality_metrics.has_particles_10um = true;
+ measurement->variant.air_quality_metrics.particles_10um = read16(buffer, 20);
+
+ measurement->variant.air_quality_metrics.has_particles_25um = true;
+ measurement->variant.air_quality_metrics.particles_25um = read16(buffer, 22);
+
+ measurement->variant.air_quality_metrics.has_particles_50um = true;
+ measurement->variant.air_quality_metrics.particles_50um = read16(buffer, 24);
+
+ measurement->variant.air_quality_metrics.has_particles_100um = true;
+ measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26);
+
+ return true;
+}
+
+bool PMSA003ISensor::isActive()
+{
+ return state == State::ACTIVE;
+}
+
+void PMSA003ISensor::sleep()
+{
+#ifdef PMSA003I_ENABLE_PIN
+ digitalWrite(PMSA003I_ENABLE_PIN, LOW);
+ state = State::IDLE;
+#endif
+}
+
+uint32_t PMSA003ISensor::wakeUp()
+{
+#ifdef PMSA003I_ENABLE_PIN
+ LOG_INFO("Waking up PMSA003I");
+ digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
+ state = State::ACTIVE;
+ return PMSA003I_WARMUP_MS;
+#endif
+ // No need to wait for warmup if already active
+ return 0;
+}
+#endif
diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.h b/src/modules/Telemetry/Sensor/PMSA003ISensor.h
new file mode 100644
index 000000000..09b43d620
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.h
@@ -0,0 +1,35 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+
+#define PMSA003I_I2C_CLOCK_SPEED 100000
+#define PMSA003I_FRAME_LENGTH 32
+#define PMSA003I_WARMUP_MS 30000
+
+class PMSA003ISensor : public TelemetrySensor
+{
+ public:
+ PMSA003ISensor();
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+
+ virtual bool isActive() override;
+ virtual void sleep() override;
+ virtual uint32_t wakeUp() override;
+
+ private:
+ enum class State { IDLE, ACTIVE };
+ State state = State::ACTIVE;
+
+ uint16_t computedChecksum = 0;
+ uint16_t receivedChecksum = 0;
+
+ uint8_t buffer[PMSA003I_FRAME_LENGTH]{};
+ TwoWire *_bus{};
+ uint8_t _address{};
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
index d6e7d1fac..f854cb5fe 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h
index 3c3e61808..af51ddfad 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.h
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h
@@ -58,6 +58,11 @@ class TelemetrySensor
// TODO: delete after migration
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
+ // Functions to sleep / wakeup sensors that support it
+ virtual void sleep(){};
+ virtual uint32_t wakeUp() { return 0; }
+ // Return active by default, override per sensor
+ virtual bool isActive() { return true; }
#if WIRE_INTERFACES_COUNT > 1
// Set to true if Implementation only works first I2C port (Wire)
@@ -65,6 +70,7 @@ class TelemetrySensor
#endif
virtual int32_t runOnce() { return INT32_MAX; }
virtual bool isInitialized() { return initialized; }
+ // TODO: is this used?
virtual bool isRunning() { return status > 0; }
virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
diff --git a/src/platform/esp32/BleOta.cpp b/src/platform/esp32/BleOta.cpp
deleted file mode 100644
index 698336f69..000000000
--- a/src/platform/esp32/BleOta.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#include "BleOta.h"
-#include "Arduino.h"
-#include
-
-static const String MESHTASTIC_OTA_APP_PROJECT_NAME("Meshtastic-OTA");
-
-const esp_partition_t *BleOta::findEspOtaAppPartition()
-{
- const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, nullptr);
-
- esp_app_desc_t app_desc;
- esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
-
- if (ret != ESP_OK || MESHTASTIC_OTA_APP_PROJECT_NAME != app_desc.project_name) {
- part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, nullptr);
- ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
- }
-
- if (ret == ESP_OK && MESHTASTIC_OTA_APP_PROJECT_NAME == app_desc.project_name) {
- return part;
- } else {
- return nullptr;
- }
-}
-
-String BleOta::getOtaAppVersion()
-{
- const esp_partition_t *part = findEspOtaAppPartition();
- esp_app_desc_t app_desc;
- esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
- String version;
- if (ret == ESP_OK) {
- version = app_desc.version;
- }
- return version;
-}
-
-bool BleOta::switchToOtaApp()
-{
- bool success = false;
- const esp_partition_t *part = findEspOtaAppPartition();
- if (part) {
- success = (ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_set_boot_partition(part)) == ESP_OK);
- }
- return success;
-}
\ No newline at end of file
diff --git a/src/platform/esp32/BleOta.h b/src/platform/esp32/BleOta.h
deleted file mode 100644
index f4c510920..000000000
--- a/src/platform/esp32/BleOta.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef BLEOTA_H
-#define BLEOTA_H
-
-#include
-#include
-
-class BleOta
-{
- public:
- explicit BleOta(){};
-
- static String getOtaAppVersion();
- static bool switchToOtaApp();
-
- private:
- String mUserAgent;
- static const esp_partition_t *findEspOtaAppPartition();
-};
-
-#endif // BLEOTA_H
\ No newline at end of file
diff --git a/src/platform/esp32/WiFiOTA.cpp b/src/platform/esp32/MeshtasticOTA.cpp
similarity index 77%
rename from src/platform/esp32/WiFiOTA.cpp
rename to src/platform/esp32/MeshtasticOTA.cpp
index 4cf157b4c..b8cb052ef 100644
--- a/src/platform/esp32/WiFiOTA.cpp
+++ b/src/platform/esp32/MeshtasticOTA.cpp
@@ -1,13 +1,13 @@
-#include "WiFiOTA.h"
+#include "MeshtasticOTA.h"
#include "configuration.h"
#include
#include
-namespace WiFiOTA
+namespace MeshtasticOTA
{
-static const char *nvsNamespace = "ota-wifi";
-static const char *appProjectName = "OTA-WiFi";
+static const char *nvsNamespace = "MeshtasticOTA";
+static const char *appProjectName = "MeshtasticOTA";
static bool updated = false;
@@ -43,12 +43,14 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network)
strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk));
}
-void saveConfig(meshtastic_Config_NetworkConfig *network)
+void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash)
{
LOG_INFO("Saving WiFi settings for upcoming OTA update");
Preferences prefs;
prefs.begin(nvsNamespace);
+ prefs.putUChar("method", method);
+ prefs.putBytes("ota_hash", ota_hash, 32);
prefs.putString("ssid", network->wifi_ssid);
prefs.putString("psk", network->wifi_psk);
prefs.putBool("updated", false);
@@ -62,10 +64,14 @@ const esp_partition_t *getAppPartition()
bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc)
{
- if (esp_ota_get_partition_description(part, app_desc) != ESP_OK)
+ if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) {
+ LOG_INFO("esp_ota_get_partition_description failed");
return false;
- if (strcmp(app_desc->project_name, appProjectName) != 0)
+ }
+ if (strcmp(app_desc->project_name, appProjectName) != 0) {
+ LOG_INFO("app_desc->project_name == 0");
return false;
+ }
return true;
}
@@ -89,4 +95,4 @@ const char *getVersion()
return app_desc.version;
}
-} // namespace WiFiOTA
+} // namespace MeshtasticOTA
diff --git a/src/platform/esp32/MeshtasticOTA.h b/src/platform/esp32/MeshtasticOTA.h
new file mode 100644
index 000000000..001eba039
--- /dev/null
+++ b/src/platform/esp32/MeshtasticOTA.h
@@ -0,0 +1,18 @@
+#ifndef MESHTASTICOTA_H
+#define MESHTASTICOTA_H
+
+#include "mesh-pb-constants.h"
+#include
+
+namespace MeshtasticOTA
+{
+void initialize();
+bool isUpdated();
+
+void recoverConfig(meshtastic_Config_NetworkConfig *network);
+void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash);
+bool trySwitchToOTA();
+const char *getVersion();
+} // namespace MeshtasticOTA
+
+#endif // MESHTASTICOTA_H
diff --git a/src/platform/esp32/WiFiOTA.h b/src/platform/esp32/WiFiOTA.h
deleted file mode 100644
index 5a7ee348a..000000000
--- a/src/platform/esp32/WiFiOTA.h
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef WIFIOTA_H
-#define WIFIOTA_H
-
-#include "mesh-pb-constants.h"
-#include
-
-namespace WiFiOTA
-{
-void initialize();
-bool isUpdated();
-
-void recoverConfig(meshtastic_Config_NetworkConfig *network);
-void saveConfig(meshtastic_Config_NetworkConfig *network);
-bool trySwitchToOTA();
-const char *getVersion();
-} // namespace WiFiOTA
-
-#endif // WIFIOTA_H
diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp
index 760964119..6667acf5c 100644
--- a/src/platform/esp32/main-esp32.cpp
+++ b/src/platform/esp32/main-esp32.cpp
@@ -5,11 +5,10 @@
#include "main.h"
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
-#include "BleOta.h"
#include "nimble/NimbleBluetooth.h"
#endif
-#include
+#include
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
@@ -144,22 +143,14 @@ void esp32Setup()
preferences.putUInt("hwVendor", HW_VENDOR);
preferences.end();
LOG_DEBUG("Number of Device Reboots: %d", rebootCounter);
-#if !MESHTASTIC_EXCLUDE_BLUETOOTH
- String BLEOTA = BleOta::getOtaAppVersion();
- if (BLEOTA.isEmpty()) {
- LOG_INFO("No BLE OTA firmware available");
- } else {
- LOG_INFO("BLE OTA firmware version %s", BLEOTA.c_str());
- }
-#endif
#if !MESHTASTIC_EXCLUDE_WIFI
- String version = WiFiOTA::getVersion();
+ String version = MeshtasticOTA::getVersion();
if (version.isEmpty()) {
- LOG_INFO("No WiFi OTA firmware available");
+ LOG_INFO("MeshtasticOTA firmware not available");
} else {
- LOG_INFO("WiFi OTA firmware version %s", version.c_str());
+ LOG_INFO("MeshtasticOTA firmware version %s", version.c_str());
}
- WiFiOTA::initialize();
+ MeshtasticOTA::initialize();
#endif
// enableModemSleep();
diff --git a/variants/esp32/esp32-common.ini b/variants/esp32/esp32-common.ini
index 81a49223b..3ee2b9516 100644
--- a/variants/esp32/esp32-common.ini
+++ b/variants/esp32/esp32-common.ini
@@ -49,6 +49,7 @@ build_flags =
-DLIBPAX_BLE
-DHAS_UDP_MULTICAST=1
;-DDEBUG_HEAP
+ -DCAN_RECLOCK_I2C
lib_deps =
${arduino_base.lib_deps}
diff --git a/variants/esp32/heltec_wireless_bridge/platformio.ini b/variants/esp32/heltec_wireless_bridge/platformio.ini
index 93c3e3394..6f9de7a84 100644
--- a/variants/esp32/heltec_wireless_bridge/platformio.ini
+++ b/variants/esp32/heltec_wireless_bridge/platformio.ini
@@ -1,9 +1,9 @@
[env:heltec-wireless-bridge]
-;build_type = debug ; to make it possible to step through our jtag debugger
+;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base
board_level = extra
board = heltec_wifi_lora_32
-build_flags =
+build_flags =
${esp32_base.build_flags}
-I variants/esp32/heltec_wireless_bridge
-D HELTEC_WIRELESS_BRIDGE
@@ -13,6 +13,7 @@ build_flags =
-D MESHTASTIC_EXCLUDE_CANNEDMESSAGES=1
-D MESHTASTIC_EXCLUDE_DETECTIONSENSOR=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
+ -D MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1
-D MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1
-D MESHTASTIC_EXCLUDE_GPS=1
diff --git a/variants/esp32s3/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini
index 2033ccb59..e0f6f0760 100644
--- a/variants/esp32s3/elecrow_panel/platformio.ini
+++ b/variants/esp32s3/elecrow_panel/platformio.ini
@@ -44,7 +44,7 @@ lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio
earlephilhower/ESP8266Audio@1.9.9
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
- earlephilhower/ESP8266SAM@1.0.1
+ earlephilhower/ESP8266SAM@1.1.0
# renovate: datasource=custom.pio depName=TCA9534 packageName=hideakitai/library/TCA9534
hideakitai/TCA9534@0.1.1
lovyan03/LovyanGFX@1.2.0 ; note: v1.2.7 breaks the elecrow 7" display functionality
diff --git a/variants/esp32s3/t-watch-s3/variant.h b/variants/esp32s3/t-watch-s3/variant.h
index dfd219391..216dda589 100644
--- a/variants/esp32s3/t-watch-s3/variant.h
+++ b/variants/esp32s3/t-watch-s3/variant.h
@@ -53,10 +53,13 @@
#define HAS_BMA423 1
#define BMA4XX_INT 14 // Interrupt for BMA_423 axis sensor
+#define HAS_GPS 1
#define GPS_DEFAULT_NOT_PRESENT 1
#define GPS_BAUDRATE 38400
-#define GPS_RX_PIN 42
-#define GPS_TX_PIN 41
+#define GPS_RX_PIN 41
+#define GPS_TX_PIN 42
+
+#define BUTTON_PIN 0 // only for Plus version
#define USE_SX1262
#define USE_SX1268
diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini
index 3a7afb016..5973db1d0 100644
--- a/variants/esp32s3/tlora-pager/platformio.ini
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -33,7 +33,7 @@ lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=ESP8266Audio packageName=earlephilhower/library/ESP8266Audio
earlephilhower/ESP8266Audio@1.9.9
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
- earlephilhower/ESP8266SAM@1.0.1
+ earlephilhower/ESP8266SAM@1.1.0
# renovate: datasource=custom.pio depName=Adafruit DRV2605 packageName=adafruit/library/Adafruit DRV2605 Library
adafruit/Adafruit DRV2605 Library@1.2.4
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
@@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.3
# renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
- https://github.com/pschatzmann/arduino-audio-driver/archive/v0.1.3.zip
+ https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip
# TODO renovate
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
# TODO renovate
diff --git a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
index 984f967d8..e46391207 100644
--- a/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
+++ b/variants/nrf52840/ELECROW-ThinkNode-M6/variant.h
@@ -134,12 +134,14 @@ static const uint8_t A0 = PIN_A0;
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
-#define AREF_VOLTAGE 3.0
-#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
+#define AREF_VOLTAGE 2.4
+#define VBAT_AR_INTERNAL AR_INTERNAL_2_4
#define ADC_MULTIPLIER (1.75F)
#define HAS_SOLAR
+#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3450
+
#ifdef __cplusplus
}
#endif
diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
index c5af9a4a4..b4c0c958f 100644
--- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
+++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini
@@ -13,6 +13,7 @@ build_flags =
-DPIN_SERIAL1_RX=PB7
-DPIN_SERIAL1_TX=PB6
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
+ -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-DMESHTASTIC_EXCLUDE_I2C=1
-DMESHTASTIC_EXCLUDE_GPS=1
diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini
index b9a4b8a04..4d96e98f9 100644
--- a/variants/stm32/rak3172/platformio.ini
+++ b/variants/stm32/rak3172/platformio.ini
@@ -12,6 +12,7 @@ build_flags =
-DPIN_WIRE_SDA=PA11
-DPIN_WIRE_SCL=PA12
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
+ -DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-DMESHTASTIC_EXCLUDE_I2C=1
-DMESHTASTIC_EXCLUDE_GPS=1