feat/add sfa30 (#9372)
* Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Initial implementation for SFA30Sensor * Move PMSA003I to separate class and update AQ telemetry * AirQualityTelemetry module not depend on PM sensor presence * Remove commented line * Fixes on PMS class * Add missing warmup period to wakeUp function * Fixes on compilation for different variants * Add functions to check for I2C bus speed and set it * Add ScreenFonts.h Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de> * PMSA003I 1st round test * Fix I2C scan speed * Fix minor issues and bring back I2C SPEED def * Remove PMSA003I library as its no longer needed * Add functional SCD4X * Fix screen frame for CO2 * Add admin commands to SCD4X class * Add further admin commands and fixes. * Remove unused I2C speed functions and cleanup * Cleanup of SEN5X specific code added from switching branches * Remove SCAN_I2C_CLOCK_SPEED block as its not needed * Remove associated functions for setting I2C speed * Unify build epoch to add flag in platformio-custom.py (#7917) * Unify build_epoch replacement logic in platformio-custom * Missed one * Fix build error in rak_wismesh_tap_v2 (#7905) In the logs was: "No screen resolution defined in build_flags. Please define DISPLAY_SIZE." set according to similar devices. * Put guards in place around debug heap operations (#7955) * Put guards in place around debug heap operations * Add macros to clean up code * Add pointer as well * Cleanup * Fix memory leak in NextHopRouter: always free packet copy when removing from pending * Formatting * Only queue 2 client notification * Merge pull request #7965 from compumike/compumike/fix-nrf52-bluetooth-memory-leak Fix memory leak in `NRF52Bluetooth`: allocate `BluetoothStatus` on stack, not heap * Merge pull request #7964 from compumike/compumike/fix-nimble-bluetooth-memory-leak Fix memory leak in `NimbleBluetooth`: allocate `BluetoothStatus` on stack, not heap * Update protobufs (#7973) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs * Trunk * Trunk * Static memory pool allocation (#7966) * Static memory pool * Initializer * T-Lora Pager: Support LR1121 and SX1280 models (#7956) * T-Lora Pager: Support LR1121 and SX1280 models * Remove ifdefs --------- Co-authored-by: WillyJL <me@willyjl.dev> * Portduino dynamic alloc * Missed * Drop the limit * Update meshtastic-esp8266-oled-ssd1306 digest to 0cbc26b (#7977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Fix json report crashes on esp32 (#7978) * Tweak maximums * Fix DRAM overflow on old esp32 targets * Guard bad time warning logs using GPS_DEBUG (#7897) In 2.7.7 / 2.7.8 we introduced some new checks for time accuracy. In combination, these result in a spamming of the logs when a bad time is found When the GPS is active, we're calling the GPS thread every 0.2secs. So this log could be printed 4,500 times in a no-lock scenario :) Reserve this experience for developers using GPS_DEBUG. Fixes https://github.com/meshtastic/firmware/issues/7896 * Scale probe buffer size based on current baud rate (#7975) * Scale probe buffer size based on current baud rate * Throttle bad time validation logging and fix time comparison logic * Remove comment * Missed the other instances * Copy pasta * Fix GPS gm_mktime memory leak (#7981) * Fix overflow of time value (#7984) * Fix overflow of time value * Revert "Fix overflow of time value" This reverts commit 084796920179e80a7500d36c25fd4d82b3ef4214. * That got boogered up * Remove PMSA003 include from modules * Add flag to exclude air quality module * Rework PMSA003I to align with new I2C scanner * Reworks AQ telemetry to match new dynamic allocation method * Adds VBLE_I2C_CLOCK_SPEED build flag for sensors with different I2C speed requirements * Reworks PMSA003I * Move add sensor template to separate file * Split telemetry on screen options * Add variable I2C clock compile flag * Added to Seeed Xiao S3 as demo * Fix drawFrame in AQ module * Module settings override to i2cScan module function * Move to CAN_RECLOCK_I2C per architecture * Add reclock function in TelemetrySensor.cpp * Add flag in ESP32 common * Minor fix * Move I2C reclock function to src/detect * Fix uninitMemberVar errors and compile issue * Make sleep, wakeUp functions generic * Fix STM32 builds * Add exclude AQ sensor to builds that have environmental sensor excludes * Add includes to AddI2CSensorTemplate.h * SEN5X first pass * WIP Sen5X functions * Further (non-working) progress in SEN5X * WIP Sen5X functions * Changes on SEN5X library - removing pm_env as well * Small cleanup of SEN5X sensors * Minor change for SEN5X detection * Remove dup code * Enable PM sensor before sending telemetry. This enables the PM sensor for a predefined period to allow for warmup. Once telemetry is sent, the sensor shuts down again. * Small cleanups in SEN5X sensor * Add dynamic measurement interval for SEN5X * Only disable SEN5X if enough time after reading. * Idle for SEN5X on communication error * Cleanup of logs and remove unnecessary delays * Small TODO * Settle on uint16_t for SEN5X PM data * Make AQTelemetry sensors non-exclusive * Implementation of cleaning in FS prefs and cleanup * Remove unnecessary LOGS * Add cleaning date storage in FS * Report non-cumulative PN * Bring back detection code for SEN5X after branch rebase * Add placeholder for admin message * Add VOC measurements and persistence (WIP) * Adds VOC measurements and state * Still not working on VOC Index persistence * Should it stay in continuous mode? * Add one-shot mode config flag to SEN5X * Add nan checks on sensor data from SEN5X * Working implementation on VOCState * Adds initial timer for SEN55 to not sleep if VOCstate is not stable (1h) * Adds conditions for stability and sensor state * Fixes on VOC state and mode swtiching * Adds a new RHT/Gas only mode, with 3600s stabilization time * Fixes the VOCState buffer mismatch * Fixes SEN50/54/55 model mistake * Adapt SEN5X to new sensor list structure. Improve reclock. * Improve reClockI2C conditions for different variants * Add sleep, wakeUp, pendingForReady, hasSleep functions to PM sensors to save battery * Add SEN5X * Fix merge errors * Small reordering in PMS class for consistency * If one sensor fails, AQ telemetry still reports data * Small formatting fix * Add SEN5X to AQI in ScanI2C * SCD4X now part of AQ module with template list * Fixes difference between idle and sleep * In LowPower, sleep is disabled * Requires testing for I2C clock comms for commands * Remove unnecessary import * Add co2 to serialized AQ metrics * Add SFA30 with new sensor template in AQ module * Update library dependencies in platformio.ini * Fix unitialized variables in SEN5X constructor * Fix missing import * Fix uninitMemberVars * Fix import error for SCD4X * Fix I2CClock logic * Fix not reclocking back to 700000Hz * Fix multiple sensors being read simultaneously * The logic in AQ module is different to the one in EnvironmentTelemetryModule. In Env module, if any sensor fails to getMetrics, no valid flag for the module. This prevents other sensors to report data. * Fix pending clock change in PMSA003 * Cleanup of SEN5X class * Exclude AQ sensor from wio-e5 due to flash limitations * Fix I2C clock change logic * Make sure clock is always set to needed value * Fix returns * Fix trunk * Fix on condition in reclock * Fix trunk * Final SFA30 class implementation * Add HCHO to screen and improve logs * Add metrics to mesh packet serializer * Minor fixes in logs * OCD tidy up of logs * Fix sleep function * Remove old I2C_CLOCK_SPEED code --------- Co-authored-by: nikl <nikl174@mailbox.org> Co-authored-by: Hannes Fuchs <hannes.fuchs+git@0xef.de> Co-authored-by: Nashui-Yan <yannashui10@gmail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Tom Fifield <tom@tomfifield.net> Co-authored-by: Mike Robbins <mrobbins@alum.mit.edu> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> Co-authored-by: WillyJL <me@willyjl.dev> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
GitHub
nikl
Hannes Fuchs
Nashui-Yan
Ben Meadors
Tom Fifield
Mike Robbins
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
thebentern
WillyJL
renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
parent
6d299eac67
commit
15297cb56f
@@ -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
|
||||
|
||||
@@ -217,6 +217,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,8 +89,9 @@ class ScanI2C
|
||||
DA217,
|
||||
CHSC6X,
|
||||
CST226SE,
|
||||
CW2015,
|
||||
SEN5X
|
||||
SEN5X,
|
||||
SFA30,
|
||||
CW2015
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(<SensirionI2cScd4x.h>)
|
||||
#include "Sensor/SCD4XSensor.h"
|
||||
#endif
|
||||
#if __has_include(<SensirionI2cSfa3x.h>)
|
||||
#include "Sensor/SFA30Sensor.h"
|
||||
#endif
|
||||
|
||||
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
{
|
||||
@@ -50,6 +54,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
#if __has_include(<SensirionI2cScd4x.h>)
|
||||
addSensor<SCD4XSensor>(i2cScanner, ScanI2C::DeviceType::SCD4X);
|
||||
#endif
|
||||
#if __has_include(<SensirionI2cSfa3x.h>)
|
||||
addSensor<SFA30Sensor>(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
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
#endif
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cSfa3x.h>)
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,39 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cSfa3x.h>)
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "RTC.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <SensirionI2cSfa3x.h>
|
||||
|
||||
#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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user