diff --git a/platformio.ini b/platformio.ini
index 12eab550e4..1d18b45735 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -217,6 +217,8 @@ lib_deps =
sensirion/Sensirion I2C SCD4x@1.1.0
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
sensirion/Sensirion I2C SFA3x@1.0.0
+ # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30
+ sensirion/Sensirion I2C SCD30@1.0.0
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
@@ -247,3 +249,5 @@ lib_deps =
sensirion/Sensirion I2C SCD4x@1.1.0
# renovate: datasource=custom.pio depName=Sensirion I2C SFA3x packageName=sensirion/library/Sensirion I2C SFA3x
sensirion/Sensirion I2C SFA3x@1.0.0
+ # renovate: datasource=custom.pio depName=Sensirion I2C SCD30 packageName=sensirion/library/Sensirion I2C SCD30
+ sensirion/Sensirion I2C SCD30@1.0.0
\ No newline at end of file
diff --git a/src/configuration.h b/src/configuration.h
index 5ec856f88c..53ae30d51d 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -244,6 +244,7 @@ along with this program. If not, see .
#define BQ25896_ADDR 0x6B
#define LTR553ALS_ADDR 0x23
#define SEN5X_ADDR 0x69
+#define SCD30_ADDR 0x61
// -----------------------------------------------------------------------------
// ACCELEROMETER
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 1611927664..7b575dd635 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -91,7 +91,8 @@ class ScanI2C
CST226SE,
SEN5X,
SFA30,
- CW2015
+ CW2015,
+ SCD30
} DeviceType;
// typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 2802854abf..df0fad792f 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -557,6 +557,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
+ SCAN_SIMPLE_CASE(SCD30_ADDR, SCD30, "SCD30", (uint8_t)addr.address);
case CST328_ADDR:
// Do we have the CST328 or the CST226SE
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
index d7127bb016..5ffe4d9926 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -29,6 +29,9 @@
#if __has_include()
#include "Sensor/SFA30Sensor.h"
#endif
+#if __has_include()
+#include "Sensor/SCD30Sensor.h"
+#endif
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
@@ -57,6 +60,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::SFA30);
#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::SCD30);
+#endif
}
int32_t AirQualityTelemetryModule::runOnce()
diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.cpp b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp
new file mode 100644
index 0000000000..0478b6651b
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SCD30Sensor.cpp
@@ -0,0 +1,511 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include()
+
+#include "../detect/reClockI2C.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("Init sensor: %s", sensorName);
+
+ _bus = bus;
+ _address = dev->address.address;
+
+#ifdef SCD30_I2C_CLOCK_SPEED
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true);
+#else
+ LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
+ return false;
+#endif /* CAN_RECLOCK_I2C */
+#endif /* SCD30_I2C_CLOCK_SPEED */
+
+ scd30.begin(*_bus, _address);
+
+ if (!startMeasurement()) {
+ LOG_ERROR("%s: Failed to start periodic measurement", sensorName);
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+ return false;
+ }
+
+ if (!getASC(ascActive)) {
+ LOG_WARN("%s: Could not determine ASC state", sensorName);
+ }
+
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ 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
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true);
+#else
+ LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
+ return false;
+#endif /* CAN_RECLOCK_I2C */
+#endif /* SCD30_I2C_CLOCK_SPEED */
+
+ if (scd30.readMeasurementData(co2, temperature, humidity) != SCD30_NO_ERROR) {
+ LOG_ERROR("SCD30: Failed to read measurement data.");
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+ return false;
+ }
+
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ if (co2 == 0) {
+ LOG_ERROR("SCD30: Invalid CO₂ reading.");
+ 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("Got %s 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: Unable to set measurement interval. Error code: %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: Unable to get measurement interval. Error code: %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: Couldn'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: Unable to stop measurement", sensorName);
+ return false;
+ }
+
+ state = SCD30_IDLE;
+ return true;
+}
+
+bool SCD30Sensor::performFRC(uint16_t targetCO2)
+{
+ uint16_t error;
+
+ LOG_INFO("%s: Issuing FRC. Ensure device has been working at least 3 minutes 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: Unable to perform forced recalibration.", sensorName);
+ return false;
+ }
+
+ LOG_INFO("%s: FRC Correction successful.", 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: Unable to send command.", sensorName);
+ return false;
+ }
+
+ if (!getASC(ascActive)) {
+ LOG_ERROR("%s: Unable to check if ASC is 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: Unable to send command.", 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 temperature at 0degC", sensorName);
+ _tempOffset = 0;
+ } else {
+
+ LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference);
+
+ error = scd30.readMeasurementData(co2, temperature, humidity);
+ if (error != SCD30_NO_ERROR) {
+ LOG_ERROR("%s: Unable to read current temperature. Error code: %u", sensorName, error);
+ return false;
+ }
+
+ LOG_INFO("%s: Current sensor temperature: %.2f", sensorName, temperature);
+
+ tempOffset = (temperature - tempReference);
+ if (tempOffset < 0) {
+ LOG_ERROR("%s temperature offset is only positive", sensorName);
+ return false;
+ }
+
+ tempOffset *= 100;
+ _tempOffset = static_cast(tempOffset);
+ }
+
+ LOG_INFO("%s: Setting temperature offset: %u (*100)", sensorName, _tempOffset);
+
+ error = scd30.setTemperatureOffset(_tempOffset);
+ if (error != SCD30_NO_ERROR) {
+ LOG_ERROR("%s: Unable to set temperature offset. Error code: %u", sensorName, error);
+ return false;
+ }
+
+ scd30.getTemperatureOffset(updatedTempOffset);
+ LOG_INFO("%s: Updated sensor temperature 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: Unable to set altitude. Error code: %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: Unable to get altitude. Error code: %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: Unable to do soft reset. Error code: %u", sensorName, error);
+ return false;
+ }
+
+ LOG_INFO("%s: soft reset successful", 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
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true);
+#else
+ LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
+ return 0;
+#endif /* CAN_RECLOCK_I2C */
+#endif /* SCD30_I2C_CLOCK_SPEED */
+
+ startMeasurement();
+
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ 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
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true);
+#else
+ LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
+ return;
+#endif /* CAN_RECLOCK_I2C */
+#endif /* SCD30_I2C_CLOCK_SPEED */
+
+ stopMeasurement();
+
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+}
+
+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
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SCD30_I2C_CLOCK_SPEED, _bus, true);
+#else
+ LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
+ return AdminMessageHandleResult::NOT_HANDLED;
+#endif /* CAN_RECLOCK_I2C */
+#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 {
+
+ if (request->sensor_config.scd30_config.has_set_asc) {
+ this->setASC(request->sensor_config.scd30_config.set_asc);
+ if (request->sensor_config.scd30_config.set_asc == false) {
+ LOG_DEBUG("%s: Request for FRC", sensorName);
+ if (request->sensor_config.scd30_config.has_set_target_co2_conc) {
+ this->performFRC(request->sensor_config.scd30_config.set_target_co2_conc);
+ } else {
+ // FRC requested but no target CO2 provided
+ LOG_ERROR("%s: target CO2 not provided", 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 (request->sensor_config.scd30_config.has_set_temperature) {
+ this->setTemperature(request->sensor_config.scd30_config.set_temperature);
+ }
+
+ // Check for altitude
+ if (request->sensor_config.scd30_config.has_set_altitude) {
+ this->setAltitude(request->sensor_config.scd30_config.set_altitude);
+ }
+
+ // Check for set measuremen interval
+ if (request->sensor_config.scd30_config.has_set_measurement_interval) {
+ this->setMeasurementInterval(request->sensor_config.scd30_config.set_measurement_interval);
+ }
+ }
+
+ result = AdminMessageHandleResult::HANDLED;
+ break;
+
+ default:
+ result = AdminMessageHandleResult::NOT_HANDLED;
+ }
+
+#if defined(SCD30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ return result;
+}
+
+#endif
diff --git a/src/modules/Telemetry/Sensor/SCD30Sensor.h b/src/modules/Telemetry/Sensor/SCD30Sensor.h
new file mode 100644
index 0000000000..6e03e2dda6
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SCD30Sensor.h
@@ -0,0 +1,53 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+#define SCD30_I2C_CLOCK_SPEED 100000
+
+class SCD30Sensor : public TelemetrySensor
+{
+ private:
+ SensirionI2cScd30 scd30;
+ TwoWire *_bus{};
+ uint8_t _address{};
+
+ bool performFRC(uint16_t targetCO2);
+ bool setASC(bool ascEnabled);
+ bool getASC(uint16_t &ascEnabled);
+ bool setTemperature(float tempReference);
+ bool getAltitude(uint16_t &altitude);
+ bool setAltitude(uint16_t altitude);
+ bool softReset(); //
+ bool setMeasurementInterval(uint16_t measInterval);
+ bool getMeasurementInterval(uint16_t &measInterval);
+ bool startMeasurement();
+ bool stopMeasurement();
+
+ // Parameters
+ uint16_t ascActive = 1;
+ uint16_t measurementInterval = 2;
+
+ public:
+ SCD30Sensor();
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+
+ enum SCD30State { SCD30_OFF, SCD30_IDLE, SCD30_MEASUREMENT };
+ SCD30State state = SCD30_OFF;
+
+ virtual bool isActive() override;
+
+ virtual void sleep() override; // Stops measurement (measurement -> idle)
+ virtual uint32_t wakeUp() override; // Starts measurement (idle -> measurement)
+ virtual bool canSleep() override;
+ virtual int32_t wakeUpTimeMs() override;
+ virtual int32_t pendingForReadyMs() override;
+ AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
index 6572ef9b10..caa9bf854f 100644
--- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
@@ -217,7 +217,7 @@ bool SCD4XSensor::startMeasurement()
state = SCD4X_MEASUREMENT;
return true;
} else {
- LOG_ERROR("%s: Couldn't start measurement mode", sensorName);
+ LOG_ERROR("%s: Unable to start measurement mode", sensorName);
return false;
}
}
@@ -232,7 +232,7 @@ bool SCD4XSensor::stopMeasurement()
error = scd4x.stopPeriodicMeasurement();
if (error != SCD4X_NO_ERROR) {
- LOG_ERROR("%s: Unable to set idle mode on SCD4X.", sensorName);
+ LOG_ERROR("%s: Unable to stop measurement.", sensorName);
return false;
}
@@ -283,11 +283,7 @@ bool SCD4XSensor::getASC(uint16_t &_ascActive)
return false;
}
- if (_ascActive) {
- LOG_INFO("%s: ASC is enabled", sensorName);
- } else {
- LOG_INFO("%s: FRC is enabled", sensorName);
- }
+ LOG_INFO("%s ASC is %s", sensorName, _ascActive ? "enabled" : "disabled");
return true;
}
@@ -305,11 +301,7 @@ bool SCD4XSensor::setASC(bool ascEnabled)
{
uint16_t error;
- if (ascEnabled) {
- LOG_INFO("%s: Enabling ASC", sensorName);
- } else {
- LOG_INFO("%s: Disabling ASC", sensorName);
- }
+ LOG_INFO("%s %s ASC", sensorName, ascEnabled ? "Enabling" : "Disabling");
if (!stopMeasurement()) {
return false;
@@ -351,7 +343,6 @@ bool SCD4XSensor::setASC(bool ascEnabled)
*/
bool SCD4XSensor::setASCBaseline(uint32_t targetCO2)
{
- // TODO - Remove?
// Available in library, but not described in datasheet.
uint16_t error;
LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2);
diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
index 0a9db4dff9..257762a299 100644
--- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
@@ -205,7 +205,6 @@ uint8_t SEN5XSensor::sen5xCRC(uint8_t *buffer)
void SEN5XSensor::sleep()
{
- // TODO Check this works
idle(true);
}
@@ -230,41 +229,43 @@ bool SEN5XSensor::idle(bool checkState)
// Check if we have time, and store it
uint32_t now; // If time is RTCQualityNone, it will return zero
now = getValidTime(RTCQuality::RTCQualityDevice);
+ // Check if state is valid (non-zero)
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!");
+ if (!(vocStateStable() && vocValid)) {
+ LOG_INFO("%s: Not stopping measurement, vocState is not stable yet!", sensorName);
return true;
}
}
+ // Save state and prefs (on all models)
+ saveState();
}
if (!oneShotMode) {
- LOG_INFO("SEN5X: Not stopping measurement, continuous mode!");
+ LOG_INFO("%s: Not stopping measurement, continuous mode!", sensorName);
return true;
+ } else {
+ LOG_INFO("%s: One shot mode enabled", sensorName);
}
// Switch to low-power based on the model
if (model == SEN50) {
if (!sendCommand(SEN5X_STOP_MEASUREMENT)) {
- LOG_ERROR("SEN5X: Error stopping measurement");
+ LOG_ERROR("%s: Error stopping measurement", sensorName);
return false;
}
state = SEN5X_IDLE;
- LOG_INFO("SEN5X: Stop measurement mode");
+ LOG_INFO("%s: Stop measurement mode", sensorName);
} else {
if (!sendCommand(SEN5X_START_MEASUREMENT_RHT_GAS)) {
- LOG_ERROR("SEN5X: Error switching to RHT/Gas measurement");
+ LOG_ERROR("%s: Error switching to RHT/Gas measurement", sensorName);
return false;
}
state = SEN5X_RHTGAS_ONLY;
- LOG_INFO("SEN5X: Switch to RHT/Gas only measurement mode");
+ LOG_INFO("%s: Switch to RHT/Gas only measurement mode", sensorName);
}
delay(200); // From Sensirion Datasheet
@@ -289,10 +290,10 @@ 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");
+ LOG_DEBUG("%s: VOC state is all 0, invalid", sensorName);
return false;
} else {
- LOG_DEBUG("SEN5X: VOC state is valid");
+ LOG_DEBUG("%s: VOC state is valid", sensorName);
return true;
}
}
@@ -618,7 +619,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
}
} else {
// TODO - Should this actually ignore? We could end up never cleaning...
- LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved state. Trying again later");
+ LOG_INFO("SEN5X: Not enough RTCQuality, ignoring saved cleaning and VOC state");
}
idle(false);
@@ -965,4 +966,4 @@ AdminMessageHandleResult SEN5XSensor::handleAdminMessage(const meshtastic_MeshPa
return result;
}
-#endif
\ No newline at end of file
+#endif