Working implementation on VOCState

* Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h)
* Adds conditions for stability and sensor state
This commit is contained in:
oscgonfer 2025-08-30 13:00:22 +02:00
parent d583991248
commit 1038765219
3 changed files with 172 additions and 89 deletions

View File

@ -79,13 +79,16 @@ int32_t AirQualityTelemetryModule::runOnce()
} }
// Wake up the sensors that need it, before we need to take telemetry data // Wake up the sensors that need it, before we need to take telemetry data
if ((lastSentToMesh == 0) || // TODO - Do it for SENSOR ROLE too?
if (((lastSentToMesh == 0) ||
(sen5xSensor.hasSensor() && !Throttle::isWithinTimespanMs(lastSentToMesh - SEN5X_WARMUP_MS_1, Default::getConfiguredOrDefaultMsScaled( (sen5xSensor.hasSensor() && !Throttle::isWithinTimespanMs(lastSentToMesh - SEN5X_WARMUP_MS_1, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval, moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) || default_telemetry_broadcast_interval_secs, numOnlineNodes))) ||
(pmsa003iSensor.hasSensor() && !Throttle::isWithinTimespanMs(lastSentToMesh - PMSA003I_WARMUP_MS, Default::getConfiguredOrDefaultMsScaled( (pmsa003iSensor.hasSensor() && !Throttle::isWithinTimespanMs(lastSentToMesh - PMSA003I_WARMUP_MS, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval, moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes)))) { default_telemetry_broadcast_interval_secs, numOnlineNodes)))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
if (sen5xSensor.hasSensor() && !sen5xSensor.isActive()) if (sen5xSensor.hasSensor() && !sen5xSensor.isActive())
return sen5xSensor.wakeUp(); return sen5xSensor.wakeUp();
@ -100,10 +103,11 @@ int32_t AirQualityTelemetryModule::runOnce()
if (sen5xSensor.hasSensor() && sen5xSensor.isActive()) { if (sen5xSensor.hasSensor() && sen5xSensor.isActive()) {
sen5xPendingForReady = sen5xSensor.pendingForReady(); sen5xPendingForReady = sen5xSensor.pendingForReady();
LOG_DEBUG("SEN5X: Pending for ready %ums", sen5xPendingForReady); LOG_DEBUG("SEN5X: Pending for ready %ums", sen5xPendingForReady);
if (sen5xPendingForReady) { if (sen5xPendingForReady > 0) {
return sen5xPendingForReady; return sen5xPendingForReady;
} }
} }
LOG_DEBUG("Checking if sending telemetry");
if (((lastSentToMesh == 0) || if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
@ -122,6 +126,7 @@ int32_t AirQualityTelemetryModule::runOnce()
} }
// Send the sensor to idle ONLY if there is enough time to wake it up before the next reading cycle // Send the sensor to idle ONLY if there is enough time to wake it up before the next reading cycle
// TODO - include conditions here for module timing
#ifdef PMSA003I_ENABLE_PIN #ifdef PMSA003I_ENABLE_PIN
if (pmsa003iSensor.hasSensor() && pmsa003iSensor.isActive()) { if (pmsa003iSensor.hasSensor() && pmsa003iSensor.isActive()) {
if (PMSA003I_WARMUP_MS < Default::getConfiguredOrDefaultMsScaled( if (PMSA003I_WARMUP_MS < Default::getConfiguredOrDefaultMsScaled(
@ -322,8 +327,10 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m.time = getTime(); m.time = getTime();
// TODO - if one sensor fails here, we will stop taking measurements from everything // TODO - if one sensor fails here, we will stop taking measurements from everything
// Can we do this in a smarter way, for instance checking the nodeTelemetrySensor map and making it dynamic? // Can we do this in a smarter way, for instance checking the nodeTelemetrySensor map and making it dynamic?
if (getAirQualityTelemetry(&m)) { if (getAirQualityTelemetry(&m)) {
LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u",
m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,

View File

@ -218,25 +218,36 @@ bool SEN5XSensor::I2Cdetect(TwoWire *_Wire, uint8_t address)
bool SEN5XSensor::idle() bool SEN5XSensor::idle()
{ {
// From the datasheet:
// By default, the VOC algorithm resets its state to initial
// values each time a measurement is started,
// even if the measurement was stopped only for a short
// time. So, the VOC index output value needs a long time
// until it is stable again. This can be avoided by
// restoring the previously memorized algorithm state before
// starting the measure mode
// If the stabilisation period is not passed for SEN55, don't go to idle
// Get VOC state before going to idle mode if (model == SEN55) {
if (vocStateFromSensor()) { // Get VOC state before going to idle mode
// TODO Should this be saved with saveState()?
// It so, we can consider not saving it when rebooting as
// we would have likely saved it recently
// Check if we have time, and store it
uint32_t now; // If time is RTCQualityNone, it will return zero
now = getValidTime(RTCQuality::RTCQualityDevice);
if (now) {
vocTime = now;
vocValid = true;
// saveState();
}
} else {
vocValid = false; vocValid = false;
if (vocStateFromSensor()) {
vocValid = vocStateValid();
// Check if we have time, and store it
uint32_t now; // If time is RTCQualityNone, it will return zero
now = getValidTime(RTCQuality::RTCQualityDevice);
if (now) {
// Check if state is valid (non-zero)
vocTime = now;
}
}
if (vocStateStable() && vocValid) {
saveState();
} else {
LOG_INFO("SEN5X: Not stopping measurement, vocState is not stable yet!");
return true;
}
} }
if (!oneShotMode) { if (!oneShotMode) {
@ -248,8 +259,8 @@ bool SEN5XSensor::idle()
LOG_ERROR("SEN5X: Error stoping measurement"); LOG_ERROR("SEN5X: Error stoping measurement");
return false; return false;
} }
delay(200); // From Sensirion Datasheet
delay(200); // From Sensirion Datasheet
LOG_INFO("SEN5X: Stop measurement mode"); LOG_INFO("SEN5X: Stop measurement mode");
state = SEN5X_IDLE; state = SEN5X_IDLE;
@ -257,12 +268,40 @@ bool SEN5XSensor::idle()
return true; return true;
} }
bool SEN5XSensor::vocStateRecent(uint32_t now){
if (now) {
uint32_t passed = now - vocTime; //in seconds
// Check if state is recent, less than 10 minutes (600 seconds)
if (passed < SEN5X_VOC_VALID_TIME && (now > SEN5X_VOC_VALID_DATE)) {
return true;
}
}
return false;
}
bool SEN5XSensor::vocStateValid() {
if (!vocState[0] && !vocState[1] && !vocState[2] && !vocState[3] &&
!vocState[4] && !vocState[5] && !vocState[6] && !vocState[7]) {
LOG_DEBUG("SEN5X: VOC state is all 0, invalid");
return false;
} else {
LOG_DEBUG("SEN5X: VOC state is valid");
return true;
}
}
bool SEN5XSensor::vocStateToSensor() bool SEN5XSensor::vocStateToSensor()
{ {
if (model != SEN55){ if (model != SEN55){
return true; return true;
} }
if (!vocStateValid()) {
LOG_INFO("SEN5X: VOC state is invalid, not sending");
return true;
}
if (!sendCommand(SEN5X_STOP_MEASUREMENT)) { if (!sendCommand(SEN5X_STOP_MEASUREMENT)) {
LOG_ERROR("SEN5X: Error stoping measurement"); LOG_ERROR("SEN5X: Error stoping measurement");
return false; return false;
@ -320,8 +359,7 @@ bool SEN5XSensor::vocStateFromSensor()
vocState[7] = vocBuffer[10]; vocState[7] = vocBuffer[10];
// Print the state (if debug is on) // Print the state (if debug is on)
LOG_DEBUG("SEN5X: VOC state retrieved from sensor"); LOG_DEBUG("SEN5X: VOC state retrieved from sensor: [%u, %u, %u, %u, %u, %u, %u, %u]",
LOG_DEBUG("[%u, %u, %u, %u, %u, %u, %u, %u]",
vocState[0],vocState[1], vocState[2], vocState[3], vocState[0],vocState[1], vocState[2], vocState[3],
vocState[4],vocState[5], vocState[6], vocState[7]); vocState[4],vocState[5], vocState[6], vocState[7]);
@ -337,24 +375,36 @@ bool SEN5XSensor::loadState()
if (file) { if (file) {
LOG_INFO("%s state read from %s", sensorName, sen5XStateFileName); LOG_INFO("%s state read from %s", sensorName, sen5XStateFileName);
pb_istream_t stream = {&readcb, &file, meshtastic_SEN5XState_size}; pb_istream_t stream = {&readcb, &file, meshtastic_SEN5XState_size};
if (!pb_decode(&stream, &meshtastic_SEN5XState_msg, &sen5xstate)) { if (!pb_decode(&stream, &meshtastic_SEN5XState_msg, &sen5xstate)) {
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream)); LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
} else { } else {
lastCleaning = sen5xstate.last_cleaning_time; lastCleaning = sen5xstate.last_cleaning_time;
lastCleaningValid = sen5xstate.last_cleaning_valid; lastCleaningValid = sen5xstate.last_cleaning_valid;
oneShotMode = sen5xstate.one_shot_mode; oneShotMode = sen5xstate.one_shot_mode;
// Unpack state
vocState[7] = (uint8_t)(sen5xstate.voc_state >> 56);
vocState[6] = (uint8_t)(sen5xstate.voc_state >> 48);
vocState[5] = (uint8_t)(sen5xstate.voc_state >> 40);
vocState[4] = (uint8_t)(sen5xstate.voc_state >> 32);
vocState[3] = (uint8_t)(sen5xstate.voc_state >> 24);
vocState[2] = (uint8_t)(sen5xstate.voc_state >> 16);
vocState[1] = (uint8_t)(sen5xstate.voc_state >> 8);
vocState[0] = (uint8_t)sen5xstate.voc_state;
vocTime = sen5xstate.voc_time; if (model == SEN55) {
vocValid = sen5xstate.voc_valid; vocTime = sen5xstate.voc_state_time;
vocValid = sen5xstate.voc_state_valid;
// Unpack state
vocState[7] = (uint8_t)(sen5xstate.voc_state_array >> 56);
vocState[6] = (uint8_t)(sen5xstate.voc_state_array >> 48);
vocState[5] = (uint8_t)(sen5xstate.voc_state_array >> 40);
vocState[4] = (uint8_t)(sen5xstate.voc_state_array >> 32);
vocState[3] = (uint8_t)(sen5xstate.voc_state_array >> 24);
vocState[2] = (uint8_t)(sen5xstate.voc_state_array >> 16);
vocState[1] = (uint8_t)(sen5xstate.voc_state_array >> 8);
vocState[0] = (uint8_t) sen5xstate.voc_state_array;
}
// LOG_DEBUG("Loaded lastCleaning %u", lastCleaning);
// LOG_DEBUG("Loaded lastCleaningValid %u", lastCleaningValid);
// LOG_DEBUG("Loaded oneShotMode %s", oneShotMode ? "true" : "false");
// LOG_DEBUG("Loaded vocTime %u", vocTime);
// LOG_DEBUG("Loaded [%u, %u, %u, %u, %u, %u, %u, %u]",
// vocState[7], vocState[6], vocState[5], vocState[4], vocState[3], vocState[2], vocState[1], vocState[0]);
// LOG_DEBUG("Loaded %svalid VOC state", vocValid ? "" : "in");
okay = true; okay = true;
} }
file.close(); file.close();
@ -370,7 +420,7 @@ bool SEN5XSensor::loadState()
bool SEN5XSensor::saveState() bool SEN5XSensor::saveState()
{ {
// TODO - This should be called before a reboot // TODO - This should be called before a reboot for VOC index storage
// is there a way to get notified? // is there a way to get notified?
#ifdef FSCom #ifdef FSCom
auto file = SafeFile(sen5XStateFileName); auto file = SafeFile(sen5XStateFileName);
@ -379,20 +429,23 @@ bool SEN5XSensor::saveState()
sen5xstate.last_cleaning_valid = lastCleaningValid; sen5xstate.last_cleaning_valid = lastCleaningValid;
sen5xstate.one_shot_mode = oneShotMode; sen5xstate.one_shot_mode = oneShotMode;
// Unpack state (12 bytes in two parts) if (model == SEN55) {
sen5xstate.voc_state = ((uint64_t) vocState[7] << 56) | sen5xstate.has_voc_state_time = true;
((uint64_t) vocState[6] << 48) | sen5xstate.has_voc_state_valid = true;
((uint64_t) vocState[5] << 40) | sen5xstate.has_voc_state_array = true;
((uint64_t) vocState[4] << 32) |
((uint32_t) vocState[3] << 24) |
((uint32_t) vocState[2] << 16) |
((uint32_t) vocState[1] << 8) |
vocState[0];
LOG_INFO("sen5xstate.voc_state %i", sen5xstate.voc_state); sen5xstate.voc_state_time = vocTime;
sen5xstate.voc_state_valid = vocValid;
sen5xstate.voc_time = vocTime; // Unpack state (8 bytes)
sen5xstate.voc_valid = vocValid; sen5xstate.voc_state_array = (((uint64_t) vocState[7]) << 56) |
((uint64_t) vocState[6] << 48) |
((uint64_t) vocState[5] << 40) |
((uint64_t) vocState[4] << 32) |
((uint64_t) vocState[3] << 24) |
((uint64_t) vocState[2] << 16) |
((uint64_t) vocState[1] << 8) |
((uint64_t) vocState[0]);
}
bool okay = false; bool okay = false;
@ -421,42 +474,26 @@ bool SEN5XSensor::isActive(){
} }
uint32_t SEN5XSensor::wakeUp(){ uint32_t SEN5XSensor::wakeUp(){
// LOG_INFO("SEN5X: Attempting to wakeUp sensor"); uint32_t now;
now = getValidTime(RTCQuality::RTCQualityDevice);
LOG_DEBUG("SEN5X: Waking up sensor");
// From the datasheet // Check if state is recent, less than 10 minutes (600 seconds)
// By default, the VOC algorithm resets its state to initial if (vocStateRecent(now) && vocStateValid()) {
// values each time a measurement is started, if (!vocStateToSensor()){
// even if the measurement was stopped only for a short LOG_ERROR("SEN5X: Sending VOC state to sensor failed");
// time. So, the VOC index output value needs a long time }
// until it is stable again. This can be avoided by } else {
// restoring the previously memorized algorithm state before LOG_DEBUG("SEN5X: No valid VOC state found. Ignoring");
// starting the measure mode }
// TODO - This needs to be tested
// In SC, the sensor is operated in contionuous mode if
// VOCs are present, increasing battery consumption
// A different approach should be possible as stated on the
// datasheet (see above)
// uint32_t now, passed;
// now = getValidTime(RTCQuality::RTCQualityDevice);
// passed = now - vocTime; //in seconds
// // Check if state is recent, less than 10 minutes (600 seconds)
// if ((passed < SEN5X_VOC_VALID_TIME) && (now > SEN5X_VOC_VALID_DATE) && vocValid) {
// if (!vocStateToSensor()){
// LOG_ERROR("SEN5X: Sending VOC state to sensor failed");
// }
// } else {
// LOG_DEBUG("SEN5X: No valid VOC state found. Ignoring");
// }
if (!sendCommand(SEN5X_START_MEASUREMENT)) { if (!sendCommand(SEN5X_START_MEASUREMENT)) {
LOG_ERROR("SEN5X: Error starting measurement"); LOG_ERROR("SEN5X: Error starting measurement");
// TODO - what should this return?? // TODO - what should this return?? Something actually on the default interval
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
} }
delay(50); // From Sensirion Datasheet delay(50); // From Sensirion Datasheet
// LOG_INFO("SEN5X: Setting measurement mode");
// TODO - This is currently "problematic" // TODO - This is currently "problematic"
// If time is updated in between reads, there is no way to // If time is updated in between reads, there is no way to
// keep track of how long it has passed // keep track of how long it has passed
@ -467,6 +504,15 @@ uint32_t SEN5XSensor::wakeUp(){
return SEN5X_WARMUP_MS_1; return SEN5X_WARMUP_MS_1;
} }
bool SEN5XSensor::vocStateStable()
{
uint32_t now;
now = getTime();
uint32_t sinceFirstMeasureStarted = (now - firstMeasureStarted);
LOG_DEBUG("sinceFirstMeasureStarted: %us", sinceFirstMeasureStarted);
return sinceFirstMeasureStarted > SEN55_VOC_STATE_WARMUP_S;
}
bool SEN5XSensor::startCleaning() bool SEN5XSensor::startCleaning()
{ {
// Note: we only should enter here if we have a valid RTC with at least // Note: we only should enter here if we have a valid RTC with at least
@ -532,7 +578,7 @@ int32_t SEN5XSensor::runOnce()
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
} }
// Check if firmware version allows The direct switch between Measurement and RHT/Gas-Only Measurement mode // Check the firmware version
if (!getVersion()) return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; if (!getVersion()) return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
if (firmwareVer < 2) { if (firmwareVer < 2) {
LOG_ERROR("SEN5X: error firmware is too old and will not work with this implementation"); LOG_ERROR("SEN5X: error firmware is too old and will not work with this implementation");
@ -551,8 +597,8 @@ int32_t SEN5XSensor::runOnce()
uint32_t now; uint32_t now;
int32_t passed; int32_t passed;
now = getValidTime(RTCQuality::RTCQualityDevice); now = getValidTime(RTCQuality::RTCQualityDevice);
// If time is not RTCQualityNone, it will return non-zero
// If time is not RTCQualityNone, it will return non-zero
if (now) { if (now) {
if (lastCleaningValid) { if (lastCleaningValid) {
@ -566,36 +612,35 @@ int32_t SEN5XSensor::runOnce()
LOG_INFO("SEN5X: Cleaning not needed (%ds passed). Last cleaning date (in epoch): %us", passed, lastCleaning); LOG_INFO("SEN5X: Cleaning not needed (%ds passed). Last cleaning date (in epoch): %us", passed, lastCleaning);
} }
} else { } else {
// We assume the device has just been updated or it is new, so no need to trigger a cleaning. // We assume the device has just been updated or it is new,
// so no need to trigger a cleaning.
// Just save the timestamp to do a cleaning one week from now. // Just save the timestamp to do a cleaning one week from now.
// TODO - could we trigger this after getting time?
// Otherwise, we will never trigger cleaning in some cases // Otherwise, we will never trigger cleaning in some cases
lastCleaning = now; lastCleaning = now;
lastCleaningValid = true; lastCleaningValid = true;
LOG_INFO("SEN5X: No valid last cleaning date found, saving it now: %us", lastCleaning); LOG_INFO("SEN5X: No valid last cleaning date found, saving it now: %us", lastCleaning);
saveState(); saveState();
} }
if (model == SEN55) { if (model == SEN55) {
if (!vocValid) { if (!vocValid) {
LOG_INFO("SEN5X: No valid VOC's state found"); LOG_INFO("SEN5X: No valid VOC's state found");
} else { } else {
passed = now - vocTime; //in seconds // Check if state is recent
if (vocStateRecent(now)) {
// Check if state is recent, less than 10 minutes (600 seconds)
if (passed < SEN5X_VOC_VALID_TIME && (now > SEN5X_VOC_VALID_DATE)) {
// If current date greater than 01/01/2018 (validity check) // If current date greater than 01/01/2018 (validity check)
// Send it to the sensor // Send it to the sensor
LOG_INFO("SEN5X: VOC state is valid and recent"); LOG_INFO("SEN5X: VOC state is valid and recent");
vocStateToSensor(); vocStateToSensor();
} else { } else {
LOG_INFO("SEN5X VOC state is to old or date is invalid"); LOG_INFO("SEN5X: VOC state is too old or date is invalid");
LOG_DEBUG("SEN5X: vocTime %u, Passed %u, and now %u", vocTime, passed, now);
} }
} }
} }
} else { } else {
// TODO - Should this actually ignore? We could end up never cleaning... // TODO - Should this actually ignore? We could end up never cleaning...
LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state"); LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state. Trying again later");
} }
return initI2CSensor(); return initI2CSensor();
@ -645,6 +690,17 @@ bool SEN5XSensor::readValues()
sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5, sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5,
sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0); sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
if (model == SEN54 || model == SEN55) {
LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, noxIndex=%.2f",
sen5xmeasurement.humidity, sen5xmeasurement.temperature,
sen5xmeasurement.noxIndex);
}
if (model == SEN55) {
LOG_DEBUG("Got: vocIndex=%.2f",
sen5xmeasurement.vocIndex);
}
return true; return true;
} }
@ -783,6 +839,10 @@ int32_t SEN5XSensor::pendingForReady(){
return SEN5X_WARMUP_MS_1 - sinceMeasureStarted; return SEN5X_WARMUP_MS_1 - sinceMeasureStarted;
} }
if (!firstMeasureStarted) {
firstMeasureStarted = now;
}
// Get PN values to check if we are above or below threshold // Get PN values to check if we are above or below threshold
readPnValues(true); readPnValues(true);
@ -883,6 +943,7 @@ bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement)
} }
} }
return true; return true;
} else if (response == 1) { } else if (response == 1) {
// TODO return because data was not ready yet // TODO return because data was not ready yet

View File

@ -20,6 +20,16 @@
#define SEN5X_I2C_CLOCK_SPEED 100000 #define SEN5X_I2C_CLOCK_SPEED 100000
#endif #endif
/*
Time after which the sensor can go to sleep, as the warmup period has passed
and the VOCs sensor will is allowed to stop (although needs to recover the state
each time)
*/
#ifndef SEN55_VOC_STATE_WARMUP_S
// TODO for Testing 5' - Sensirion recommends 1h. We can try to test a smaller value
#define SEN55_VOC_STATE_WARMUP_S 3600
#endif
#define ONE_WEEK_IN_SECONDS 604800 #define ONE_WEEK_IN_SECONDS 604800
struct _SEN5XMeasurements { struct _SEN5XMeasurements {
@ -77,6 +87,7 @@ class SEN5XSensor : public TelemetrySensor
// Flag to work on one-shot (read and sleep), or continuous mode // Flag to work on one-shot (read and sleep), or continuous mode
bool oneShotMode = true; bool oneShotMode = true;
void setMode(bool setOneShot); void setMode(bool setOneShot);
bool vocStateValid();
bool sendCommand(uint16_t command); bool sendCommand(uint16_t command);
bool sendCommand(uint16_t command, uint8_t* buffer, uint8_t byteNumber=0); bool sendCommand(uint16_t command, uint8_t* buffer, uint8_t byteNumber=0);
@ -91,6 +102,7 @@ class SEN5XSensor : public TelemetrySensor
bool readValues(); bool readValues();
uint32_t measureStarted = 0; uint32_t measureStarted = 0;
uint32_t firstMeasureStarted = 0;
_SEN5XMeasurements sen5xmeasurement; _SEN5XMeasurements sen5xmeasurement;
protected: protected:
@ -108,10 +120,13 @@ class SEN5XSensor : public TelemetrySensor
// VOC State // VOC State
#define SEN5X_VOC_STATE_BUFFER_SIZE 8 #define SEN5X_VOC_STATE_BUFFER_SIZE 8
uint8_t vocState[SEN5X_VOC_STATE_BUFFER_SIZE]; uint8_t vocState[SEN5X_VOC_STATE_BUFFER_SIZE];
uint32_t vocTime; uint32_t vocTime = 0;
bool vocValid = true; bool vocValid = false;
bool vocStateFromSensor(); bool vocStateFromSensor();
bool vocStateToSensor(); bool vocStateToSensor();
bool vocStateStable();
bool vocStateRecent(uint32_t now);
virtual void setup() override; virtual void setup() override;