mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-13 23:21:19 +10:00
* Add SEN6X * Adds new SENXX class for SEN5X and SEN6X * Adds CO2 sensor calibration class to be shared among othre CO2 sensors * Make existing CO2 sensor draw from CO2Sensor class * Minor coment for CO2 sensor class * Move away from getRTC in SENXX class to keep track of time changes. * Change all sensors to millis for tracking time, instead of using getRTC * Add comments regarding VOC state * Avoid storing non-valid RTC Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Avoid CO2 sensor warm-up time to be below PM measured started Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fix limits in CO2 sensor calibration Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Add pragma once on headers * Avoid non-working ASC commands Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fix data poll * Move pm measure started before warmup check * Make cleaning non-blocking * Restore previous state if cleaning fails. Fix data ready condition. * Fix CO2 sensor checks for calibration --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
473 lines
13 KiB
C++
473 lines
13 KiB
C++
#include "configuration.h"
|
|
|
|
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cScd30.h>)
|
|
|
|
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
|
#include "SCD30Sensor.h"
|
|
|
|
#define SCD30_NO_ERROR 0
|
|
|
|
SCD30Sensor::SCD30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD30, "SCD30") {}
|
|
|
|
bool SCD30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
|
{
|
|
LOG_INFO("%s: Init sensor", sensorName);
|
|
|
|
_bus = bus;
|
|
_address = dev->address.address;
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
_port = dev->address.port;
|
|
reClockI2C.setup(_bus, _port);
|
|
reClockI2C.setClock(SCD30_I2C_CLOCK_SPEED);
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
scd30.begin(*_bus, _address);
|
|
|
|
if (!startMeasurement()) {
|
|
LOG_ERROR("%s: Periodic measurement start failed", sensorName);
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
return false;
|
|
}
|
|
|
|
if (!getASC(ascActive)) {
|
|
LOG_WARN("%s: Can't determine ASC state", sensorName);
|
|
}
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
if (state == SCD30_MEASUREMENT) {
|
|
status = 1;
|
|
} else {
|
|
status = 0;
|
|
}
|
|
|
|
initI2CSensor();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::getMetrics(meshtastic_Telemetry *measurement)
|
|
{
|
|
float co2, temperature, humidity;
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.setClock(SCD30_I2C_CLOCK_SPEED);
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
if (scd30.readMeasurementData(co2, temperature, humidity) != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Measurement read failed", sensorName);
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
return false;
|
|
}
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
if (co2 == 0) {
|
|
LOG_ERROR("%s: Invalid CO₂ reading", sensorName);
|
|
return false;
|
|
}
|
|
|
|
measurement->variant.air_quality_metrics.has_co2 = true;
|
|
measurement->variant.air_quality_metrics.has_co2_temperature = true;
|
|
measurement->variant.air_quality_metrics.has_co2_humidity = true;
|
|
measurement->variant.air_quality_metrics.co2 = (uint32_t)co2;
|
|
measurement->variant.air_quality_metrics.co2_temperature = temperature;
|
|
measurement->variant.air_quality_metrics.co2_humidity = humidity;
|
|
|
|
LOG_DEBUG("%s: Got readings: co2=%u, co2_temp=%.2f, co2_hum=%.2f", sensorName, (uint32_t)co2, temperature, humidity);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::setMeasurementInterval(uint16_t measInterval)
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: setting measurement interval at %us", sensorName, measInterval);
|
|
error = scd30.setMeasurementInterval(measInterval);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't set measurement interval, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
// Restart measuring so we don't need to wait the current interval to finish
|
|
// (useful when you come from very long intervals)
|
|
scd30.stopPeriodicMeasurement();
|
|
scd30.startPeriodicMeasurement(0);
|
|
|
|
getMeasurementInterval(measurementInterval);
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::getMeasurementInterval(uint16_t &measInterval)
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: getting measurement interval", sensorName);
|
|
error = scd30.getMeasurementInterval(measInterval);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't get measurement interval, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("%s: measurement interval is %us", sensorName, measInterval);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Start measurement mode
|
|
* @note This function should not change the clock
|
|
*/
|
|
bool SCD30Sensor::startMeasurement()
|
|
{
|
|
uint16_t error;
|
|
|
|
if (state == SCD30_MEASUREMENT) {
|
|
LOG_DEBUG("%s: Already in measurement mode", sensorName);
|
|
return true;
|
|
}
|
|
|
|
error = scd30.startPeriodicMeasurement(0);
|
|
|
|
if (error == SCD30_NO_ERROR) {
|
|
LOG_INFO("%s: Started measurement mode", sensorName);
|
|
|
|
state = SCD30_MEASUREMENT;
|
|
return true;
|
|
} else {
|
|
LOG_ERROR("%s: Can't start measurement mode", sensorName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Stop measurement mode
|
|
* @note This function should not change the clock
|
|
*/
|
|
bool SCD30Sensor::stopMeasurement()
|
|
{
|
|
uint16_t error;
|
|
|
|
error = scd30.stopPeriodicMeasurement();
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't stop measurement", sensorName);
|
|
return false;
|
|
}
|
|
|
|
state = SCD30_IDLE;
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::performFRC(uint16_t targetCO2)
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: Issuing FRC. Needs 3+ min in stable target environment", sensorName);
|
|
|
|
LOG_INFO("%s: Target CO2: %u ppm", sensorName, targetCO2);
|
|
error = scd30.forceRecalibration((uint16_t)targetCO2);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't perform FRC", sensorName);
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("%s: FRC done", sensorName);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::setASC(bool ascEnabled)
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling");
|
|
|
|
error = scd30.activateAutoCalibration((uint16_t)ascEnabled);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't send command", sensorName);
|
|
return false;
|
|
}
|
|
|
|
if (!getASC(ascActive)) {
|
|
LOG_ERROR("%s: Can't check if ASC enabled", sensorName);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::getASC(uint16_t &_ascActive)
|
|
{
|
|
uint16_t error;
|
|
// LOG_INFO("%s: Getting ASC", sensorName);
|
|
|
|
error = scd30.getAutoCalibrationStatus(_ascActive);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't get ASC status", sensorName);
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("%s: ASC is %s", sensorName, _ascActive ? "enabled" : "disabled");
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the temperature reference. Unit ℃.
|
|
*
|
|
* The on-board RH/T sensor is influenced by thermal self-heating of SCD30
|
|
* and other electrical components. Design-in alters the thermal properties
|
|
* of SCD30 such that temperature and humidity offsets may occur when
|
|
* operating the sensor in end-customer devices. Compensation of those
|
|
* effects is achievable by writing the temperature offset found in
|
|
* continuous operation of the device into the sensor. Temperature offset
|
|
* value is saved in non-volatile memory. The last set value will be used
|
|
* for temperature offset compensation after repowering.
|
|
*
|
|
* @param[in] tempReference
|
|
* @note this function is certainly confusing and it's not recommended
|
|
*/
|
|
bool SCD30Sensor::setTemperature(float tempReference)
|
|
{
|
|
uint16_t error;
|
|
uint16_t updatedTempOffset;
|
|
float tempOffset;
|
|
uint16_t _tempOffset;
|
|
float co2;
|
|
float temperature;
|
|
float humidity;
|
|
|
|
if (tempReference == 100) {
|
|
// Requesting the value of 100 will restore the temperature offset
|
|
LOG_INFO("%s: Setting reference temp at 0degC", sensorName);
|
|
_tempOffset = 0;
|
|
} else {
|
|
|
|
LOG_INFO("%s: Setting reference temp at: %.2f", sensorName, tempReference);
|
|
|
|
error = scd30.readMeasurementData(co2, temperature, humidity);
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't read current temp, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("%s: Current sensor temp: %.2f", sensorName, temperature);
|
|
|
|
tempOffset = (temperature - tempReference);
|
|
if (tempOffset < 0) {
|
|
LOG_ERROR("%s: temp offset is only positive", sensorName);
|
|
return false;
|
|
}
|
|
|
|
tempOffset *= 100;
|
|
_tempOffset = static_cast<uint16_t>(tempOffset);
|
|
}
|
|
|
|
LOG_INFO("%s: Setting temp offset: %u (*100)", sensorName, _tempOffset);
|
|
|
|
error = scd30.setTemperatureOffset(_tempOffset);
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't set temp offset, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
scd30.getTemperatureOffset(updatedTempOffset);
|
|
LOG_INFO("%s: Updated sensor temp offset: %u (*100)", sensorName, updatedTempOffset);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::setAltitude(uint16_t altitude)
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: setting altitude at %um", sensorName, altitude);
|
|
|
|
error = scd30.setAltitudeCompensation(altitude);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't set altitude, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
uint16_t newAltitude;
|
|
getAltitude(newAltitude);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::getAltitude(uint16_t &altitude)
|
|
{
|
|
uint16_t error;
|
|
// LOG_INFO("%s: Getting altitude", sensorName);
|
|
|
|
error = scd30.getAltitudeCompensation(altitude);
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't get altitude, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
LOG_INFO("%s: Sensor altitude: %u", sensorName, altitude);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SCD30Sensor::softReset()
|
|
{
|
|
uint16_t error;
|
|
|
|
LOG_INFO("%s: Requesting soft reset", sensorName);
|
|
|
|
error = scd30.softReset();
|
|
|
|
if (error != SCD30_NO_ERROR) {
|
|
LOG_ERROR("%s: Can't soft reset, rc=%u", sensorName, error);
|
|
return false;
|
|
}
|
|
|
|
LOG_INFO("%s: soft reset done", sensorName);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if sensor is in measurement mode
|
|
*/
|
|
bool SCD30Sensor::isActive()
|
|
{
|
|
return state == SCD30_MEASUREMENT;
|
|
}
|
|
|
|
/**
|
|
* @brief Start measurement mode
|
|
* @note Not used in admin comands, getMetrics or init, can change clock.
|
|
*/
|
|
uint32_t SCD30Sensor::wakeUp()
|
|
{
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.setClock(SCD30_I2C_CLOCK_SPEED);
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
startMeasurement();
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Stop measurement mode
|
|
* @note Not used in admin comands, getMetrics or init, can change clock.
|
|
*/
|
|
void SCD30Sensor::sleep()
|
|
{
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.setClock(SCD30_I2C_CLOCK_SPEED);
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
stopMeasurement();
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
}
|
|
|
|
bool SCD30Sensor::canSleep()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int32_t SCD30Sensor::wakeUpTimeMs()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int32_t SCD30Sensor::pendingForReadyMs()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
AdminMessageHandleResult SCD30Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
|
|
meshtastic_AdminMessage *response)
|
|
{
|
|
AdminMessageHandleResult result;
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.setClock(SCD30_I2C_CLOCK_SPEED);
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
switch (request->which_payload_variant) {
|
|
case meshtastic_AdminMessage_sensor_config_tag:
|
|
// Check for ASC-FRC request first
|
|
if (!request->sensor_config.has_scd30_config) {
|
|
result = AdminMessageHandleResult::NOT_HANDLED;
|
|
break;
|
|
}
|
|
|
|
if (request->sensor_config.scd30_config.has_soft_reset) {
|
|
LOG_DEBUG("%s: Requested soft reset", sensorName);
|
|
this->softReset();
|
|
} else {
|
|
const auto &cfg = request->sensor_config.scd30_config;
|
|
|
|
// ASC/FRC/altitude calibration branching is shared with SCD4XSensor and the
|
|
// CO2-capable SEN6X variants via CO2CalibrationSensor.
|
|
if (cfg.has_set_asc || cfg.has_set_altitude) {
|
|
Co2AdminRequest co2req;
|
|
co2req.hasSetAsc = cfg.has_set_asc;
|
|
co2req.setAsc = cfg.set_asc;
|
|
co2req.hasTargetCo2 = cfg.has_set_target_co2_conc;
|
|
co2req.targetCo2 = cfg.set_target_co2_conc;
|
|
co2req.hasSetAltitude = cfg.has_set_altitude;
|
|
co2req.setAltitude = cfg.set_altitude;
|
|
if (!this->handleCo2AdminRequest(co2req, sensorName)) {
|
|
result = AdminMessageHandleResult::NOT_HANDLED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check for temperature offset
|
|
// NOTE: this requires to have a sensor working on stable environment
|
|
// And to make it between readings
|
|
if (cfg.has_set_temperature) {
|
|
this->setTemperature(cfg.set_temperature);
|
|
}
|
|
|
|
// Check for set measuremen interval
|
|
if (cfg.has_set_measurement_interval) {
|
|
this->setMeasurementInterval(cfg.set_measurement_interval);
|
|
}
|
|
}
|
|
|
|
result = AdminMessageHandleResult::HANDLED;
|
|
break;
|
|
|
|
default:
|
|
result = AdminMessageHandleResult::NOT_HANDLED;
|
|
}
|
|
|
|
#ifdef SCD30_I2C_CLOCK_SPEED
|
|
reClockI2C.restoreClock();
|
|
#endif /* SCD30_I2C_CLOCK_SPEED */
|
|
|
|
return result;
|
|
}
|
|
|
|
#endif
|