From 8fa513ff1f479568caca28c026d3ae29b4531e6e Mon Sep 17 00:00:00 2001 From: Riley Nielsen Date: Sat, 31 Aug 2024 10:30:33 -0700 Subject: [PATCH] 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