diff --git a/platformio.ini b/platformio.ini index b5f08dd72..c15cd34ee 100644 --- a/platformio.ini +++ b/platformio.ini @@ -199,3 +199,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 0e24990b5..bc5aa3b01 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -199,6 +199,7 @@ along with this program. If not, see . #define BQ27220_ADDR 0x55 // same address as TDECK_KB #define BQ25896_ADDR 0x6B #define LTR553ALS_ADDR 0x23 +#define SEN5X_ADDR 0x69 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c1358861b..2d3cb1ae5 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -79,7 +79,8 @@ class ScanI2C BQ27220, LTR553ALS, BHI260AP, - BMM150 + BMM150, + SEN5X } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8b3670cd9..3644a765f 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) @@ -488,21 +493,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 c0276d6a6..c215fda26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -691,7 +691,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 df193c8a5..1eb125f58 100644 --- a/src/modules/Telemetry/AirQualityTelemetry.cpp +++ b/src/modules/Telemetry/AirQualityTelemetry.cpp @@ -24,6 +24,13 @@ PMSA003ISensor pmsa003iSensor; #include "graphics/ScreenFonts.h" +#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; } @@ -333,6 +359,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