From 8fa513ff1f479568caca28c026d3ae29b4531e6e Mon Sep 17 00:00:00 2001 From: Riley Nielsen Date: Sat, 31 Aug 2024 10:30:33 -0700 Subject: [PATCH 1/7] add CO2 sensing with SCD4X --- platformio.ini | 1 + src/configuration.h | 1 + src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 7 ++- src/main.cpp | 3 +- src/mesh/generated/meshtastic/telemetry.pb.h | 27 +++++---- .../Telemetry/EnvironmentTelemetry.cpp | 27 +++++++-- src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 57 +++++++++++++++++++ src/modules/Telemetry/Sensor/SCD4XSensor.h | 23 ++++++++ 9 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SCD4XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SCD4XSensor.h diff --git a/platformio.ini b/platformio.ini index 5c3c4e421..7935eb457 100644 --- a/platformio.ini +++ b/platformio.ini @@ -149,6 +149,7 @@ lib_deps = ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 + sensirion/Sensirion I2C SCD4x@^0.4.0 https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502 diff --git a/src/configuration.h b/src/configuration.h index 2e0efffd4..79f24453d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -139,6 +139,7 @@ along with this program. If not, see . #define MLX90632_ADDR 0x3A #define DFROBOT_LARK_ADDR 0x42 #define NAU7802_ADDR 0x2A +#define SCD4X_ADDR 0x62 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 711e8bee5..de5d2dc34 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -51,7 +51,8 @@ class ScanI2C AHT10, BMX160, DFROBOT_LARK, - NAU7802 + NAU7802, + SCD4X } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 1183d0ddc..85b0001b1 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -367,6 +367,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); + case SCD4X_ADDR: + type = SCD4X; + LOG_INFO("SCD4X CO2 sensor found\n"); + break; + default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); } @@ -404,4 +409,4 @@ size_t ScanI2CTwoWire::countDevices() const { return foundDevices.size(); } -#endif +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b73d9803b..9a5e24b43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -583,6 +583,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X) i2cScanner.reset(); #endif @@ -1155,4 +1156,4 @@ void loop() } // if (didWake) LOG_DEBUG("wake!\n"); } -#endif +#endif \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index cedc2867e..bfe4f3fcf 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -71,7 +71,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go) */ meshtastic_TelemetrySensorType_MAX17048 = 28, /* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor */ - meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29 + meshtastic_TelemetrySensorType_CUSTOM_SENSOR = 29, + /* SCD40/SCD41 CO2 sensor */ + meshtastic_TelemetrySensorType_SCD4X = 30 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -114,8 +116,8 @@ typedef struct _meshtastic_EnvironmentMetrics { /* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x) */ bool has_current; float current; - /* relative scale IAQ value as measured by Bosch BME680 . value 0-500. - Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ + /* relative scale IAQ value as measured by Bosch BME680. value 0-500. + Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here. */ bool has_iaq; uint16_t iaq; /* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */ @@ -134,7 +136,7 @@ typedef struct _meshtastic_EnvironmentMetrics { bool has_uv_lux; float uv_lux; /* Wind direction in degrees - 0 degrees = North, 90 = East, etc... */ + 0 degrees = North, 90 = East, etc... */ bool has_wind_direction; uint16_t wind_direction; /* Wind speed in m/s */ @@ -149,6 +151,9 @@ typedef struct _meshtastic_EnvironmentMetrics { /* Wind lull in m/s */ bool has_wind_lull; float wind_lull; + /* CO2 measured in ppm */ + bool has_co2; + uint16_t co2; } meshtastic_EnvironmentMetrics; /* Power Metrics (voltage / current / etc) */ @@ -267,8 +272,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_CUSTOM_SENSOR -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_CUSTOM_SENSOR+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SCD4X +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SCD4X+1)) @@ -280,14 +285,14 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_DeviceMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}} #define meshtastic_Nau7802Config_init_default {0, 0} #define meshtastic_DeviceMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0} -#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} +#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0} #define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0} @@ -317,6 +322,7 @@ extern "C" { #define meshtastic_EnvironmentMetrics_weight_tag 15 #define meshtastic_EnvironmentMetrics_wind_gust_tag 16 #define meshtastic_EnvironmentMetrics_wind_lull_tag 17 +#define meshtastic_EnvironmentMetrics_co2_tag 18 #define meshtastic_PowerMetrics_ch1_voltage_tag 1 #define meshtastic_PowerMetrics_ch1_current_tag 2 #define meshtastic_PowerMetrics_ch2_voltage_tag 3 @@ -379,7 +385,8 @@ X(a, STATIC, OPTIONAL, UINT32, wind_direction, 13) \ X(a, STATIC, OPTIONAL, FLOAT, wind_speed, 14) \ X(a, STATIC, OPTIONAL, FLOAT, weight, 15) \ X(a, STATIC, OPTIONAL, FLOAT, wind_gust, 16) \ -X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) +X(a, STATIC, OPTIONAL, FLOAT, wind_lull, 17) \ +X(a, STATIC, OPTIONAL, UINT32, co2, 18) #define meshtastic_EnvironmentMetrics_CALLBACK NULL #define meshtastic_EnvironmentMetrics_DEFAULT NULL @@ -473,4 +480,4 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg; } /* extern "C" */ #endif -#endif +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 4755a5be5..1a6233370 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -30,6 +30,7 @@ #include "Sensor/NAU7802Sensor.h" #include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" +#include "Sensor/SCD4XSensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" @@ -54,6 +55,7 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; +SCD4XSensor scd4xSensor; #ifdef T1000X_SENSOR_EN T1000xSensor t1000xSensor; #endif @@ -139,6 +141,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = mlx90632Sensor.runOnce(); if (nau7802Sensor.hasSensor()) result = nau7802Sensor.runOnce(); + if (scd4xSensor.hasSensor()) + result = scd4xSensor.runOnce(); #endif } return result; @@ -252,6 +256,10 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurement.variant.environment_metrics.weight != 0) display->drawString(x, y += _fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); + + if (lastMeasurement.variant.environment_metrics.co2 != 0) + display->drawString(x, y += _fontHeight(FONT_SMALL), + "CO2: " + String(lastMeasurement.variant.environment_metrics.co2) + " ppm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -268,9 +276,9 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); - LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", sender, + LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm\n", sender, t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction, - t->variant.environment_metrics.weight); + t->variant.environment_metrics.weight, t->variant.environment_metrics.co2); #endif // release previous packet before occupying a new spot @@ -383,6 +391,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } + if (scd4xSensor.hasSensor()) { + valid = valid && scd4xSensor.getMetrics(m); + hasSensor = true; + } #endif return valid && hasSensor; @@ -433,8 +445,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage, m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); - LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", m.variant.environment_metrics.wind_speed, - m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight); + LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm\n", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight, m.variant.environment_metrics.co2); sensor_read_error_count = 0; @@ -568,7 +580,12 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (scd4xSensor.hasSensor()) { + result = scd4xSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } return result; } -#endif \ No newline at end of file +#endif diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp new file mode 100644 index 000000000..a958de3fa --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -0,0 +1,57 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SCD4XSensor.h" +#include "TelemetrySensor.h" +#include + +SCD4XSensor::SCD4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD4X, "SCD4X") {} + +int32_t SCD4XSensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + // scd4x = SensirionI2CScd4x(nodeTelemetrySensorsMap[sensorType].second); + // status = scd4x.begin(nodeTelemetrySensorsMap[sensorType].first); + scd4x.begin(*nodeTelemetrySensorsMap[sensorType].second); + scd4x.stopPeriodicMeasurement(); + status = scd4x.startLowPowerPeriodicMeasurement(); + if (status == 0) { + status = 1; + } else { + status = 0; + } + return initI2CSensor(); +} + +void SCD4XSensor::setup() +{ + +} + +bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + uint16_t co2, error; + float temperature, humidity; + error = scd4x.readMeasurement( + co2, temperature, humidity + ); + if (error || co2 == 0) { + LOG_DEBUG("Skipping invalid SCD4X measurement.\n"); + return false; + } else { + measurement->variant.environment_metrics.has_temperature = true; + measurement->variant.environment_metrics.has_relative_humidity = true; + measurement->variant.environment_metrics.has_co2 = true; + measurement->variant.environment_metrics.temperature = temperature; + measurement->variant.environment_metrics.relative_humidity = humidity; + measurement->variant.environment_metrics.co2 = co2; + return true; + } +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.h b/src/modules/Telemetry/Sensor/SCD4XSensor.h new file mode 100644 index 000000000..0be607e1b --- /dev/null +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.h @@ -0,0 +1,23 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class SCD4XSensor : public TelemetrySensor +{ + private: + SensirionI2CScd4x scd4x = SensirionI2CScd4x(); + + protected: + virtual void setup() override; + + public: + SCD4XSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif From f599984990645a3d56da102652502973e612278b Mon Sep 17 00:00:00 2001 From: Riley Nielsen Date: Wed, 25 Sep 2024 18:34:17 -0700 Subject: [PATCH 2/7] update from upstream --- src/detect/ScanI2C.h | 3 ++- src/detect/ScanI2CTwoWire.cpp | 6 +---- src/main.cpp | 1 + .../Telemetry/EnvironmentTelemetry.cpp | 25 ++++++++++++++++--- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 3b49026ce..c86b61d2c 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -58,7 +58,8 @@ class ScanI2C NAU7802, FT6336U, STK8BAXX, - ICM20948 + ICM20948, + SCD4X } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 1dd800519..4242ef745 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -394,6 +394,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); + SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X CO2 sensor found\n"); case ICM20948_ADDR: // same as BMX160_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR @@ -413,11 +414,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - case SCD4X_ADDR: - type = SCD4X; - LOG_INFO("SCD4X CO2 sensor found\n"); - break; - default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); } diff --git a/src/main.cpp b/src/main.cpp index 87a4db97c..fa078845a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -576,6 +576,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X) i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index ac291c4ab..1fae63122 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -31,6 +31,7 @@ #include "Sensor/NAU7802Sensor.h" #include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" +#include "Sensor/SCD4XSensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" @@ -55,6 +56,7 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; +SCD4XSensor scd4xSensor; BMP3XXSensor bmp3xxSensor; #ifdef T1000X_SENSOR_EN T1000xSensor t1000xSensor; @@ -146,6 +148,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = nau7802Sensor.runOnce(); if (max17048Sensor.hasSensor()) result = max17048Sensor.runOnce(); + if (scd4xSensor.hasSensor()) + result = scd4xSensor.runOnce(); #endif } return result; @@ -258,6 +262,10 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurement.variant.environment_metrics.weight != 0) display->drawString(x, y += _fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); + + if (lastMeasurement.variant.environment_metrics.co2 != 0) + display->drawString(x, y += _fontHeight(FONT_SMALL), + "CO2: " + String(lastMeasurement.variant.environment_metrics.co2) + " ppm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -274,9 +282,9 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); - LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", sender, + LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm\n", sender, t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction, - t->variant.environment_metrics.weight); + t->variant.environment_metrics.weight, t->variant.environment_metrics.co2); #endif // release previous packet before occupying a new spot @@ -403,6 +411,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && max17048Sensor.getMetrics(m); hasSensor = true; } + if (scd4xSensor.hasSensor()) { + valid = valid && scd4xSensor.getMetrics(m); + hasSensor = true; + } #endif return valid && hasSensor; @@ -453,8 +465,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage, m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); - LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", m.variant.environment_metrics.wind_speed, - m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight); + LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm\n", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight, m.variant.environment_metrics.co2); sensor_read_error_count = 0; @@ -598,6 +610,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (scd4xSensor.hasSensor()) { + result = scd4xSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } return result; } From 318da220dc08d4bfc4984ac9ff8371fdf04d19dd Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 18 Oct 2024 18:16:45 +1100 Subject: [PATCH 3/7] Abstract AirQualityTelemetry module Previously the module was designed around a single sensor. This patch brings it into line with the same design as EnvironmentTelemetry. --- src/modules/Telemetry/AirQualityTelemetry.cpp | 139 ++++++++++++------ src/modules/Telemetry/AirQualityTelemetry.h | 16 +- .../Telemetry/EnvironmentTelemetry.cpp | 25 +--- .../Telemetry/Sensor/PMSA0031Sensor.cpp | 47 ++++++ src/modules/Telemetry/Sensor/PMSA0031Sensor.h | 24 +++ src/modules/Telemetry/Sensor/SCD4XSensor.cpp | 13 +- src/modules/Telemetry/Sensor/SCD4XSensor.h | 2 +- 7 files changed, 186 insertions(+), 80 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/PMSA0031Sensor.cpp create mode 100644 src/modules/Telemetry/Sensor/PMSA0031Sensor.h diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 594707537..6d3e58d07 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -10,12 +10,32 @@ #include "PowerFSM.h" #include "RTC.h" #include "Router.h" +#include "UnitConversions.h" #include "detect/ScanI2CTwoWire.h" +#include "graphics/ScreenFonts.h" #include "main.h" +#include "sleep.h" #include +// Sensors +#include "Sensor/PMSA0031Sensor.h" +#include "Sensor/SCD4XSensor.h" + +SCD4XSensor scd4xSensor; +PMSA0031Sensor pmsa0031Sensor; + int32_t AirQualityTelemetryModule::runOnce() { + if (sleepOnNextExecution == true) { + sleepOnNextExecution = false; + uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval, + default_telemetry_broadcast_interval_secs); + LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs); + doDeepSleep(nightyNightMs, true); + } + + uint32_t result = UINT32_MAX; + /* Uncomment the preferences below if you want to use the module without having to configure it from the PythonAPI or WebUI. @@ -34,28 +54,13 @@ int32_t AirQualityTelemetryModule::runOnce() if (moduleConfig.telemetry.air_quality_enabled) { LOG_INFO("Air quality Telemetry: Initializing"); - if (!aqi.begin_I2C()) { - LOG_WARN("Could not establish i2c connection to AQI sensor. Rescanning..."); - // 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 1000; - } - return disable(); - } - return 1000; + + if (scd4xSensor.hasSensor()) + result = scd4xSensor.runOnce(); + if (pmsa0031Sensor.hasSensor()) + result = pmsa0031Sensor.runOnce(); } - return disable(); + return result; } 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) @@ -69,13 +74,15 @@ int32_t AirQualityTelemetryModule::runOnce() airTime->isTxAllowedAirUtil()) { sendTelemetry(); lastSentToMesh = millis(); - } else if (service->isToPhoneQueueEmpty()) { + } 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(); } } - return sendToPhoneIntervalMs; + return min(sendToPhoneIntervalMs, result); } bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -84,9 +91,9 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack #ifdef DEBUG_PORT const char *sender = getSenderShortName(mp); - LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender, + LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i, co2=%i ppm", sender, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, - t->variant.air_quality_metrics.pm100_standard); + t->variant.air_quality_metrics.pm100_standard, t->variant.air_quality_metrics.co2); LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental, @@ -102,32 +109,68 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack return false; // Let others look at this message also if they want } -bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) +bool AirQualityTelemetryModule::wantUIFrame() { - if (!aqi.read(&data)) { - LOG_WARN("Skipping send measurements. Could not read AQIn"); - return false; + return moduleConfig.telemetry.environment_screen_enabled; +} + +void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + + if (lastMeasurementPacket == nullptr) { + // If there's no valid packet, display "Environment" + display->drawString(x, y, "Air Quality"); + display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement"); + return; } + // Decode the last measurement packet + meshtastic_Telemetry lastMeasurement; + uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket); + const char *lastSender = getSenderShortName(*lastMeasurementPacket); + + const meshtastic_Data &p = lastMeasurementPacket->decoded; + if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) { + display->drawString(x, y, "Measurement Error"); + LOG_ERROR("Unable to decode last packet"); + return; + } + + // Display "Env. From: ..." on its own + display->drawString(x, y, "AQ. From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + + // Continue with the remaining details + /*display->drawString(x, y += _fontHeight(FONT_SMALL), + "Temp/Hum: " + last_temp + " / " + + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%");*/ + + /*if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); + }*/ +} + +bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) +{ + // XXX + bool valid = true; + bool hasSensor = false; m->time = getTime(); m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; - m->variant.air_quality_metrics.pm10_standard = data.pm10_standard; - m->variant.air_quality_metrics.pm25_standard = data.pm25_standard; - m->variant.air_quality_metrics.pm100_standard = data.pm100_standard; + m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero; - m->variant.air_quality_metrics.pm10_environmental = data.pm10_env; - m->variant.air_quality_metrics.pm25_environmental = data.pm25_env; - m->variant.air_quality_metrics.pm100_environmental = data.pm100_env; + if (scd4xSensor.hasSensor()) { + valid = valid && scd4xSensor.getMetrics(m); + hasSensor = true; + } + if (pmsa0031Sensor.hasSensor()) { + valid = valid && pmsa0031Sensor.getMetrics(m); + hasSensor = true; + } - LOG_INFO("(Sending): 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() @@ -162,6 +205,14 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) { meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; if (getAirQualityTelemetry(&m)) { + LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i, cO2=%i ppm", + 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.co2); + + 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); + meshtastic_MeshPacket *p = allocDataProtobuf(m); p->to = dest; p->decoded.want_response = false; diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h index fb8edd07e..0387463f5 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.h +++ b/src/modules/Telemetry/AirQualityTelemetry.h @@ -4,9 +4,10 @@ #pragma once #include "../mesh/generated/meshtastic/telemetry.pb.h" -#include "Adafruit_PM25AQI.h" #include "NodeDB.h" #include "ProtobufModule.h" +#include +#include class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule { @@ -20,10 +21,15 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) { lastMeasurementPacket = nullptr; - setIntervalFromNow(10 * 1000); - aqi = Adafruit_PM25AQI(); nodeStatusObserver.observe(&nodeStatus->onNewStatus); + 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 @@ -42,12 +48,12 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); private: - Adafruit_PM25AQI aqi; - PM25_AQI_Data data = {0}; 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; + uint32_t sensor_read_error_count = 0; }; #endif \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 8891cec79..4b7fbb856 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -32,7 +32,6 @@ #include "Sensor/NAU7802Sensor.h" #include "Sensor/OPT3001Sensor.h" #include "Sensor/RCWL9620Sensor.h" -#include "Sensor/SCD4XSensor.h" #include "Sensor/SHT31Sensor.h" #include "Sensor/SHT4XSensor.h" #include "Sensor/SHTC3Sensor.h" @@ -57,7 +56,6 @@ AHT10Sensor aht10Sensor; MLX90632Sensor mlx90632Sensor; DFRobotLarkSensor dfRobotLarkSensor; NAU7802Sensor nau7802Sensor; -SCD4XSensor scd4xSensor; BMP3XXSensor bmp3xxSensor; #ifdef T1000X_SENSOR_EN T1000xSensor t1000xSensor; @@ -149,8 +147,6 @@ int32_t EnvironmentTelemetryModule::runOnce() result = nau7802Sensor.runOnce(); if (max17048Sensor.hasSensor()) result = max17048Sensor.runOnce(); - if (scd4xSensor.hasSensor()) - result = scd4xSensor.runOnce(); #endif } return result; @@ -247,10 +243,6 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt if (lastMeasurement.variant.environment_metrics.weight != 0) display->drawString(x, y += _fontHeight(FONT_SMALL), "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg"); - - if (lastMeasurement.variant.environment_metrics.co2 != 0) - display->drawString(x, y += _fontHeight(FONT_SMALL), - "CO2: " + String(lastMeasurement.variant.environment_metrics.co2) + " ppm"); } bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -266,9 +258,9 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac t->variant.environment_metrics.temperature); LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f", sender, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux); - LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm", sender, + LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg", sender, t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction, - t->variant.environment_metrics.weight, t->variant.environment_metrics.co2); + t->variant.environment_metrics.weight); #endif // release previous packet before occupying a new spot @@ -395,10 +387,6 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && max17048Sensor.getMetrics(m); hasSensor = true; } - if (scd4xSensor.hasSensor()) { - valid = valid && scd4xSensor.getMetrics(m); - hasSensor = true; - } #endif return valid && hasSensor; @@ -449,8 +437,8 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly) LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f", m.variant.environment_metrics.voltage, m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux); - LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg, co2=%d ppm", m.variant.environment_metrics.wind_speed, - m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight, m.variant.environment_metrics.co2); + LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg", m.variant.environment_metrics.wind_speed, + m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight); sensor_read_error_count = 0; @@ -594,11 +582,6 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } - if (scd4xSensor.hasSensor()) { - result = scd4xSensor.handleAdminMessage(mp, request, response); - if (result != AdminMessageHandleResult::NOT_HANDLED) - return result; - } return result; } diff --git a/src/modules/Telemetry/Sensor/PMSA0031Sensor.cpp b/src/modules/Telemetry/Sensor/PMSA0031Sensor.cpp new file mode 100644 index 000000000..d2e032f60 --- /dev/null +++ b/src/modules/Telemetry/Sensor/PMSA0031Sensor.cpp @@ -0,0 +1,47 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "PMSA0031Sensor.h" +#include "TelemetrySensor.h" +#include + +PMSA0031Sensor::PMSA0031Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA0031") {} + +int32_t PMSA0031Sensor::runOnce() +{ + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + aqi = Adafruit_PM25AQI(); + delay(10000); + aqi.begin_I2C(); + /* nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address; + nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second = + i2cScanner->fetchI2CBus(found.address););*/ + return initI2CSensor(); +} + +void PMSA0031Sensor::setup() {} + +bool PMSA0031Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + uint16_t co2, error; + float temperature, humidity; + if (!aqi.read(&data)) { + LOG_WARN("Skipping send measurements. Could not read AQIn"); + return false; + } + measurement->variant.air_quality_metrics.pm10_standard = data.pm10_standard; + measurement->variant.air_quality_metrics.pm25_standard = data.pm25_standard; + measurement->variant.air_quality_metrics.pm100_standard = data.pm100_standard; + + measurement->variant.air_quality_metrics.pm10_environmental = data.pm10_env; + measurement->variant.air_quality_metrics.pm25_environmental = data.pm25_env; + measurement->variant.air_quality_metrics.pm100_environmental = data.pm100_env; + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/PMSA0031Sensor.h b/src/modules/Telemetry/Sensor/PMSA0031Sensor.h new file mode 100644 index 000000000..880766007 --- /dev/null +++ b/src/modules/Telemetry/Sensor/PMSA0031Sensor.h @@ -0,0 +1,24 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class PMSA0031Sensor : public TelemetrySensor +{ + private: + Adafruit_PM25AQI aqi; + PM25_AQI_Data data = {0}; + + protected: + virtual void setup() override; + + public: + PMSA0031Sensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp index a958de3fa..1fdfacecf 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp @@ -28,28 +28,23 @@ int32_t SCD4XSensor::runOnce() return initI2CSensor(); } -void SCD4XSensor::setup() -{ - -} +void SCD4XSensor::setup() {} bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement) { uint16_t co2, error; float temperature, humidity; - error = scd4x.readMeasurement( - co2, temperature, humidity - ); + error = scd4x.readMeasurement(co2, temperature, humidity); if (error || co2 == 0) { LOG_DEBUG("Skipping invalid SCD4X measurement.\n"); return false; } else { measurement->variant.environment_metrics.has_temperature = true; measurement->variant.environment_metrics.has_relative_humidity = true; - measurement->variant.environment_metrics.has_co2 = true; + measurement->variant.air_quality_metrics.has_co2 = true; measurement->variant.environment_metrics.temperature = temperature; measurement->variant.environment_metrics.relative_humidity = humidity; - measurement->variant.environment_metrics.co2 = co2; + measurement->variant.air_quality_metrics.co2 = co2; return true; } } diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.h b/src/modules/Telemetry/Sensor/SCD4XSensor.h index 0be607e1b..26141f57e 100644 --- a/src/modules/Telemetry/Sensor/SCD4XSensor.h +++ b/src/modules/Telemetry/Sensor/SCD4XSensor.h @@ -20,4 +20,4 @@ class SCD4XSensor : public TelemetrySensor virtual bool getMetrics(meshtastic_Telemetry *measurement) override; }; -#endif +#endif \ No newline at end of file From 8faf4665b0561193d7723c838b496747ad8911c3 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 19 Oct 2024 13:00:25 +1100 Subject: [PATCH 4/7] Re-add I2C scan, fix frame. --- src/detect/ScanI2C.cpp | 6 ++ src/detect/ScanI2C.h | 2 + src/main.cpp | 7 +- src/main.h | 1 + src/modules/Telemetry/AirQualityTelemetry.cpp | 87 ++++++++++++------- 5 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index a9d70edaa..14d893f31 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -41,6 +41,12 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const return firstOfOrNONE(7, types); } +ScanI2C::FoundDevice ScanI2C::firstAQI() const +{ + ScanI2C::DeviceType types[] = {PMSA0031, SCD4X}; + return firstOfOrNONE(2, types); +} + ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const { return DEVICE_NONE; diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 05995b7fd..abfe261d4 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -115,6 +115,8 @@ class ScanI2C FoundDevice firstAccelerometer() const; + FoundDevice firstAQI() const; + virtual FoundDevice find(DeviceType) const; virtual bool exists(DeviceType) const; diff --git a/src/main.cpp b/src/main.cpp index 0205a17e0..f021cb986 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -150,6 +150,8 @@ ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE; ScanI2C::DeviceAddress accelerometer_found = ScanI2C::ADDRESS_NONE; // The I2C address of the RGB LED (if found) ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ScanI2C::ADDRESS_NONE); +/// The I2C address of our Air Quality Indicator (if found) +ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) ATECCX08A atecc; @@ -532,6 +534,9 @@ void setup() pmu_found = i2cScanner->exists(ScanI2C::DeviceType::PMU_AXP192_AXP2101); + auto aqiInfo = i2cScanner->firstAQI(); + aqi_found = aqiInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE; + /* * There are a bunch of sensors that have no further logic than to be found and stuffed into the * nodeTelemetrySensorsMap singleton. This wraps that logic in a temporary scope to declare the temporary field @@ -1187,4 +1192,4 @@ void loop() mainDelay.delay(delayMsec); } } -#endif +#endif \ No newline at end of file diff --git a/src/main.h b/src/main.h index 5722f7cf0..432585034 100644 --- a/src/main.h +++ b/src/main.h @@ -33,6 +33,7 @@ extern uint8_t kb_model; extern ScanI2C::DeviceAddress rtc_found; extern ScanI2C::DeviceAddress accelerometer_found; extern ScanI2C::FoundDevice rgb_found; +extern ScanI2C::DeviceAddress aqi_found; extern bool eink_found; extern bool pmu_found; diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 6d3e58d07..7ad859528 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -40,7 +40,6 @@ int32_t AirQualityTelemetryModule::runOnce() Uncomment the preferences below if you want to use the module without having to configure it from the PythonAPI or WebUI. */ - // moduleConfig.telemetry.air_quality_enabled = 1; if (!(moduleConfig.telemetry.air_quality_enabled)) { @@ -55,34 +54,54 @@ int32_t AirQualityTelemetryModule::runOnce() if (moduleConfig.telemetry.air_quality_enabled) { LOG_INFO("Air quality Telemetry: Initializing"); + if (aqi_found.address == 0x00) { + LOG_DEBUG("Rescanning for I2C AQI Sensor"); + uint8_t i2caddr_scan[] = {PMSA0031_ADDR, SCD4X_ADDR}; + uint8_t i2caddr_asize = 2; + auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); + +#if WIRE_INTERFACES_COUNT == 2 + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); +#endif + i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); + auto aqi_info = i2cScanner->firstAQI(); + + if (aqi_info.type != ScanI2C::DeviceType::NONE) { + aqi_found = aqi_info.address; + } + if (aqi_found.address == 0x00) { + return disable(); + } + } if (scd4xSensor.hasSensor()) result = scd4xSensor.runOnce(); if (pmsa0031Sensor.hasSensor()) result = pmsa0031Sensor.runOnce(); - } - return result; - } 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(); + return result; - 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(); + } 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(); + + 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(); + } } + return min(sendToPhoneIntervalMs, result); } - return min(sendToPhoneIntervalMs, result); } bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t) @@ -141,20 +160,26 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta // Display "Env. From: ..." on its own display->drawString(x, y, "AQ. From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); - // Continue with the remaining details - /*display->drawString(x, y += _fontHeight(FONT_SMALL), - "Temp/Hum: " + last_temp + " / " + - String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%");*/ - - /*if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) { + if (lastMeasurement.variant.air_quality_metrics.has_pm10_standard) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA"); - }*/ + "PM1.0(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm10_standard, 0)); + } + if (lastMeasurement.variant.air_quality_metrics.has_pm25_standard) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "PM2.5(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm25_standard, 0)); + } + if (lastMeasurement.variant.air_quality_metrics.has_pm10_environmental) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "PM10.0(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm100_standard, 0)); + } + if (lastMeasurement.variant.air_quality_metrics.has_co2) { + display->drawString(x, y += _fontHeight(FONT_SMALL), + "CO2: " + String(lastMeasurement.variant.air_quality_metrics.co2, 0) + " ppm"); + } } bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) { - // XXX bool valid = true; bool hasSensor = false; m->time = getTime(); From 7fe9efe80551efb67c2493f274f01c8a98857e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 7 Apr 2025 10:03:40 +0200 Subject: [PATCH 5/7] Wupps --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 9532cbfe9..dd80c239e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -136,6 +136,6 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip https://github.com/DFRobot/DFRobot_RainfallSensor/archive/38fea5e02b40a5430be6dab39a99a6f6347d667e.zip robtillaart/INA226@0.6.4 - https://github.com/gjelsoe/STK8xxx-Accelerometer/archive#v0.1.1.zip + https://github.com/gjelsoe/STK8xxx-Accelerometer/archive/v0.1.1.zip ; Health Sensor Libraries sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2 From 58d916da39854ea43cedebd01dcfa485ae9b94ef Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 1 Jul 2025 19:49:42 +1000 Subject: [PATCH 6/7] Fix merge --- platformio.ini | 1 - src/configuration.h | 1 - src/detect/ScanI2CTwoWire.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index a6c0ef5cb..c7b728d6a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -146,7 +146,6 @@ lib_deps = adafruit/Adafruit TSL2591 Library@1.4.5 # renovate: datasource=custom.pio depName=EmotiBit MLX90632 packageName=emotibit/library/EmotiBit MLX90632 emotibit/EmotiBit MLX90632@1.0.8 - sensirion/Sensirion I2C SCD4x@^0.4.0 # renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library adafruit/Adafruit MLX90614 Library@2.1.5 # renovate: datasource=github-tags depName=INA3221 packageName=KodinLanewave/INA3221 diff --git a/src/configuration.h b/src/configuration.h index 990a57b26..cddc7ba7a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -188,7 +188,6 @@ along with this program. If not, see . #define DFROBOT_LARK_ADDR 0x42 #define DFROBOT_RAIN_ADDR 0x1d #define NAU7802_ADDR 0x2A -#define SCD4X_ADDR 0x62 #define MAX30102_ADDR 0x57 #define SCD4X_ADDR 0x62 #define MLX90614_ADDR_DEF 0x5A diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 7cd382f7e..9e9441123 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -446,7 +446,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048", (uint8_t)addr.address); SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address); SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address); - SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address); SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address); SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address); From cfc355b29af81fa42d1efc8496feb4219d8f14e9 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 1 Jul 2025 21:31:23 +1000 Subject: [PATCH 7/7] Update main.cpp --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 34dbadc21..9e0985a3a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -693,7 +693,6 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DFROBOT_RAIN, meshtastic_TelemetrySensorType_DFROBOT_RAIN); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::LTR390UV, meshtastic_TelemetrySensorType_LTR390UV); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::DPS310, meshtastic_TelemetrySensorType_DPS310); - scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);