SEN5X first pass

This commit is contained in:
oscgonfer 2025-07-06 19:25:46 +02:00 committed by Thomas Göttgens
parent 9604dadc45
commit 94df0cb636
8 changed files with 231 additions and 9 deletions

View File

@ -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

View File

@ -195,6 +195,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LTR390UV_ADDR 0x53
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418
#define PCT2075_ADDR 0x37
#define SEN5X_ADDR 0x69
// -----------------------------------------------------------------------------
// ACCELEROMETER

View File

@ -75,6 +75,7 @@ class ScanI2C
TCA8418KB,
PCT2075,
BMM150,
SEN5X
} DeviceType;
// typedef uint8_t DeviceAddress;

View File

@ -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] = &registerValue;
// 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;

View File

@ -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

View File

@ -24,6 +24,13 @@ PMSA003ISensor pmsa003iSensor;
NullSensor pmsa003iSensor;
#endif
#if __has_include(<SensirionI2CSen5x.h>)
#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;

View File

@ -0,0 +1,107 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<SensirionI2CSen5x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "SEN5XSensor.h"
#include "TelemetrySensor.h"
#include <SensirionI2CSen5x.h>
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

View File

@ -0,0 +1,43 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<SensirionI2CSen5x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SensirionI2CSen5x.h>
#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