mirror of
https://github.com/meshtastic/firmware.git
synced 2025-07-31 02:45:41 +00:00
Changes on SEN5X library - removing pm_env as well
This commit is contained in:
parent
9b68f51a1c
commit
8e23190140
@ -93,10 +93,8 @@ 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(
|
||||
@ -114,13 +112,13 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
lastSentToPhone = millis();
|
||||
}
|
||||
|
||||
// TODO - When running this continuously, we are turning on and off the sensors but not sending data to mesh or phone, which turns on the device unnecessarily for a while
|
||||
#ifdef PMSA003I_ENABLE_PIN
|
||||
pmsa003iSensor.sleep();
|
||||
#endif /* PMSA003I_ENABLE_PIN */
|
||||
|
||||
#ifdef SEN5X_ENABLE_PIN
|
||||
sen5xSensor.sleep();
|
||||
#endif /* SEN5X_ENABLE_PIN */
|
||||
// TODO - Add logic here to send the sensor to idle ONLY if there is enough time to wake it up before the next reading cycle
|
||||
sen5xSensor.idle();
|
||||
|
||||
}
|
||||
return min(sendToPhoneIntervalMs, result);
|
||||
@ -166,8 +164,7 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
const auto &m = telemetry.variant.air_quality_metrics;
|
||||
|
||||
// Check if any telemetry field has valid data
|
||||
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental || m.has_pm25_environmental ||
|
||||
m.has_pm100_environmental;
|
||||
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard;
|
||||
|
||||
if (!hasAny) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
@ -231,9 +228,10 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
|
||||
t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
|
||||
t->variant.air_quality_metrics.pm100_standard);
|
||||
|
||||
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
|
||||
t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental,
|
||||
t->variant.air_quality_metrics.pm100_environmental);
|
||||
// TODO - Decide what to do with these
|
||||
// LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
|
||||
// t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental,
|
||||
// t->variant.air_quality_metrics.pm100_environmental);
|
||||
#endif
|
||||
// release previous packet before occupying a new spot
|
||||
if (lastMeasurementPacket != nullptr)
|
||||
@ -253,16 +251,14 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
|
||||
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
|
||||
|
||||
// TODO - This is currently problematic, as it assumes only one sensor connected
|
||||
// We should implement some logic to avoid not getting data if one sensor disconnects
|
||||
if (pmsa003iSensor.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 && pmsa003iSensor.getMetrics(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;
|
||||
}
|
||||
@ -303,11 +299,12 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
|
||||
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m.time = getTime();
|
||||
// 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?
|
||||
if (getAirQualityTelemetry(&m)) {
|
||||
LOG_INFO("Send: pm10_standard=%f, pm25_standard=%f, pm100_standard=%f, pm10_environmental=%f, pm100_environmental=%f",
|
||||
LOG_INFO("Send: pm10_standard=%.2f, pm25_standard=%.2f, pm100_standard=%.2f",
|
||||
m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,
|
||||
m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental,
|
||||
m.variant.air_quality_metrics.pm100_environmental);
|
||||
m.variant.air_quality_metrics.pm100_standard);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
|
@ -84,12 +84,12 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
measurement->variant.air_quality_metrics.has_pm100_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm100_standard = pmsa003iData.pm100_standard;
|
||||
|
||||
measurement->variant.air_quality_metrics.has_pm10_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm10_environmental = pmsa003iData.pm10_env;
|
||||
measurement->variant.air_quality_metrics.has_pm25_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm25_environmental = pmsa003iData.pm25_env;
|
||||
measurement->variant.air_quality_metrics.has_pm100_environmental = true;
|
||||
measurement->variant.air_quality_metrics.pm100_environmental = pmsa003iData.pm100_env;
|
||||
// measurement->variant.air_quality_metrics.has_pm10_environmental = true;
|
||||
// measurement->variant.air_quality_metrics.pm10_environmental = pmsa003iData.pm10_env;
|
||||
// measurement->variant.air_quality_metrics.has_pm25_environmental = true;
|
||||
// measurement->variant.air_quality_metrics.pm25_environmental = pmsa003iData.pm25_env;
|
||||
// measurement->variant.air_quality_metrics.has_pm100_environmental = true;
|
||||
// measurement->variant.air_quality_metrics.pm100_environmental = pmsa003iData.pm100_env;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5,9 +5,21 @@
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "SEN5XSensor.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "FSCommon.h"
|
||||
#include "SPILock.h"
|
||||
|
||||
SEN5XSensor::SEN5XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SEN5X, "SEN5X") {}
|
||||
|
||||
bool SEN5XSensor::restoreClock(uint32_t currentClock){
|
||||
#ifdef SEN5X_I2C_CLOCK_SPEED
|
||||
if (currentClock != SEN5X_I2C_CLOCK_SPEED){
|
||||
LOG_DEBUG("Restoring I2C clock to %uHz", currentClock);
|
||||
return bus->setClock(currentClock);
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SEN5XSensor::getVersion()
|
||||
{
|
||||
if (!sendCommand(SEN5X_GET_FIRMWARE_VERSION)){
|
||||
@ -27,9 +39,9 @@ bool SEN5XSensor::getVersion()
|
||||
hardwareVer = versionBuffer[3] + (versionBuffer[4] / 10);
|
||||
protocolVer = versionBuffer[5] + (versionBuffer[6] / 10);
|
||||
|
||||
LOG_INFO("SEN5X Firmware Version: %d", firmwareVer);
|
||||
LOG_INFO("SEN5X Hardware Version: %d", hardwareVer);
|
||||
LOG_INFO("SEN5X Protocol Version: %d", protocolVer);
|
||||
LOG_INFO("SEN5X Firmware Version: %0.2f", firmwareVer);
|
||||
LOG_INFO("SEN5X Hardware Version: %0.2f", hardwareVer);
|
||||
LOG_INFO("SEN5X Protocol Version: %0.2f", protocolVer);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -95,17 +107,28 @@ bool SEN5XSensor::sendCommand(uint16_t command, uint8_t* buffer, uint8_t byteNum
|
||||
while (bi < byteNumber) {
|
||||
toSend[i++] = buffer[bi++];
|
||||
toSend[i++] = buffer[bi++];
|
||||
uint8_t calcCRC = CRC(&buffer[bi - 2]);
|
||||
uint8_t calcCRC = sen5xCRC(&buffer[bi - 2]);
|
||||
toSend[i++] = calcCRC;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SEN5X_I2C_CLOCK_SPEED
|
||||
uint32_t currentClock;
|
||||
currentClock = bus->getClock();
|
||||
if (currentClock != SEN5X_I2C_CLOCK_SPEED){
|
||||
LOG_DEBUG("Changing I2C clock to %u", SEN5X_I2C_CLOCK_SPEED);
|
||||
bus->setClock(SEN5X_I2C_CLOCK_SPEED);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Transmit the data
|
||||
LOG_INFO("Beginning connection to SEN5X: 0x%x", address);
|
||||
bus->beginTransmission(address);
|
||||
size_t writtenBytes = bus->write(toSend, bufferSize);
|
||||
uint8_t i2c_error = bus->endTransmission();
|
||||
|
||||
restoreClock(currentClock);
|
||||
|
||||
if (writtenBytes != bufferSize) {
|
||||
LOG_ERROR("SEN5X: Error writting on I2C bus");
|
||||
return false;
|
||||
@ -120,32 +143,40 @@ bool SEN5XSensor::sendCommand(uint16_t command, uint8_t* buffer, uint8_t byteNum
|
||||
|
||||
uint8_t SEN5XSensor::readBuffer(uint8_t* buffer, uint8_t byteNumber)
|
||||
{
|
||||
size_t readedBytes = bus->requestFrom(address, byteNumber);
|
||||
#ifdef SEN5X_I2C_CLOCK_SPEED
|
||||
uint32_t currentClock;
|
||||
currentClock = bus->getClock();
|
||||
if (currentClock != SEN5X_I2C_CLOCK_SPEED){
|
||||
LOG_DEBUG("Changing I2C clock to %u", SEN5X_I2C_CLOCK_SPEED);
|
||||
bus->setClock(SEN5X_I2C_CLOCK_SPEED);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (readedBytes != byteNumber) {
|
||||
size_t readBytes = bus->requestFrom(address, byteNumber);
|
||||
if (readBytes != byteNumber) {
|
||||
LOG_ERROR("SEN5X: Error reading I2C bus");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t i = 0;
|
||||
uint8_t receivedBytes = 0;
|
||||
while (readedBytes > 0) {
|
||||
while (readBytes > 0) {
|
||||
buffer[i++] = bus->read(); // Just as a reminder: i++ returns i and after that increments.
|
||||
buffer[i++] = bus->read();
|
||||
uint8_t recvCRC = bus->read();
|
||||
uint8_t calcCRC = CRC(&buffer[i - 2]);
|
||||
uint8_t calcCRC = sen5xCRC(&buffer[i - 2]);
|
||||
if (recvCRC != calcCRC) {
|
||||
LOG_ERROR("SEN5X: Checksum error while receiving msg");
|
||||
return 0;
|
||||
}
|
||||
readedBytes -=3;
|
||||
readBytes -=3;
|
||||
receivedBytes += 2;
|
||||
}
|
||||
|
||||
restoreClock(currentClock);
|
||||
return receivedBytes;
|
||||
}
|
||||
|
||||
uint8_t SEN5XSensor::CRC(uint8_t* buffer)
|
||||
uint8_t SEN5XSensor::sen5xCRC(uint8_t* buffer)
|
||||
{
|
||||
// This code is based on Sensirion's own implementation https://github.com/Sensirion/arduino-core/blob/41fd02cacf307ec4945955c58ae495e56809b96c/src/SensirionCrc.cpp
|
||||
uint8_t crc = 0xff;
|
||||
@ -174,6 +205,131 @@ bool SEN5XSensor::I2Cdetect(TwoWire *_Wire, uint8_t address)
|
||||
else return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// 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
|
||||
|
||||
LOG_INFO("SEN5X: Stop measurement mode");
|
||||
|
||||
state = SEN5X_IDLE;
|
||||
measureStarted = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SEN5XSensor::loadCleaningState()
|
||||
{
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
auto file = FSCom.open(sen5XCleaningFileName, FILE_O_READ);
|
||||
if (file) {
|
||||
file.read();
|
||||
file.close();
|
||||
LOG_INFO("Cleaning state %u read for %s read from %s", lastCleaning, sensorName, sen5XCleaningFileName);
|
||||
} else {
|
||||
LOG_INFO("No %s state found (File: %s)", sensorName, sen5XCleaningFileName);
|
||||
}
|
||||
spiLock->unlock();
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
#endif
|
||||
}
|
||||
|
||||
void SEN5XSensor::updateCleaningState()
|
||||
{
|
||||
#ifdef FSCom
|
||||
spiLock->lock();
|
||||
|
||||
if (FSCom.exists(sen5XCleaningFileName) && !FSCom.remove(sen5XCleaningFileName)) {
|
||||
LOG_WARN("Can't remove old state file");
|
||||
}
|
||||
auto file = FSCom.open(sen5XCleaningFileName, FILE_O_WRITE);
|
||||
if (file) {
|
||||
LOG_INFO("Save cleaning state %u for %s to %s", lastCleaning, sensorName, sen5XCleaningFileName);
|
||||
file.write(lastCleaning);
|
||||
file.flush();
|
||||
file.close();
|
||||
} else {
|
||||
LOG_INFO("Can't write %s state (File: %s)", sensorName, sen5XCleaningFileName);
|
||||
}
|
||||
|
||||
spiLock->unlock();
|
||||
#else
|
||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SEN5XSensor::isActive(){
|
||||
return state == SEN5X_MEASUREMENT || state == SEN5X_MEASUREMENT_2;
|
||||
}
|
||||
|
||||
uint32_t SEN5XSensor::wakeUp(){
|
||||
LOG_INFO("SEN5X: Attempting to wakeUp sensor");
|
||||
if (!sendCommand(SEN5X_START_MEASUREMENT)) {
|
||||
LOG_INFO("SEN5X: Error starting measurement");
|
||||
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
|
||||
}
|
||||
delay(50); // From Sensirion Arduino library
|
||||
|
||||
LOG_INFO("SEN5X: Setting measurement mode");
|
||||
uint32_t now;
|
||||
now = getTime();
|
||||
measureStarted = now;
|
||||
state = SEN5X_MEASUREMENT;
|
||||
if (state == SEN5X_MEASUREMENT)
|
||||
LOG_INFO("SEN5X: Started measurement mode");
|
||||
return SEN5X_WARMUP_MS_1;
|
||||
}
|
||||
|
||||
bool SEN5XSensor::startCleaning()
|
||||
{
|
||||
state = SEN5X_CLEANING;
|
||||
|
||||
// Note that this 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
|
||||
|
||||
if (!sendCommand(SEN5X_START_FAN_CLEANING)) {
|
||||
LOG_ERROR("SEN5X: Error starting fan cleaning");
|
||||
return false;
|
||||
}
|
||||
delay(20); // From Sensirion Arduino library
|
||||
|
||||
// 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...");
|
||||
|
||||
uint16_t started = millis();
|
||||
while (millis() - started < 10500) {
|
||||
// Serial.print(".");
|
||||
delay(500);
|
||||
}
|
||||
LOG_INFO(" Cleaning done!!");
|
||||
|
||||
// Save timestamp in flash so we know when a week has passed
|
||||
uint32_t now;
|
||||
now = getTime();
|
||||
lastCleaning = now;
|
||||
updateCleaningState();
|
||||
|
||||
idle();
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t SEN5XSensor::runOnce()
|
||||
{
|
||||
state = SEN5X_NOT_DETECTED;
|
||||
@ -183,7 +339,7 @@ int32_t SEN5XSensor::runOnce()
|
||||
}
|
||||
|
||||
bus = nodeTelemetrySensorsMap[sensorType].second;
|
||||
// sen5x.begin(*bus);
|
||||
address = (uint8_t)nodeTelemetrySensorsMap[sensorType].first;
|
||||
|
||||
delay(50); // without this there is an error on the deviceReset function
|
||||
|
||||
@ -248,84 +404,265 @@ int32_t SEN5XSensor::runOnce()
|
||||
status = 1;
|
||||
LOG_INFO("SEN5X Enabled");
|
||||
|
||||
// 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;
|
||||
// }
|
||||
// Check if it is time to do a cleaning
|
||||
// TODO - this is not currently working as intended - always reading 0 from the file. We should probably make a unified approach for both the cleaning and the VOCstate
|
||||
loadCleaningState();
|
||||
LOG_INFO("Last cleaning time: %u", lastCleaning);
|
||||
if (lastCleaning) {
|
||||
LOG_INFO("Last cleaning is valid");
|
||||
|
||||
// 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;
|
||||
// }
|
||||
uint32_t now;
|
||||
now = getTime();
|
||||
LOG_INFO("Current time %us", now);
|
||||
uint32_t passed = now - lastCleaning;
|
||||
LOG_INFO("Elapsed time since last cleaning: %us", passed);
|
||||
|
||||
if (passed > ONE_WEEK_IN_SECONDS && (now > 1514764800)) { // If current date greater than 01/01/2018 (validity check)
|
||||
LOG_INFO("SEN5X: More than a week since las cleaning, cleaning...");
|
||||
startCleaning();
|
||||
} else {
|
||||
LOG_INFO("Last cleaning date (in epoch): %u", lastCleaning);
|
||||
}
|
||||
} else {
|
||||
LOG_INFO("Last cleaning is not valid");
|
||||
// We asume 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.
|
||||
lastCleaning = getTime();
|
||||
updateCleaningState();
|
||||
LOG_INFO("SEN5X: No valid last cleaning date found, saving it now: %u", lastCleaning);
|
||||
}
|
||||
|
||||
// TODO - Should wakeUp happen here?
|
||||
return initI2CSensor();
|
||||
}
|
||||
|
||||
void SEN5XSensor::setup()
|
||||
{
|
||||
#ifdef SEN5X_ENABLE_PIN
|
||||
pinMode(SEN5X_ENABLE_PIN, OUTPUT);
|
||||
digitalWrite(SEN5X_ENABLE_PIN, HIGH);
|
||||
delay(25);
|
||||
#endif /* SEN5X_ENABLE_PIN */
|
||||
}
|
||||
|
||||
#ifdef SEN5X_ENABLE_PIN
|
||||
// void SEN5XSensor::sleep() {
|
||||
// digitalWrite(SEN5X_ENABLE_PIN, LOW);
|
||||
// state = SSEN5XState::SEN5X_OFF;
|
||||
bool SEN5XSensor::readValues()
|
||||
{
|
||||
if (!sendCommand(SEN5X_READ_VALUES)){
|
||||
LOG_ERROR("SEN5X: Error sending read command");
|
||||
return false;
|
||||
}
|
||||
LOG_DEBUG("SEN5X: Reading PM Values");
|
||||
delay(20); // From Sensirion Arduino library
|
||||
|
||||
uint8_t dataBuffer[24];
|
||||
size_t receivedNumber = readBuffer(&dataBuffer[0], 24);
|
||||
if (receivedNumber == 0) {
|
||||
LOG_ERROR("SEN5X: Error getting values");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First get the integers
|
||||
uint16_t uint_pM1p0 = static_cast<uint16_t>((dataBuffer[0] << 8) | dataBuffer[1]);
|
||||
uint16_t uint_pM2p5 = static_cast<uint16_t>((dataBuffer[2] << 8) | dataBuffer[3]);
|
||||
uint16_t uint_pM4p0 = static_cast<uint16_t>((dataBuffer[4] << 8) | dataBuffer[5]);
|
||||
uint16_t uint_pM10p0 = static_cast<uint16_t>((dataBuffer[6] << 8) | dataBuffer[7]);
|
||||
int16_t int_humidity = static_cast<int16_t>((dataBuffer[8] << 8) | dataBuffer[9]);
|
||||
int16_t int_temperature = static_cast<int16_t>((dataBuffer[10] << 8) | dataBuffer[11]);
|
||||
int16_t int_vocIndex = static_cast<int16_t>((dataBuffer[12] << 8) | dataBuffer[13]);
|
||||
int16_t int_noxIndex = static_cast<int16_t>((dataBuffer[14] << 8) | dataBuffer[15]);
|
||||
|
||||
// TODO we should check if values are NAN before converting them
|
||||
// convert them based on Sensirion Arduino lib
|
||||
sen5xmeasurement.pM1p0 = uint_pM1p0 / 10.0f;
|
||||
sen5xmeasurement.pM2p5 = uint_pM2p5 / 10.0f;
|
||||
sen5xmeasurement.pM4p0 = uint_pM4p0 / 10.0f;
|
||||
sen5xmeasurement.pM10p0 = uint_pM10p0 / 10.0f;
|
||||
sen5xmeasurement.humidity = int_humidity / 100.0f;
|
||||
sen5xmeasurement.temperature = int_temperature / 200.0f;
|
||||
sen5xmeasurement.vocIndex = int_vocIndex / 10.0f;
|
||||
sen5xmeasurement.noxIndex = int_noxIndex / 10.0f;
|
||||
|
||||
// TODO - this is currently returning crap
|
||||
LOG_INFO("Got: pM1p0=%.2f, pM2p5=%.2f, pM4p0=%.2f, pM10p0=%.2f",
|
||||
sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5,
|
||||
sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SEN5XSensor::readPnValues()
|
||||
{
|
||||
if (!sendCommand(SEN5X_READ_PM_VALUES)){
|
||||
LOG_ERROR("SEN5X: Error sending read command");
|
||||
return false;
|
||||
}
|
||||
LOG_DEBUG("SEN5X: Reading PN Values");
|
||||
delay(20); // From Sensirion Arduino library
|
||||
|
||||
uint8_t dataBuffer[30];
|
||||
size_t receivedNumber = readBuffer(&dataBuffer[0], 30);
|
||||
if (receivedNumber == 0) {
|
||||
LOG_ERROR("SEN5X: Error getting PM values");
|
||||
return false;
|
||||
}
|
||||
|
||||
// First get the integers
|
||||
// uint16_t uint_pM1p0 = static_cast<uint16_t>((dataBuffer[0] << 8) | dataBuffer[1]);
|
||||
// uint16_t uint_pM2p5 = static_cast<uint16_t>((dataBuffer[2] << 8) | dataBuffer[3]);
|
||||
// uint16_t uint_pM4p0 = static_cast<uint16_t>((dataBuffer[4] << 8) | dataBuffer[5]);
|
||||
// uint16_t uint_pM10p0 = static_cast<uint16_t>((dataBuffer[6] << 8) | dataBuffer[7]);
|
||||
uint16_t uint_pN0p5 = static_cast<uint16_t>((dataBuffer[8] << 8) | dataBuffer[9]);
|
||||
uint16_t uint_pN1p0 = static_cast<uint16_t>((dataBuffer[10] << 8) | dataBuffer[11]);
|
||||
uint16_t uint_pN2p5 = static_cast<uint16_t>((dataBuffer[12] << 8) | dataBuffer[13]);
|
||||
uint16_t uint_pN4p0 = static_cast<uint16_t>((dataBuffer[14] << 8) | dataBuffer[15]);
|
||||
uint16_t uint_pN10p0 = static_cast<uint16_t>((dataBuffer[16] << 8) | dataBuffer[17]);
|
||||
uint16_t uint_tSize = static_cast<uint16_t>((dataBuffer[18] << 8) | dataBuffer[19]);
|
||||
|
||||
// Convert them based on Sensirion Arduino lib
|
||||
// sen5xmeasurement.pM1p0 = uint_pM1p0 / 10.0f;
|
||||
// sen5xmeasurement.pM2p5 = uint_pM2p5 / 10.0f;
|
||||
// sen5xmeasurement.pM4p0 = uint_pM4p0 / 10.0f;
|
||||
// sen5xmeasurement.pM10p0 = uint_pM10p0 / 10.0f;
|
||||
sen5xmeasurement.pN0p5 = uint_pN0p5 / 10;
|
||||
sen5xmeasurement.pN1p0 = uint_pN1p0 / 10;
|
||||
sen5xmeasurement.pN2p5 = uint_pN2p5 / 10;
|
||||
sen5xmeasurement.pN4p0 = uint_pN4p0 / 10;
|
||||
sen5xmeasurement.pN10p0 = uint_pN10p0 / 10;
|
||||
sen5xmeasurement.tSize = uint_tSize / 1000.0f;
|
||||
|
||||
// Convert PN readings from #/cm3 to #/0.1l
|
||||
sen5xmeasurement.pN0p5 *= 100;
|
||||
sen5xmeasurement.pN1p0 *= 100;
|
||||
sen5xmeasurement.pN2p5 *= 100;
|
||||
sen5xmeasurement.pN4p0 *= 100;
|
||||
sen5xmeasurement.pN10p0 *= 100;
|
||||
sen5xmeasurement.tSize *= 100;
|
||||
|
||||
// TODO - this is currently returning crap
|
||||
LOG_INFO("Got: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f",
|
||||
sen5xmeasurement.pN0p5, sen5xmeasurement.pN1p0,
|
||||
sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0,
|
||||
sen5xmeasurement.pN10p0, sen5xmeasurement.tSize
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO - Decide if we want to have this here or not
|
||||
// bool SEN5XSensor::readRawValues()
|
||||
// {
|
||||
// if (!sendCommand(SEN5X_READ_RAW_VALUES)){
|
||||
// LOG_ERROR("SEN5X: Error sending read command");
|
||||
// return false;
|
||||
// }
|
||||
// delay(20); // From Sensirion Arduino library
|
||||
|
||||
// uint8_t dataBuffer[12];
|
||||
// size_t receivedNumber = readBuffer(&dataBuffer[0], 12);
|
||||
// if (receivedNumber == 0) {
|
||||
// LOG_ERROR("SEN5X: Error getting Raw values");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Get values
|
||||
// rawHumidity = static_cast<int16_t>((dataBuffer[0] << 8) | dataBuffer[1]);
|
||||
// rawTemperature = static_cast<int16_t>((dataBuffer[2] << 8) | dataBuffer[3]);
|
||||
// rawVoc = static_cast<uint16_t>((dataBuffer[4] << 8) | dataBuffer[5]);
|
||||
// rawNox = static_cast<uint16_t>((dataBuffer[6] << 8) | dataBuffer[7]);
|
||||
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// uint32_t SEN5XSensor::wakeUp() {
|
||||
// digitalWrite(SEN5X_ENABLE_PIN, HIGH);
|
||||
// state = SEN5XState::SEN5X_IDLE;
|
||||
// return SEN5X_WARMUP_MS;
|
||||
// }
|
||||
#endif /* SEN5X_ENABLE_PIN */
|
||||
uint8_t SEN5XSensor::getMeasurements()
|
||||
{
|
||||
// Try to get new data
|
||||
if (!sendCommand(SEN5X_READ_DATA_READY)){
|
||||
LOG_ERROR("SEN5X: Error sending command data ready flag");
|
||||
return 2;
|
||||
}
|
||||
delay(20); // From Sensirion Arduino library
|
||||
|
||||
uint8_t dataReadyBuffer[3];
|
||||
size_t charNumber = readBuffer(&dataReadyBuffer[0], 3);
|
||||
if (charNumber == 0) {
|
||||
LOG_ERROR("SEN5X: Error getting device version value");
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool data_ready = dataReadyBuffer[1];
|
||||
|
||||
if (!data_ready) {
|
||||
LOG_INFO("SEN5X: Data is not ready");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!readValues()) {
|
||||
LOG_ERROR("SEN5X: Error getting readings");
|
||||
return 2;
|
||||
}
|
||||
|
||||
if(!readPnValues()) {
|
||||
LOG_ERROR("SEN5X: Error getting PM readings");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// if(!readRawValues()) {
|
||||
// LOG_ERROR("SEN5X: Error getting Raw readings");
|
||||
// return 2;
|
||||
// }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SEN5XSensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
uint16_t error;
|
||||
char errorMessage[256];
|
||||
LOG_INFO("SEN5X: Attempting to get metrics");
|
||||
if (!isActive()){
|
||||
LOG_INFO("SEN5X: not in measurement mode");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Measurement
|
||||
float massConcentrationPm1p0;
|
||||
float massConcentrationPm2p5;
|
||||
float massConcentrationPm4p0;
|
||||
float massConcentrationPm10p0;
|
||||
float ambientHumidity;
|
||||
float ambientTemperature;
|
||||
float vocIndex;
|
||||
float noxIndex;
|
||||
uint8_t response;
|
||||
response = getMeasurements();
|
||||
|
||||
// error = sen5x.readMeasuredValues(
|
||||
// massConcentrationPm1p0, massConcentrationPm2p5, massConcentrationPm4p0,
|
||||
// massConcentrationPm10p0, ambientHumidity, ambientTemperature, vocIndex,
|
||||
// noxIndex);
|
||||
if (response == 0) {
|
||||
measurement->variant.air_quality_metrics.has_pm10_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm10_standard = sen5xmeasurement.pM1p0;
|
||||
measurement->variant.air_quality_metrics.has_pm25_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm25_standard = sen5xmeasurement.pM2p5;
|
||||
measurement->variant.air_quality_metrics.has_pm40_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm40_standard = sen5xmeasurement.pM4p0;
|
||||
measurement->variant.air_quality_metrics.has_pm100_standard = true;
|
||||
measurement->variant.air_quality_metrics.pm100_standard = sen5xmeasurement.pM10p0;
|
||||
|
||||
// if (error) {
|
||||
// LOG_INFO("Error trying to execute readMeasuredValues(): ");
|
||||
// errorToString(error, errorMessage, 256);
|
||||
// LOG_INFO(errorMessage);
|
||||
// return false;
|
||||
// }
|
||||
measurement->variant.air_quality_metrics.has_particles_05um = true;
|
||||
measurement->variant.air_quality_metrics.particles_05um = sen5xmeasurement.pN0p5;
|
||||
measurement->variant.air_quality_metrics.has_particles_10um = true;
|
||||
measurement->variant.air_quality_metrics.particles_10um = sen5xmeasurement.pN1p0;
|
||||
measurement->variant.air_quality_metrics.has_particles_25um = true;
|
||||
measurement->variant.air_quality_metrics.particles_25um = sen5xmeasurement.pN2p5;
|
||||
measurement->variant.air_quality_metrics.has_particles_40um = true;
|
||||
measurement->variant.air_quality_metrics.particles_40um = sen5xmeasurement.pN4p0;
|
||||
measurement->variant.air_quality_metrics.has_particles_100um = true;
|
||||
measurement->variant.air_quality_metrics.particles_100um = sen5xmeasurement.pN10p0;
|
||||
|
||||
// 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;
|
||||
if (model == SEN54 || model == SEN55) {
|
||||
measurement->variant.air_quality_metrics.has_pm_humidity = true;
|
||||
measurement->variant.air_quality_metrics.pm_humidity = sen5xmeasurement.humidity;
|
||||
measurement->variant.air_quality_metrics.has_pm_temperature = true;
|
||||
measurement->variant.air_quality_metrics.pm_temperature = sen5xmeasurement.temperature;
|
||||
measurement->variant.air_quality_metrics.has_pm_nox_idx = true;
|
||||
measurement->variant.air_quality_metrics.pm_nox_idx = sen5xmeasurement.noxIndex;
|
||||
}
|
||||
|
||||
if (model == SEN55) {
|
||||
measurement->variant.air_quality_metrics.has_pm_voc_idx = true;
|
||||
measurement->variant.air_quality_metrics.pm_voc_idx = sen5xmeasurement.vocIndex;
|
||||
}
|
||||
return true;
|
||||
} else if (response == 1) {
|
||||
// TODO return because data was not ready yet
|
||||
// Should this return false?
|
||||
return false;
|
||||
} else if (response == 2) {
|
||||
// Return with error for non-existing data
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -5,13 +5,45 @@
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include "Wire.h"
|
||||
// #include <SensirionI2CSen5x.h>
|
||||
#include "RTC.h"
|
||||
|
||||
#ifndef SEN5X_WARMUP_MS
|
||||
#ifndef SEN5X_WARMUP_MS_1
|
||||
// from the SEN5X datasheet
|
||||
#define SEN5X_WARMUP_MS 30000
|
||||
// #define SEN5X_WARMUP_MS_1 15000 - Change to this
|
||||
#define SEN5X_WARMUP_MS_1 30000
|
||||
#endif
|
||||
|
||||
// TODO - For now, we ignore this threshold, and we only use the MS_1 (to 30000)
|
||||
#ifndef SEN5X_WARMUP_MS_2
|
||||
// from the SEN5X datasheet
|
||||
#define SEN5X_WARMUP_MS_2 30000
|
||||
#endif
|
||||
|
||||
#ifndef SEN5X_I2C_CLOCK_SPEED
|
||||
#define SEN5X_I2C_CLOCK_SPEED 100000
|
||||
#endif
|
||||
|
||||
#define ONE_WEEK_IN_SECONDS 604800
|
||||
|
||||
// TODO - These are currently ints in the protobuf
|
||||
// Decide on final type for this values and change accordingly
|
||||
struct _SEN5XMeasurements {
|
||||
float pM1p0;
|
||||
float pM2p5;
|
||||
float pM4p0;
|
||||
float pM10p0;
|
||||
uint32_t pN0p5;
|
||||
uint32_t pN1p0;
|
||||
uint32_t pN2p5;
|
||||
uint32_t pN4p0;
|
||||
uint32_t pN10p0;
|
||||
float tSize;
|
||||
float humidity;
|
||||
float temperature;
|
||||
float vocIndex;
|
||||
float noxIndex;
|
||||
};
|
||||
|
||||
class SEN5XSensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
@ -45,26 +77,62 @@ class SEN5XSensor : public TelemetrySensor
|
||||
enum SEN5XState { SEN5X_OFF, SEN5X_IDLE, SEN5X_MEASUREMENT, SEN5X_MEASUREMENT_2, SEN5X_CLEANING, SEN5X_NOT_DETECTED };
|
||||
SEN5XState state = SEN5X_OFF;
|
||||
|
||||
bool continousMode = false;
|
||||
bool forcedContinousMode = false;
|
||||
|
||||
// TODO
|
||||
// Sensirion recommends taking a reading after 16 seconds, if the Perticle number reading is over 100#/cm3 the reading is OK, but if it is lower wait until 30 seconds and take it again.
|
||||
// https://sensirion.com/resource/application_note/low_power_mode/sen5x
|
||||
// TODO Implement logic for this concentrationThreshold
|
||||
// This can reduce battery consumption by a lot
|
||||
// uint16_t concentrationThreshold = 100;
|
||||
|
||||
bool sendCommand(uint16_t wichCommand);
|
||||
bool sendCommand(uint16_t wichCommand, uint8_t* buffer, uint8_t byteNumber=0);
|
||||
uint8_t readBuffer(uint8_t* buffer, uint8_t byteNumber); // Return number of bytes received
|
||||
uint8_t CRC(uint8_t* buffer);
|
||||
uint8_t sen5xCRC(uint8_t* buffer);
|
||||
bool I2Cdetect(TwoWire *_Wire, uint8_t address);
|
||||
bool restoreClock(uint32_t);
|
||||
bool startCleaning();
|
||||
uint8_t getMeasurements();
|
||||
bool readRawValues();
|
||||
bool readPnValues();
|
||||
bool readValues();
|
||||
|
||||
uint32_t measureStarted = 0;
|
||||
_SEN5XMeasurements sen5xmeasurement;
|
||||
|
||||
protected:
|
||||
// Store status of the sensor in this file
|
||||
const char *sen5XCleaningFileName = "/prefs/sen5XCleaning.dat";
|
||||
const char *sen5XVOCFileName = "/prefs/sen5XVOC.dat";
|
||||
|
||||
// Cleaning State
|
||||
#define SEN5X_MAX_CLEANING_SIZE 32
|
||||
// Last cleaning status - if > 0 - valid, otherwise 0
|
||||
uint32_t lastCleaning = 0;
|
||||
void loadCleaningState();
|
||||
void updateCleaningState();
|
||||
|
||||
// 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();
|
||||
|
||||
virtual void setup() override;
|
||||
|
||||
public:
|
||||
|
||||
// #ifdef SEN5X_ENABLE_PIN
|
||||
// void sleep();
|
||||
// uint32_t wakeUp();
|
||||
// #endif
|
||||
|
||||
SEN5XSensor();
|
||||
bool isActive();
|
||||
uint32_t wakeUp();
|
||||
bool idle();
|
||||
virtual int32_t runOnce() override;
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif
|
@ -125,18 +125,18 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_standard) {
|
||||
msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard);
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
msgPayload["pm10_e"] =
|
||||
new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental);
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
msgPayload["pm25_e"] =
|
||||
new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental);
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
msgPayload["pm100_e"] =
|
||||
new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental);
|
||||
}
|
||||
// if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
// msgPayload["pm10_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental);
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
// msgPayload["pm25_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental);
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
// msgPayload["pm100_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental);
|
||||
// }
|
||||
} else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||
if (decoded->variant.power_metrics.has_ch1_voltage) {
|
||||
msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage);
|
||||
|
@ -120,15 +120,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_standard) {
|
||||
jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard;
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental;
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental;
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental;
|
||||
}
|
||||
// if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
// jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental;
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
// jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental;
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
// jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental;
|
||||
// }
|
||||
} else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||
if (decoded->variant.power_metrics.has_ch1_voltage) {
|
||||
jsonObj["payload"]["voltage_ch1"] = decoded->variant.power_metrics.ch1_voltage;
|
||||
|
Loading…
Reference in New Issue
Block a user