mirror of
https://github.com/meshtastic/firmware.git
synced 2025-09-06 03:29:17 +00:00
Add VOC measurements and persistence (WIP)
* Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode?
This commit is contained in:
parent
b3107fd0d5
commit
7d1deaf4ce
@ -11,8 +11,6 @@
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
meshtastic_SEN5XState sen5xstate = meshtastic_SEN5XState_init_zero;
|
||||
|
||||
SEN5XSensor::SEN5XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SEN5X, "SEN5X") {}
|
||||
|
||||
bool SEN5XSensor::restoreClock(uint32_t currentClock){
|
||||
@ -31,7 +29,7 @@ bool SEN5XSensor::getVersion()
|
||||
LOG_ERROR("SEN5X: Error sending version command");
|
||||
return false;
|
||||
}
|
||||
delay(20); // From Sensirion Arduino library
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
uint8_t versionBuffer[12];
|
||||
size_t charNumber = readBuffer(&versionBuffer[0], 3);
|
||||
@ -57,7 +55,7 @@ bool SEN5XSensor::findModel()
|
||||
LOG_ERROR("SEN5X: Error asking for product name");
|
||||
return false;
|
||||
}
|
||||
delay(50); // From Sensirion Arduino library
|
||||
delay(50); // From Sensirion Datasheet
|
||||
|
||||
const uint8_t nameSize = 48;
|
||||
uint8_t name[nameSize];
|
||||
@ -127,12 +125,16 @@ bool SEN5XSensor::sendCommand(uint16_t command, uint8_t* buffer, uint8_t byteNum
|
||||
#endif
|
||||
|
||||
// Transmit the data
|
||||
// LOG_INFO("Beginning connection to SEN5X: 0x%x", address);
|
||||
// LOG_DEBUG("Beginning connection to SEN5X: 0x%x. Size: %u", address, bufferSize);
|
||||
// Note: this is necessary to allow for long-buffers
|
||||
delay(20);
|
||||
bus->beginTransmission(address);
|
||||
size_t writtenBytes = bus->write(toSend, bufferSize);
|
||||
uint8_t i2c_error = bus->endTransmission();
|
||||
|
||||
#ifdef SEN5X_I2C_CLOCK_SPEED
|
||||
restoreClock(currentClock);
|
||||
#endif
|
||||
|
||||
if (writtenBytes != bufferSize) {
|
||||
LOG_ERROR("SEN5X: Error writting on I2C bus");
|
||||
@ -177,7 +179,10 @@ uint8_t SEN5XSensor::readBuffer(uint8_t* buffer, uint8_t byteNumber)
|
||||
readBytes -=3;
|
||||
receivedBytes += 2;
|
||||
}
|
||||
#ifdef SEN5X_I2C_CLOCK_SPEED
|
||||
restoreClock(currentClock);
|
||||
#endif
|
||||
|
||||
return receivedBytes;
|
||||
}
|
||||
|
||||
@ -212,24 +217,107 @@ bool SEN5XSensor::I2Cdetect(TwoWire *_Wire, uint8_t address)
|
||||
|
||||
bool SEN5XSensor::idle()
|
||||
{
|
||||
// In continous mode we don't sleep
|
||||
if (continousMode || forcedContinousMode) {
|
||||
LOG_ERROR("SEN5X: Not going to idle mode, we are in continous mode!!");
|
||||
return false;
|
||||
|
||||
|
||||
// Get VOC state before going to idle mode
|
||||
if (vocStateFromSensor()) {
|
||||
// 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;
|
||||
}
|
||||
// TODO - Get VOC state before going to idle mode
|
||||
// vocStateFromSensor();
|
||||
|
||||
if (!sendCommand(SEN5X_STOP_MEASUREMENT)) {
|
||||
LOG_ERROR("SEN5X: Error stoping measurement");
|
||||
return false;
|
||||
}
|
||||
// delay(200); // From Sensirion Arduino library
|
||||
delay(200); // From Sensirion Datasheet
|
||||
|
||||
LOG_INFO("SEN5X: Stop measurement mode");
|
||||
|
||||
state = SEN5X_IDLE;
|
||||
measureStarted = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SEN5XSensor::vocStateToSensor()
|
||||
{
|
||||
if (model != SEN55){
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sendCommand(SEN5X_STOP_MEASUREMENT)) {
|
||||
LOG_ERROR("SEN5X: Error stoping measurement");
|
||||
return false;
|
||||
}
|
||||
delay(200); // From Sensirion Datasheet
|
||||
|
||||
LOG_DEBUG("SEN5X: Sending VOC state to sensor");
|
||||
LOG_DEBUG("[%u, %u, %u, %u, %u, %u, %u, %u]",
|
||||
vocState[0],vocState[1], vocState[2], vocState[3],
|
||||
vocState[4],vocState[5], vocState[6], vocState[7]);
|
||||
|
||||
// Note: send command already takes into account the CRC
|
||||
// buffer size increment needed
|
||||
if (!sendCommand(SEN5X_RW_VOCS_STATE, vocState, SEN5X_VOC_STATE_BUFFER_SIZE)){
|
||||
LOG_ERROR("SEN5X: Error sending VOC's state command'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SEN5XSensor::vocStateFromSensor()
|
||||
{
|
||||
if (model != SEN55){
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("SEN5X: Getting VOC state from sensor");
|
||||
// Ask VOCs state from the sensor
|
||||
if (!sendCommand(SEN5X_RW_VOCS_STATE)){
|
||||
LOG_ERROR("SEN5X: Error sending VOC's state command'");
|
||||
return false;
|
||||
}
|
||||
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
// Retrieve the data
|
||||
// Allocate buffer to account for CRC
|
||||
uint8_t vocBuffer[SEN5X_VOC_STATE_BUFFER_SIZE + (SEN5X_VOC_STATE_BUFFER_SIZE / 2)];
|
||||
size_t receivedNumber = readBuffer(&vocBuffer[0], SEN5X_VOC_STATE_BUFFER_SIZE + (SEN5X_VOC_STATE_BUFFER_SIZE / 2));
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
if (receivedNumber == 0) {
|
||||
LOG_DEBUG("SEN5X: Error getting VOC's state");
|
||||
return false;
|
||||
}
|
||||
|
||||
vocState[0] = vocBuffer[0];
|
||||
vocState[1] = vocBuffer[1];
|
||||
vocState[2] = vocBuffer[3];
|
||||
vocState[3] = vocBuffer[4];
|
||||
vocState[4] = vocBuffer[6];
|
||||
vocState[5] = vocBuffer[7];
|
||||
vocState[6] = vocBuffer[9];
|
||||
vocState[7] = vocBuffer[10];
|
||||
|
||||
// Print the state (if debug is on)
|
||||
LOG_DEBUG("SEN5X: VOC state retrieved from sensor");
|
||||
LOG_DEBUG("[%u, %u, %u, %u, %u, %u, %u, %u]",
|
||||
vocState[0],vocState[1], vocState[2], vocState[3],
|
||||
vocState[4],vocState[5], vocState[6], vocState[7]);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -248,6 +336,18 @@ bool SEN5XSensor::loadState()
|
||||
} else {
|
||||
lastCleaning = sen5xstate.last_cleaning_time;
|
||||
lastCleaningValid = sen5xstate.last_cleaning_valid;
|
||||
// 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;
|
||||
vocValid = sen5xstate.voc_valid;
|
||||
okay = true;
|
||||
}
|
||||
file.close();
|
||||
@ -263,11 +363,29 @@ bool SEN5XSensor::loadState()
|
||||
|
||||
bool SEN5XSensor::saveState()
|
||||
{
|
||||
// TODO - This should be called before a reboot
|
||||
// is there a way to get notified?
|
||||
#ifdef FSCom
|
||||
auto file = SafeFile(sen5XStateFileName);
|
||||
|
||||
sen5xstate.last_cleaning_time = lastCleaning;
|
||||
sen5xstate.last_cleaning_valid = lastCleaningValid;
|
||||
|
||||
// Unpack state (12 bytes in two parts)
|
||||
sen5xstate.voc_state = ((uint64_t) vocState[7] << 56) |
|
||||
((uint64_t) vocState[6] << 48) |
|
||||
((uint64_t) vocState[5] << 40) |
|
||||
((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_time = vocTime;
|
||||
sen5xstate.voc_valid = vocValid;
|
||||
|
||||
bool okay = false;
|
||||
|
||||
LOG_INFO("%s: state write to %s", sensorName, sen5XStateFileName);
|
||||
@ -296,17 +414,45 @@ bool SEN5XSensor::isActive(){
|
||||
|
||||
uint32_t SEN5XSensor::wakeUp(){
|
||||
// LOG_INFO("SEN5X: Attempting to wakeUp sensor");
|
||||
|
||||
// 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
|
||||
|
||||
// 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)) {
|
||||
LOG_INFO("SEN5X: Error starting measurement");
|
||||
LOG_ERROR("SEN5X: Error starting measurement");
|
||||
// TODO - what should this return??
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
// Not needed
|
||||
// delay(50); // From Sensirion Arduino library
|
||||
delay(50); // From Sensirion Datasheet
|
||||
|
||||
// LOG_INFO("SEN5X: Setting measurement mode");
|
||||
uint32_t now;
|
||||
now = getTime();
|
||||
measureStarted = now;
|
||||
// TODO - This is currently "problematic"
|
||||
// If time is updated in between reads, there is no way to
|
||||
// keep track of how long it has passed
|
||||
measureStarted = getTime();
|
||||
state = SEN5X_MEASUREMENT;
|
||||
if (state == SEN5X_MEASUREMENT)
|
||||
LOG_INFO("SEN5X: Started measurement mode");
|
||||
@ -319,18 +465,18 @@ bool SEN5XSensor::startCleaning()
|
||||
// RTCQuality::RTCQualityDevice
|
||||
state = SEN5X_CLEANING;
|
||||
|
||||
// Note that this command can only be run when the sensor is in measurement mode
|
||||
// Note that cleaning command can only be run when the sensor is in measurement mode
|
||||
if (!sendCommand(SEN5X_START_MEASUREMENT)) {
|
||||
LOG_ERROR("SEN5X: Error starting measurment mode");
|
||||
return false;
|
||||
}
|
||||
delay(50); // From Sensirion Arduino library
|
||||
delay(50); // From Sensirion Datasheet
|
||||
|
||||
if (!sendCommand(SEN5X_START_FAN_CLEANING)) {
|
||||
LOG_ERROR("SEN5X: Error starting fan cleaning");
|
||||
return false;
|
||||
}
|
||||
// delay(20); // From Sensirion Arduino library
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
// This message will be always printed so the user knows the device it's not hung
|
||||
LOG_INFO("SEN5X: Started fan cleaning it will take 10 seconds...");
|
||||
@ -371,7 +517,7 @@ int32_t SEN5XSensor::runOnce()
|
||||
LOG_ERROR("SEN5X: Error reseting device");
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
delay(200); // From Sensirion Arduino library
|
||||
delay(200); // From Sensirion Datasheet
|
||||
|
||||
if (!findModel()) {
|
||||
LOG_ERROR("SEN5X: error finding sensor model");
|
||||
@ -384,7 +530,7 @@ int32_t SEN5XSensor::runOnce()
|
||||
LOG_ERROR("SEN5X: error firmware is too old and will not work with this implementation");
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
delay(200); // From Sensirion Arduino library
|
||||
delay(200); // From Sensirion Datasheet
|
||||
|
||||
// Detection succeeded
|
||||
state = SEN5X_IDLE;
|
||||
@ -395,25 +541,23 @@ int32_t SEN5XSensor::runOnce()
|
||||
|
||||
// Check if it is time to do a cleaning
|
||||
uint32_t now;
|
||||
int32_t passed;
|
||||
now = getValidTime(RTCQuality::RTCQualityDevice);
|
||||
// If time is not RTCQualityNone, it will return non-zero
|
||||
|
||||
if (now) {
|
||||
if (lastCleaningValid) {
|
||||
// LOG_INFO("SEN5X: Last cleaning is valid");
|
||||
// LOG_INFO("SEN5X: Current time %us", now);
|
||||
|
||||
int32_t passed = now - lastCleaning; // in seconds
|
||||
// LOG_INFO("SEN5X: Elapsed time since last cleaning: %us", passed);
|
||||
passed = now - lastCleaning; // in seconds
|
||||
|
||||
if (passed > ONE_WEEK_IN_SECONDS && (now > 1514764800)) { // If current date greater than 01/01/2018 (validity check)
|
||||
if (passed > ONE_WEEK_IN_SECONDS && (now > SEN5X_VOC_VALID_DATE)) {
|
||||
// If current date greater than 01/01/2018 (validity check)
|
||||
LOG_INFO("SEN5X: More than a week (%us) since last cleaning in epoch (%us). Trigger, cleaning...", passed, lastCleaning);
|
||||
startCleaning();
|
||||
} else {
|
||||
LOG_INFO("SEN5X: Cleaning not needed (%ds passed). Last cleaning date (in epoch): %us", passed, lastCleaning);
|
||||
}
|
||||
} else {
|
||||
// LOG_INFO("SEN5X: Last cleaning time is not valid");
|
||||
// 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.
|
||||
// TODO - could we trigger this after getting time?
|
||||
@ -422,10 +566,28 @@ int32_t SEN5XSensor::runOnce()
|
||||
lastCleaningValid = true;
|
||||
LOG_INFO("SEN5X: No valid last cleaning date found, saving it now: %us", lastCleaning);
|
||||
saveState();
|
||||
}
|
||||
if (model == SEN55) {
|
||||
if (!vocValid) {
|
||||
LOG_INFO("SEN5X: No valid VOC's state found");
|
||||
} else {
|
||||
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)) {
|
||||
// If current date greater than 01/01/2018 (validity check)
|
||||
// Send it to the sensor
|
||||
LOG_INFO("SEN5X: VOC state is valid and recent");
|
||||
vocStateToSensor();
|
||||
} else {
|
||||
LOG_INFO("SEN5X VOC state is to old or date is invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// TODO - Should this actually ignore? We could end up never cleaning...
|
||||
LOG_INFO("SEN5X: Not enough RTCQuality, ignoring cleaning");
|
||||
LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state");
|
||||
}
|
||||
|
||||
return initI2CSensor();
|
||||
@ -442,7 +604,7 @@ bool SEN5XSensor::readValues()
|
||||
return false;
|
||||
}
|
||||
LOG_DEBUG("SEN5X: Reading PM Values");
|
||||
delay(20); // From Sensirion Arduino library
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
uint8_t dataBuffer[24];
|
||||
size_t receivedNumber = readBuffer(&dataBuffer[0], 24);
|
||||
@ -488,7 +650,7 @@ bool SEN5XSensor::readPnValues(bool cumulative)
|
||||
}
|
||||
|
||||
LOG_DEBUG("SEN5X: Reading PN Values");
|
||||
delay(20); // From Sensirion Arduino library
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
uint8_t dataBuffer[30];
|
||||
size_t receivedNumber = readBuffer(&dataBuffer[0], 30);
|
||||
@ -550,7 +712,7 @@ bool SEN5XSensor::readPnValues(bool cumulative)
|
||||
// LOG_ERROR("SEN5X: Error sending read command");
|
||||
// return false;
|
||||
// }
|
||||
// delay(20); // From Sensirion Arduino library
|
||||
// delay(20); // From Sensirion Datasheet
|
||||
|
||||
// uint8_t dataBuffer[12];
|
||||
// size_t receivedNumber = readBuffer(&dataBuffer[0], 12);
|
||||
@ -575,7 +737,7 @@ uint8_t SEN5XSensor::getMeasurements()
|
||||
LOG_ERROR("SEN5X: Error sending command data ready flag");
|
||||
return 2;
|
||||
}
|
||||
delay(20); // From Sensirion Arduino library
|
||||
delay(20); // From Sensirion Datasheet
|
||||
|
||||
uint8_t dataReadyBuffer[3];
|
||||
size_t charNumber = readBuffer(&dataReadyBuffer[0], 3);
|
||||
|
@ -66,6 +66,9 @@ class SEN5XSensor : public TelemetrySensor
|
||||
#define SEN5X_READ_RAW_VALUES 0x03D2
|
||||
#define SEN5X_READ_PM_VALUES 0x0413
|
||||
|
||||
#define SEN5X_VOC_VALID_TIME 600
|
||||
#define SEN5X_VOC_VALID_DATE 1514764800
|
||||
|
||||
enum SEN5Xmodel { SEN5X_UNKNOWN = 0, SEN50 = 0b001, SEN54 = 0b010, SEN55 = 0b100 };
|
||||
SEN5Xmodel model = SEN5X_UNKNOWN;
|
||||
|
||||
@ -93,20 +96,22 @@ class SEN5XSensor : public TelemetrySensor
|
||||
protected:
|
||||
// Store status of the sensor in this file
|
||||
const char *sen5XStateFileName = "/prefs/sen5X.dat";
|
||||
meshtastic_SEN5XState sen5xstate = meshtastic_SEN5XState_init_zero;
|
||||
|
||||
bool loadState();
|
||||
bool saveState();
|
||||
|
||||
// Cleaning State
|
||||
// Last cleaning status
|
||||
uint32_t lastCleaning = 0;
|
||||
bool lastCleaningValid = false;
|
||||
|
||||
// TODO - VOC State
|
||||
// # define SEN5X_VOC_STATE_BUFFER_SIZE 12
|
||||
// uint8_t VOCstate[SEN5X_VOC_STATE_BUFFER_SIZE];
|
||||
// struct VOCstateStruct { uint8_t state[SEN5X_VOC_STATE_BUFFER_SIZE]; uint32_t time; bool valid=true; };
|
||||
// void loadVOCState();
|
||||
// void updateVOCState();
|
||||
// VOC State
|
||||
#define SEN5X_VOC_STATE_BUFFER_SIZE 8
|
||||
uint8_t vocState[SEN5X_VOC_STATE_BUFFER_SIZE];
|
||||
uint32_t vocTime;
|
||||
bool vocValid = true;
|
||||
bool vocStateFromSensor();
|
||||
bool vocStateToSensor();
|
||||
|
||||
virtual void setup() override;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user