diff --git a/platformio.ini b/platformio.ini
index b7123b865d8..b9f1e580bb5 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -214,6 +214,8 @@ lib_deps =
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
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
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
@@ -242,3 +244,5 @@ lib_deps =
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
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
diff --git a/src/configuration.h b/src/configuration.h
index 5d8d764c759..5ec856f88c7 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -217,6 +217,7 @@ along with this program. If not, see .
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
+#define SFA30_ADDR 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA003I_ADDR 0x12
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index bf01a03657d..21aceffd1ac 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
- ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X};
+ ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X, SFA30};
return firstOfOrNONE(2, types);
}
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 52815966182..16119276649 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -89,8 +89,9 @@ class ScanI2C
DA217,
CHSC6X,
CST226SE,
- CW2015,
- SEN5X
+ SEN5X,
+ SFA30,
+ CW2015
} DeviceType;
// typedef uint8_t DeviceAddress;
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index e5e69dca7b9..2802854abf2 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -1,4 +1,6 @@
#include "ScanI2CTwoWire.h"
+#include "configuration.h"
+#include "detect/ScanI2C.h"
#if !MESHTASTIC_EXCLUDE_I2C
@@ -456,6 +458,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
case LPS22HB_ADDR_ALT:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD060), 48); // get device marking
+ if (registerValue != 0) {
+ type = SFA30;
+ logFoundDevice("SFA30", (uint8_t)addr.address);
+ break;
+ }
+ // TODO - What happens with these two?
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
diff --git a/src/main.cpp b/src/main.cpp
index 153b9847ea2..45685f6cdab 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -681,7 +681,6 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
- scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
#endif
#ifdef HAS_SDCARD
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
index cc1b543737d..d7127bb0162 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -1,3 +1,4 @@
+#include "DebugConfiguration.h"
#include "configuration.h"
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
@@ -25,6 +26,9 @@
#if __has_include()
#include "Sensor/SCD4XSensor.h"
#endif
+#if __has_include()
+#include "Sensor/SFA30Sensor.h"
+#endif
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
@@ -50,6 +54,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::SCD4X);
#endif
+#if __has_include()
+ addSensor(i2cScanner, ScanI2C::DeviceType::SFA30);
+#endif
}
int32_t AirQualityTelemetryModule::runOnce()
@@ -71,7 +78,8 @@ int32_t AirQualityTelemetryModule::runOnce()
}
if (firstTime) {
- // This is the first time the OSThread library has called this function, so do some setup
+ // This is the first time the OSThread library has called this function, so
+ // do some setup
firstTime = false;
if (moduleConfig.telemetry.air_quality_enabled) {
@@ -221,6 +229,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
if (m.has_co2)
entries.push_back("CO2: " + String(m.co2) + "ppm");
+ if (m.has_form_formaldehyde)
+ entries.push_back("HCHO: " + String(m.form_formaldehyde) + "ppb");
// === Show first available metric on top-right of first line ===
if (!entries.empty()) {
@@ -256,17 +266,19 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp);
- LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender,
- t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
- t->variant.air_quality_metrics.pm100_standard);
+ if (t->variant.air_quality_metrics.has_pm10_standard)
+ LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, "
+ "pm100_standard=%i",
+ sender, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
+ t->variant.air_quality_metrics.pm100_standard);
- // 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);
+ if (t->variant.air_quality_metrics.has_co2)
+ LOG_INFO("CO2=%i, CO2_T=%.2f, CO2_H=%.2f", t->variant.air_quality_metrics.co2,
+ t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity);
- LOG_INFO(" | CO2=%i, CO2_T=%f, CO2_H=%f", t->variant.air_quality_metrics.co2,
- t->variant.air_quality_metrics.co2_temperature, t->variant.air_quality_metrics.co2_humidity);
+ if (t->variant.air_quality_metrics.has_form_formaldehyde)
+ LOG_INFO("HCHO=%.2f, HCHO_T=%.2f, HCHO_H=%.2f", t->variant.air_quality_metrics.form_formaldehyde,
+ t->variant.air_quality_metrics.form_temperature, t->variant.air_quality_metrics.form_humidity);
#endif
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
@@ -354,10 +366,18 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
m.variant.air_quality_metrics.has_co2_humidity;
if (hasAnyCO2) {
- LOG_INFO("Send: co2=%i, co2_t=%f, co2_rh=%f", m.variant.air_quality_metrics.co2,
+ LOG_INFO("Send: co2=%i, co2_t=%.2f, co2_rh=%.2f", m.variant.air_quality_metrics.co2,
m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity);
}
+ bool hasAnyHCHO = m.variant.air_quality_metrics.has_form_formaldehyde ||
+ m.variant.air_quality_metrics.has_form_temperature || m.variant.air_quality_metrics.has_form_humidity;
+
+ if (hasAnyHCHO) {
+ LOG_INFO("Send: hcho=%.2f, hcho_t=%.2f, hcho_rh=%.2f", m.variant.air_quality_metrics.form_formaldehyde,
+ m.variant.air_quality_metrics.form_temperature, m.variant.air_quality_metrics.form_humidity);
+ }
+
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
@@ -426,4 +446,4 @@ AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(
return result;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h
index 2b88b74bacf..197491f2d06 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.h
+++ b/src/modules/Telemetry/AirQualityTelemetry.h
@@ -64,8 +64,9 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
bool firstTime = true;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
+ // uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
};
-#endif
\ No newline at end of file
+#endif
diff --git a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
index bc067c04c5b..a752f2ab807 100644
--- a/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
+++ b/src/modules/Telemetry/Sensor/PMSA003ISensor.cpp
@@ -138,6 +138,10 @@ bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.air_quality_metrics.has_particles_100um = true;
measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26);
+ LOG_DEBUG("Got %s readings: pM1p0_standard=%u, pM2p5_standard=%u, pM10p0_standard=%u", sensorName,
+ measurement->variant.air_quality_metrics.pm10_standard, measurement->variant.air_quality_metrics.pm25_standard,
+ measurement->variant.air_quality_metrics.pm100_standard);
+
return true;
}
diff --git a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
index 579d9873b4c..6572ef9b100 100644
--- a/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SCD4XSensor.cpp
@@ -125,7 +125,7 @@ bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement)
reClockI2C(currentClock, _bus, false);
#endif
- LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity);
+ LOG_DEBUG("Got %s readings: co2=%u, co2_temp=%.2f, co2_hum%.2f", sensorName, co2, temperature, humidity);
if (error != SCD4X_NO_ERROR) {
LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error);
if (co2 == 0) {
diff --git a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
index 299a1f7df1c..0a9db4dff9e 100644
--- a/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
+++ b/src/modules/Telemetry/Sensor/SEN5XSensor.cpp
@@ -665,16 +665,16 @@ bool SEN5XSensor::readValues()
sen5xmeasurement.vocIndex = !isnan(int_vocIndex) ? int_vocIndex / 10.0f : FLT_MAX;
sen5xmeasurement.noxIndex = !isnan(int_noxIndex) ? int_noxIndex / 10.0f : FLT_MAX;
- LOG_DEBUG("Got: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sen5xmeasurement.pM1p0, sen5xmeasurement.pM2p5,
- sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
+ LOG_DEBUG("Got %s readings: pM1p0=%u, pM2p5=%u, pM4p0=%u, pM10p0=%u", sensorName, sen5xmeasurement.pM1p0,
+ sen5xmeasurement.pM2p5, sen5xmeasurement.pM4p0, sen5xmeasurement.pM10p0);
if (model != SEN50) {
- LOG_DEBUG("Got: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sen5xmeasurement.humidity, sen5xmeasurement.temperature,
- sen5xmeasurement.vocIndex);
+ LOG_DEBUG("Got %s readings: humidity=%.2f, temperature=%.2f, vocIndex=%.2f", sensorName, sen5xmeasurement.humidity,
+ sen5xmeasurement.temperature, sen5xmeasurement.vocIndex);
}
if (model == SEN55) {
- LOG_DEBUG("Got: noxIndex=%.2f", sen5xmeasurement.noxIndex);
+ LOG_DEBUG("Got %s readings: noxIndex=%.2f", sensorName, sen5xmeasurement.noxIndex);
}
return true;
@@ -727,9 +727,9 @@ bool SEN5XSensor::readPNValues(bool cumulative)
sen5xmeasurement.pN1p0 -= sen5xmeasurement.pN0p5;
}
- LOG_DEBUG("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);
+ LOG_DEBUG("Got %s readings: pN0p5=%u, pN1p0=%u, pN2p5=%u, pN4p0=%u, pN10p0=%u, tSize=%.2f", sensorName,
+ sen5xmeasurement.pN0p5, sen5xmeasurement.pN1p0, sen5xmeasurement.pN2p5, sen5xmeasurement.pN4p0,
+ sen5xmeasurement.pN10p0, sen5xmeasurement.tSize);
return true;
}
diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.cpp b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp
new file mode 100644
index 00000000000..42a77db405f
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SFA30Sensor.cpp
@@ -0,0 +1,198 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include()
+
+#include "../detect/reClockI2C.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "SFA30Sensor.h"
+
+SFA30Sensor::SFA30Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SFA30, "SFA30"){};
+
+bool SFA30Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
+{
+ LOG_INFO("Init sensor: %s", sensorName);
+
+ _bus = bus;
+ _address = dev->address.address;
+
+#ifdef SFA30_I2C_CLOCK_SPEED
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SFA30_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 /* SFA30_I2C_CLOCK_SPEED */
+
+ sfa30.begin(*_bus, _address);
+ delay(20);
+
+ if (this->isError(sfa30.deviceReset())) {
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+ return false;
+ }
+
+ state = State::IDLE;
+ if (this->isError(sfa30.startContinuousMeasurement())) {
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+ return false;
+ }
+
+ LOG_INFO("%s starting measurement", sensorName);
+
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ status = 1;
+ state = State::ACTIVE;
+ measureStarted = getTime();
+ LOG_INFO("%s Enabled", sensorName);
+
+ initI2CSensor();
+ return true;
+};
+
+bool SFA30Sensor::isError(uint16_t response)
+{
+ if (response == SFA30_NO_ERROR)
+ return false;
+
+ // TODO - Check error to char conversion
+ LOG_ERROR("%s: %s", sensorName, response);
+ return true;
+}
+
+void SFA30Sensor::sleep()
+{
+#ifdef SFA30_I2C_CLOCK_SPEED
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SFA30_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 /* SFA30_I2C_CLOCK_SPEED */
+
+ // Note - not recommended for this sensor on a periodic basis
+ if (this->isError(sfa30.stopMeasurement())) {
+ LOG_ERROR("%s: can't stop measurement");
+ };
+
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ LOG_INFO("%s: stop measurement");
+ state = State::IDLE;
+ measureStarted = 0;
+}
+
+uint32_t SFA30Sensor::wakeUp()
+{
+#ifdef SFA30_I2C_CLOCK_SPEED
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SFA30_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 /* SFA30_I2C_CLOCK_SPEED */
+
+ LOG_INFO("Waking up %s", sensorName);
+ if (this->isError(sfa30.startContinuousMeasurement())) {
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+ return 0;
+ }
+
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ state = State::ACTIVE;
+ measureStarted = getTime();
+ return SFA30_WARMUP_MS;
+}
+
+int32_t SFA30Sensor::wakeUpTimeMs()
+{
+ return SFA30_WARMUP_MS;
+}
+
+bool SFA30Sensor::canSleep()
+{
+ // Sleep is disabled in this sensor because readings are not tested with periodic sleep
+ // with such low power consumption, prefered to keep it active
+ return false;
+}
+
+bool SFA30Sensor::isActive()
+{
+ return state == State::ACTIVE;
+}
+
+int32_t SFA30Sensor::pendingForReadyMs()
+{
+ uint32_t now;
+ now = getTime();
+ uint32_t sinceHchoMeasureStarted = (now - measureStarted) * 1000;
+ LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceHchoMeasureStarted);
+
+ if (sinceHchoMeasureStarted < SFA30_WARMUP_MS) {
+ LOG_INFO("%s: not enough time passed since starting measurement", sensorName);
+ return SFA30_WARMUP_MS - sinceHchoMeasureStarted;
+ }
+ return 0;
+}
+
+bool SFA30Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ float hcho = 0.0;
+ float humidity = 0.0;
+ float temperature = 0.0;
+
+#ifdef SFA30_I2C_CLOCK_SPEED
+#ifdef CAN_RECLOCK_I2C
+ uint32_t currentClock = reClockI2C(SFA30_I2C_CLOCK_SPEED, _bus, false);
+#elif !HAS_SCREEN
+ reClockI2C(SFA30_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 /* SFA30_I2C_CLOCK_SPEED */
+
+ if (this->isError(sfa30.readMeasuredValues(hcho, humidity, temperature))) {
+ LOG_WARN("%s: No values", sensorName);
+ return false;
+ }
+
+#if defined(SFA30_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
+ reClockI2C(currentClock, _bus, false);
+#endif
+
+ measurement->variant.air_quality_metrics.has_form_temperature = true;
+ measurement->variant.air_quality_metrics.has_form_humidity = true;
+ measurement->variant.air_quality_metrics.has_form_formaldehyde = true;
+
+ measurement->variant.air_quality_metrics.form_temperature = temperature;
+ measurement->variant.air_quality_metrics.form_humidity = humidity;
+ measurement->variant.air_quality_metrics.form_formaldehyde = hcho;
+
+ LOG_DEBUG("Got %s readings: hcho=%.2f, hcho_temp=%.2f, hcho_hum=%.2f", sensorName, hcho, temperature, humidity);
+
+ return true;
+}
+#endif
diff --git a/src/modules/Telemetry/Sensor/SFA30Sensor.h b/src/modules/Telemetry/Sensor/SFA30Sensor.h
new file mode 100644
index 00000000000..9fa9c85fc5e
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SFA30Sensor.h
@@ -0,0 +1,39 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include()
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "RTC.h"
+#include "TelemetrySensor.h"
+#include
+
+#define SFA30_I2C_CLOCK_SPEED 100000
+#define SFA30_WARMUP_MS 10000
+#define SFA30_NO_ERROR 0
+
+class SFA30Sensor : public TelemetrySensor
+{
+ private:
+ enum class State { IDLE, ACTIVE };
+ State state = State::IDLE;
+ uint32_t measureStarted = 0;
+
+ SensirionI2cSfa3x sfa30;
+ TwoWire *_bus{};
+ uint8_t _address{};
+ bool isError(uint16_t response);
+
+ public:
+ SFA30Sensor();
+ virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+
+ virtual bool isActive() override;
+ virtual void sleep() override;
+ virtual uint32_t wakeUp() override;
+ virtual bool canSleep() override;
+ virtual int32_t wakeUpTimeMs() override;
+ virtual int32_t pendingForReadyMs() override;
+};
+
+#endif
diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp
index 042bc376396..819ba3da513 100644
--- a/src/serialization/MeshPacketSerializer.cpp
+++ b/src/serialization/MeshPacketSerializer.cpp
@@ -158,6 +158,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_humidity);
}
+ if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
+ msgPayload["form_formaldehyde"] = new JSONValue(decoded->variant.air_quality_metrics.form_formaldehyde);
+ }
+ if (decoded->variant.air_quality_metrics.has_form_temperature) {
+ msgPayload["form_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.form_temperature);
+ }
+ if (decoded->variant.air_quality_metrics.has_form_humidity) {
+ msgPayload["form_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.form_humidity);
+ }
} 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);
diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp
index a0ad4e4b9d7..bd0a29c51b0 100644
--- a/src/serialization/MeshPacketSerializer_nRF52.cpp
+++ b/src/serialization/MeshPacketSerializer_nRF52.cpp
@@ -129,6 +129,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_humidity;
}
+ if (decoded->variant.air_quality_metrics.has_form_formaldehyde) {
+ jsonObj["payload"]["form_formaldehyde"] = decoded->variant.air_quality_metrics.form_formaldehyde;
+ }
+ if (decoded->variant.air_quality_metrics.has_form_temperature) {
+ jsonObj["payload"]["form_temperature"] = decoded->variant.air_quality_metrics.form_temperature;
+ }
+ if (decoded->variant.air_quality_metrics.has_form_humidity) {
+ jsonObj["payload"]["form_humidity"] = decoded->variant.air_quality_metrics.form_humidity;
+ }
} 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;