diff --git a/platformio.ini b/platformio.ini
index c1df46746..b72d9b5b1 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -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/src/configuration.h b/src/configuration.h
index ec1b9acc2..be483b924 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -214,7 +214,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 +480,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..ceb894304 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -39,7 +39,7 @@ class ScanI2C
QMI8658,
QMC5883L,
HMC5883L,
- PMSA0031,
+ PMSA003I,
QMA6100P,
MPU6050,
LIS3DH,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 2be9212cf..202d73d84 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -442,7 +442,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..edcd0afb6
--- /dev/null
+++ b/src/detect/reClockI2C.h
@@ -0,0 +1,40 @@
+#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/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp
index d374ac0e3..e44798bc0 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -2247,7 +2247,8 @@ void menuHandler::FrameToggles_menu()
lora,
clock,
show_favorites,
- show_telemetry,
+ show_env_telemetry,
+ show_aq_telemetry,
show_power,
enumEnd
};
@@ -2292,8 +2293,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;
@@ -2356,10 +2360,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/main.cpp b/src/main.cpp
index 7e6488bd8..cdaf1ce37 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 != "") {
@@ -762,7 +763,6 @@ void setup()
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);
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..dff23abf1 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -1,36 +1,64 @@
#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"
#include "Default.h"
+#include "AirQualityTelemetry.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
-#include "detect/ScanI2CTwoWire.h"
+#include "UnitConversions.h"
+#include "graphics/SharedUIDisplay.h"
+#include "graphics/images.h"
+#include "graphics/ScreenFonts.h"
#include "main.h"
+#include "sleep.h"
#include
+#include "Sensor/AddI2CSensorTemplate.h"
-#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 +70,154 @@ 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 +244,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 +292,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 +315,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..af9c4ebc0 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..5d70ac308 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -143,34 +143,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#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;
- }
-}
+#include "Sensor/AddI2CSensorTemplate.h"
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
@@ -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..01aacc674
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/AddI2CSensorTemplate.h
@@ -0,0 +1,34 @@
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+
+#include
+#include "TelemetrySensor.h"
+#include "detect/ScanI2C.h"
+#include "detect/ScanI2CTwoWire.h"
+#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..467659efe
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
@@ -0,0 +1,164 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "PMSA003ISensor.h"
+#include "TelemetrySensor.h"
+#include "../detect/reClockI2C.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..47c8a05cc
--- /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..4a325aeed 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/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/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