From 94df0cb6368a98e64d61069fca000da42dd95c97 Mon Sep 17 00:00:00 2001 From: oscgonfer Date: Sun, 6 Jul 2025 19:25:46 +0200 Subject: [PATCH] SEN5X first pass --- platformio.ini | 2 + src/configuration.h | 1 + src/detect/ScanI2C.h | 1 + src/detect/ScanI2CTwoWire.cpp | 53 +++++++-- src/main.cpp | 2 +- src/modules/Telemetry/AirQualityTelemetry.cpp | 31 +++++ src/modules/Telemetry/Sensor/SEN5XSensor.cpp | 107 ++++++++++++++++++ src/modules/Telemetry/Sensor/SEN5XSensor.h | 43 +++++++ 8 files changed, 231 insertions(+), 9 deletions(-) create mode 100644 src/modules/Telemetry/Sensor/SEN5XSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/SEN5XSensor.h diff --git a/platformio.ini b/platformio.ini index b1f89e5b4..fe9b18ccc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -200,3 +200,5 @@ lib_deps = sensirion/Sensirion Core@0.7.1 # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x sensirion/Sensirion I2C SCD4x@1.1.0 + # renovate: datasource=custom.pio depName=Sensirion I2C SEN5X packageName=sensirion/library/Sensirion I2C SEN5X + sensirion/Sensirion I2C SEN5X \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index cddc7ba7a..97138d475 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -195,6 +195,7 @@ along with this program. If not, see . #define LTR390UV_ADDR 0x53 #define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418 #define PCT2075_ADDR 0x37 +#define SEN5X_ADDR 0x69 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index dd290db98..e25d7ce55 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -75,6 +75,7 @@ class ScanI2C TCA8418KB, PCT2075, BMM150, + SEN5X } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 9e9441123..87079cb14 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -8,6 +8,11 @@ #endif #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) #include "meshUtils.h" // vformat + +#define SEN50_NAME 48 +#define SEN54_NAME 52 +#define SEN55_NAME 53 + #endif bool in_array(uint8_t *array, int size, uint8_t lookfor) @@ -464,21 +469,53 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) } break; - case ICM20948_ADDR: // same as BMX160_ADDR + case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR + // ICM20948 Register check registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); if (registerValue == 0xEA) { type = ICM20948; logFoundDevice("ICM20948", (uint8_t)addr.address); break; - } else if (addr.address == BMX160_ADDR) { - type = BMX160; - logFoundDevice("BMX160", (uint8_t)addr.address); - break; } else { - type = MPU6050; - logFoundDevice("MPU6050", (uint8_t)addr.address); - break; + // TODO refurbish to find the model + // Just a hack for the hackathon + if (addr.address == SEN5X_ADDR) { + type = SEN5X; + logFoundDevice("SEN5X", (uint8_t)addr.address); + break; + } + + // We can get the 0xD014 register to find the model. This is not a simple task + // There is a buffer returned - getRegisterValue is not enough (maybe) + // registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD014), 6); + // Important to leave delay + // delay(50); + + // const uint8_t nameSize = 48; + // uint8_t name[nameSize] = ®isterValue; + + // switch(name[4]){ + // case SEN50_NAME: + // type = SEN50; + // break; + // case SEN54_NAME: + // type = SEN54; + // break; + // case SEN55_NAME: + // type = SEN55; + // break; + // } + + if (addr.address == BMX160_ADDR) { + type = BMX160; + logFoundDevice("BMX160", (uint8_t)addr.address); + break; + } else { + type = MPU6050; + logFoundDevice("MPU6050", (uint8_t)addr.address); + break; + } } break; diff --git a/src/main.cpp b/src/main.cpp index 640f0b1fe..0d23e6cd8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -696,7 +696,7 @@ void setup() scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::RAK12035, meshtastic_TelemetrySensorType_RAK12035); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::PCT2075, meshtastic_TelemetrySensorType_PCT2075); scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X); - + scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SEN5X, meshtastic_TelemetrySensorType_SEN5X); i2cScanner.reset(); #endif diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp index 7689802ea..9190850d0 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -24,6 +24,13 @@ PMSA003ISensor pmsa003iSensor; NullSensor pmsa003iSensor; #endif +#if __has_include() +#include "Sensor/SEN5XSensor.h" +SEN5XSensor sen5xSensor; +#else +NullSensor sen5xSensor; +#endif + int32_t AirQualityTelemetryModule::runOnce() { if (sleepOnNextExecution == true) { @@ -61,6 +68,9 @@ int32_t AirQualityTelemetryModule::runOnce() if (pmsa003iSensor.hasSensor()) result = pmsa003iSensor.runOnce(); + + if (sen5xSensor.hasSensor()) + result = sen5xSensor.runOnce(); } // it's possible to have this module enabled, only for displaying values on the screen. @@ -78,6 +88,11 @@ int32_t AirQualityTelemetryModule::runOnce() return pmsa003iSensor.wakeUp(); #endif /* PMSA003I_ENABLE_PIN */ +#ifdef SEN5X_ENABLE_PIN + if (sen5xSensor.hasSensor() && !sen5xSensor.isActive()) + return sen5xSensor.wakeUp(); +#endif /* SEN5X_ENABLE_PIN */ + if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.air_quality_interval, @@ -98,6 +113,10 @@ int32_t AirQualityTelemetryModule::runOnce() pmsa003iSensor.sleep(); #endif /* PMSA003I_ENABLE_PIN */ +#ifdef SEN5X_ENABLE_PIN + sen5xSensor.sleep(); +#endif /* SEN5X_ENABLE_PIN */ + } return min(sendToPhoneIntervalMs, result); } @@ -236,6 +255,13 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) hasSensor = true; } + if (sen5xSensor.hasSensor()) { + // TODO - Should we check for sensor state here? + // If a sensor is sleeping, we should know and check to wake it up + valid = valid && sen5xSensor.getMetrics(m); + hasSensor = true; + } + return valid && hasSensor; } @@ -329,6 +355,11 @@ AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule( return result; } + if (sen5xSensor.hasSensor()) { + result = sen5xSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } #endif return result; diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp new file mode 100644 index 000000000..b65b3e76d --- /dev/null +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp @@ -0,0 +1,107 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SEN5XSensor.h" +#include "TelemetrySensor.h" +#include + +SEN5XSensor::SEN5XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SEN5X, "SEN5X") {} + +int32_t SEN5XSensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + sen5x.begin(*nodeTelemetrySensorsMap[sensorType].second); + + delay(25); // without this there is an error on the deviceReset function (NOT WORKING) + + uint16_t error; + char errorMessage[256]; + error = sen5x.deviceReset(); + if (error) { + LOG_INFO("Error trying to execute deviceReset(): "); + errorToString(error, errorMessage, 256); + LOG_INFO(errorMessage); + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + error = sen5x.startMeasurement(); + if (error) { + LOG_INFO("Error trying to execute startMeasurement(): "); + errorToString(error, errorMessage, 256); + LOG_INFO(errorMessage); + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } else { + status = 1; + } + + return initI2CSensor(); +} + +void SEN5XSensor::setup() +{ +#ifdef SEN5X_ENABLE_PIN + pinMode(SEN5X_ENABLE_PIN, OUTPUT); +#endif /* SEN5X_ENABLE_PIN */ +} + +#ifdef SEN5X_ENABLE_PIN +void SEN5XSensor::sleep() { + digitalWrite(SEN5X_ENABLE_PIN, LOW); + state = State::IDLE; +} + +uint32_t SEN5XSensor::wakeUp() { + digitalWrite(SEN5X_ENABLE_PIN, HIGH); + state = State::ACTIVE; + return SEN5X_WARMUP_MS; +} +#endif /* SEN5X_ENABLE_PIN */ + +bool SEN5XSensor::isActive() { + return state == State::ACTIVE; +} + +bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + uint16_t error; + char errorMessage[256]; + + // Read Measurement + float massConcentrationPm1p0; + float massConcentrationPm2p5; + float massConcentrationPm4p0; + float massConcentrationPm10p0; + float ambientHumidity; + float ambientTemperature; + float vocIndex; + float noxIndex; + + error = sen5x.readMeasuredValues( + massConcentrationPm1p0, massConcentrationPm2p5, massConcentrationPm4p0, + massConcentrationPm10p0, ambientHumidity, ambientTemperature, vocIndex, + noxIndex); + + if (error) { + LOG_INFO("Error trying to execute readMeasuredValues(): "); + errorToString(error, errorMessage, 256); + LOG_INFO(errorMessage); + return false; + } + + measurement->variant.air_quality_metrics.has_pm10_standard = true; + measurement->variant.air_quality_metrics.pm10_standard = massConcentrationPm1p0; + measurement->variant.air_quality_metrics.has_pm25_standard = true; + measurement->variant.air_quality_metrics.pm25_standard = massConcentrationPm2p5; + measurement->variant.air_quality_metrics.has_pm100_standard = true; + measurement->variant.air_quality_metrics.pm100_standard = massConcentrationPm10p0; + + return true; +} + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.h b/src/modules/Telemetry/Sensor/SEN5XSensor.h new file mode 100644 index 000000000..f2b8321ee --- /dev/null +++ b/src/modules/Telemetry/Sensor/SEN5XSensor.h @@ -0,0 +1,43 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +#ifndef SEN5X_WARMUP_MS +// from the SEN5X datasheet +#define SEN5X_WARMUP_MS_SMALL 30000 +#endif + +class SEN5XSensor : public TelemetrySensor +{ + private: + SensirionI2CSen5x sen5x; + // PM25_AQI_Data pmsa003iData = {0}; + + protected: + virtual void setup() override; + + public: + enum State { + IDLE = 0, + ACTIVE = 1, + }; + +#ifdef SEN5X_ENABLE_PIN + void sleep(); + uint32_t wakeUp(); + State state = State::IDLE; +#else + State state = State::ACTIVE; +#endif + + SEN5XSensor(); + bool isActive(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file