Compare commits

...

29 Commits

Author SHA1 Message Date
Riley Nielsen
fc4ad3c0f6
Merge f0775c586f into 8a8f60d129 2025-09-03 11:05:12 +02:00
github-actions[bot]
8a8f60d129
Update protobufs (#7831)
Some checks are pending
CI / build-rp2040 (push) Blocked by required conditions
CI / build-rp2350 (push) Blocked by required conditions
CI / build-stm32 (push) Blocked by required conditions
CI / build-debian-src (push) Waiting to run
CI / package-pio-deps-native-tft (push) Waiting to run
CI / test-native (push) Waiting to run
CI / docker-deb-amd64 (push) Waiting to run
CI / docker-deb-amd64-tft (push) Waiting to run
CI / docker-alp-amd64 (push) Waiting to run
CI / docker-alp-amd64-tft (push) Waiting to run
CI / docker-deb-arm64 (push) Waiting to run
CI / docker-deb-armv7 (push) Waiting to run
CI / gather-artifacts (esp32) (push) Blocked by required conditions
CI / gather-artifacts (esp32c3) (push) Blocked by required conditions
CI / gather-artifacts (esp32c6) (push) Blocked by required conditions
CI / gather-artifacts (esp32s3) (push) Blocked by required conditions
CI / gather-artifacts (nrf52840) (push) Blocked by required conditions
CI / gather-artifacts (rp2040) (push) Blocked by required conditions
CI / gather-artifacts (rp2350) (push) Blocked by required conditions
CI / gather-artifacts (stm32) (push) Blocked by required conditions
CI / release-artifacts (push) Blocked by required conditions
CI / release-firmware (esp32) (push) Blocked by required conditions
CI / release-firmware (esp32c3) (push) Blocked by required conditions
CI / release-firmware (esp32c6) (push) Blocked by required conditions
CI / release-firmware (esp32s3) (push) Blocked by required conditions
CI / release-firmware (nrf52840) (push) Blocked by required conditions
CI / release-firmware (rp2040) (push) Blocked by required conditions
CI / release-firmware (rp2350) (push) Blocked by required conditions
CI / release-firmware (stm32) (push) Blocked by required conditions
CI / publish-firmware (push) Blocked by required conditions
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-09-02 19:08:05 -05:00
renovate[bot]
b59409bec0
chore(deps): update caveman99-stm32-crypto digest to 1aa30eb (#7808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 18:01:31 -05:00
renovate[bot]
c66125114f
chore(deps): update meshtastic/device-ui digest to 8019704 (#7830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 16:17:00 -05:00
Austin
f0775c586f
Merge branch 'master' into scd4x 2025-07-21 16:30:32 -04:00
Thomas Göttgens
80175b1e17
Merge branch 'master' into scd4x 2025-07-13 18:17:58 +02:00
Tom Fifield
1eeadde989
Merge branch 'master' into scd4x 2025-07-02 09:24:21 +10:00
Tom Fifield
cfc355b29a
Update main.cpp 2025-07-01 21:31:23 +10:00
Tom Fifield
ef203b5dca
Merge branch 'master' into scd4x 2025-07-01 21:28:58 +10:00
Tom Fifield
58d916da39
Fix merge 2025-07-01 19:49:42 +10:00
Tom Fifield
02a84142df
Merge branch 'master' into scd4x 2025-07-01 19:35:38 +10:00
Tom Fifield
726cf8611b
Merge branch 'master' into scd4x 2025-07-01 18:33:09 +10:00
Thomas Göttgens
bac816d80a
Merge branch 'master' into scd4x 2025-04-11 15:45:09 +02:00
Thomas Göttgens
7fe9efe805
Wupps 2025-04-07 10:03:40 +02:00
Thomas Göttgens
ad7647ef95
Merge branch 'master' into scd4x 2025-04-07 09:22:38 +02:00
Thomas Göttgens
3cef1e89ed
Merge branch 'master' into scd4x 2025-03-31 11:34:04 +02:00
Tom Fifield
a1cbe8ebb8
Merge branch 'master' into scd4x 2024-11-19 13:55:28 +08:00
Thomas Göttgens
16e2540f0f
Merge branch 'master' into scd4x 2024-11-07 17:37:05 +01:00
Ben Meadors
e34aadabe1
Merge branch 'master' into scd4x 2024-10-20 05:44:08 -05:00
Tom Fifield
81f9b38c11
Merge branch 'master' into scd4x 2024-10-19 13:03:26 +11:00
Tom Fifield
8faf4665b0 Re-add I2C scan, fix frame. 2024-10-19 13:00:25 +11:00
Tom Fifield
318da220dc Abstract AirQualityTelemetry module
Previously the module was designed around a single sensor.
This patch brings it into line with the same design as
EnvironmentTelemetry.
2024-10-18 18:16:45 +11:00
Tom Fifield
a28e83c35a
Merge branch 'master' into scd4x 2024-10-18 16:48:27 +11:00
Riley Nielsen
8a4427a69f Merge remote-tracking branch 'upstream/master' into scd4x 2024-10-11 09:53:09 -07:00
Riley Nielsen
c9233fef1c Merge remote-tracking branch 'upstream/master' into scd4x 2024-10-11 09:19:55 -07:00
Riley Nielsen
f599984990 update from upstream 2024-09-25 18:34:17 -07:00
Riley Nielsen
e73ff62b22 Merge remote-tracking branch 'upstream/master' into scd4x 2024-09-25 17:38:00 -07:00
Thomas Göttgens
30532d4c6a
Merge branch 'master' into scd4x 2024-09-02 10:33:12 +02:00
Riley Nielsen
8fa513ff1f add CO2 sensing with SCD4X 2024-08-31 16:32:53 -07:00
13 changed files with 304 additions and 30 deletions

View File

@ -50,7 +50,7 @@ lib_deps =
${radiolib_base.lib_deps} ${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main # renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
lib_ignore = lib_ignore =
OneButton OneButton

View File

@ -118,7 +118,7 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/a3e0e1be372d069f47b4c19d718f5267251744d7.zip https://github.com/meshtastic/device-ui/archive/8019704395b7539600d581330499208edcd80804.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]

@ -1 +1 @@
Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81 Subproject commit 34f0c8115d95f9f4be6d600095428a03833ac98e

View File

@ -360,7 +360,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */ /* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2271 #define meshtastic_BackupPreferences_size 2273
#define meshtastic_ChannelFile_size 718 #define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737 #define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196 #define meshtastic_NodeInfoLite_size 196

View File

@ -188,7 +188,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */ /* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size #define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define meshtastic_LocalConfig_size 747 #define meshtastic_LocalConfig_size 747
#define meshtastic_LocalModuleConfig_size 669 #define meshtastic_LocalModuleConfig_size 671
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -317,6 +317,9 @@ typedef struct _meshtastic_ModuleConfig_RangeTestConfig {
/* Bool value indicating that this node should save a RangeTest.csv file. /* Bool value indicating that this node should save a RangeTest.csv file.
ESP32 Only */ ESP32 Only */
bool save; bool save;
/* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
ESP32 Only */
bool clear_on_reboot;
} meshtastic_ModuleConfig_RangeTestConfig; } meshtastic_ModuleConfig_RangeTestConfig;
/* Configuration for both device and environment metrics */ /* Configuration for both device and environment metrics */
@ -519,7 +522,7 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
@ -535,7 +538,7 @@ extern "C" {
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0} #define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0} #define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
@ -610,6 +613,7 @@ extern "C" {
#define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1 #define meshtastic_ModuleConfig_RangeTestConfig_enabled_tag 1
#define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2 #define meshtastic_ModuleConfig_RangeTestConfig_sender_tag 2
#define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3 #define meshtastic_ModuleConfig_RangeTestConfig_save_tag 3
#define meshtastic_ModuleConfig_RangeTestConfig_clear_on_reboot_tag 4
#define meshtastic_ModuleConfig_TelemetryConfig_device_update_interval_tag 1 #define meshtastic_ModuleConfig_TelemetryConfig_device_update_interval_tag 1
#define meshtastic_ModuleConfig_TelemetryConfig_environment_update_interval_tag 2 #define meshtastic_ModuleConfig_TelemetryConfig_environment_update_interval_tag 2
#define meshtastic_ModuleConfig_TelemetryConfig_environment_measurement_enabled_tag 3 #define meshtastic_ModuleConfig_TelemetryConfig_environment_measurement_enabled_tag 3
@ -803,7 +807,8 @@ X(a, STATIC, SINGULAR, BOOL, is_server, 6)
#define meshtastic_ModuleConfig_RangeTestConfig_FIELDLIST(X, a) \ #define meshtastic_ModuleConfig_RangeTestConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \ X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, UINT32, sender, 2) \ X(a, STATIC, SINGULAR, UINT32, sender, 2) \
X(a, STATIC, SINGULAR, BOOL, save, 3) X(a, STATIC, SINGULAR, BOOL, save, 3) \
X(a, STATIC, SINGULAR, BOOL, clear_on_reboot, 4)
#define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL #define meshtastic_ModuleConfig_RangeTestConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL #define meshtastic_ModuleConfig_RangeTestConfig_DEFAULT NULL
@ -901,7 +906,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_MapReportSettings_size 14 #define meshtastic_ModuleConfig_MapReportSettings_size 14
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 10 #define meshtastic_ModuleConfig_NeighborInfoConfig_size 10
#define meshtastic_ModuleConfig_PaxcounterConfig_size 30 #define meshtastic_ModuleConfig_PaxcounterConfig_size 30
#define meshtastic_ModuleConfig_RangeTestConfig_size 10 #define meshtastic_ModuleConfig_RangeTestConfig_size 12
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96 #define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28 #define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24 #define meshtastic_ModuleConfig_StoreForwardConfig_size 24

View File

@ -10,10 +10,19 @@
#include "PowerFSM.h" #include "PowerFSM.h"
#include "RTC.h" #include "RTC.h"
#include "Router.h" #include "Router.h"
#include "UnitConversions.h"
#include "detect/ScanI2CTwoWire.h" #include "detect/ScanI2CTwoWire.h"
#include "graphics/ScreenFonts.h"
#include "main.h" #include "main.h"
#include "sleep.h"
#include <Throttle.h> #include <Throttle.h>
// Sensors
#include "Sensor/PMSA0031Sensor.h"
#include "Sensor/SCD4XSensor.h"
SCD4XSensor scd4xSensor;
PMSA0031Sensor pmsa0031Sensor;
#ifndef PMSA003I_WARMUP_MS #ifndef PMSA003I_WARMUP_MS
// from the PMSA003I datasheet: // from the PMSA003I datasheet:
// "Stable data should be got at least 30 seconds after the sensor wakeup // "Stable data should be got at least 30 seconds after the sensor wakeup
@ -23,11 +32,20 @@
int32_t AirQualityTelemetryModule::runOnce() int32_t AirQualityTelemetryModule::runOnce()
{ {
if (sleepOnNextExecution == true) {
sleepOnNextExecution = false;
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval,
default_telemetry_broadcast_interval_secs);
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs);
doDeepSleep(nightyNightMs, true);
}
uint32_t result = UINT32_MAX;
/* /*
Uncomment the preferences below if you want to use the module Uncomment the preferences below if you want to use the module
without having to configure it from the PythonAPI or WebUI. without having to configure it from the PythonAPI or WebUI.
*/ */
// moduleConfig.telemetry.air_quality_enabled = 1; // moduleConfig.telemetry.air_quality_enabled = 1;
if (!(moduleConfig.telemetry.air_quality_enabled)) { if (!(moduleConfig.telemetry.air_quality_enabled)) {
@ -41,24 +59,27 @@ int32_t AirQualityTelemetryModule::runOnce()
if (moduleConfig.telemetry.air_quality_enabled) { if (moduleConfig.telemetry.air_quality_enabled) {
LOG_INFO("Air quality Telemetry: init"); LOG_INFO("Air quality Telemetry: init");
#ifdef PMSA003I_ENABLE_PIN #ifdef PMSA003I_ENABLE_PIN
// put the sensor to sleep on startup // put the sensor to sleep on startup
pinMode(PMSA003I_ENABLE_PIN, OUTPUT); pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
digitalWrite(PMSA003I_ENABLE_PIN, LOW); digitalWrite(PMSA003I_ENABLE_PIN, LOW);
#endif /* PMSA003I_ENABLE_PIN */ #endif /* PMSA003I_ENABLE_PIN */
if (aqi_found.address == 0x00) {
if (!aqi.begin_I2C()) {
#ifndef I2C_NO_RESCAN #ifndef I2C_NO_RESCAN
LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan"); LOG_WARN("Rescan for I2C AQI Sensor");
// rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty. // rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty.
uint8_t i2caddr_scan[] = {PMSA0031_ADDR}; uint8_t i2caddr_scan[] = {PMSA0031_ADDR, SCD4X_ADDR};
uint8_t i2caddr_asize = 1; uint8_t i2caddr_asize = 2;
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire()); auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if defined(I2C_SDA1)
#if WIRE_INTERFACES_COUNT == 2
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
#endif #endif
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize); i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031); auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031);
if (found.type != ScanI2C::DeviceType::NONE) { if (found.type != ScanI2C::DeviceType::NONE) {
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address; nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
@ -66,8 +87,36 @@ int32_t AirQualityTelemetryModule::runOnce()
i2cScanner->fetchI2CBus(found.address); i2cScanner->fetchI2CBus(found.address);
return setStartDelay(); return setStartDelay();
} }
#endif if (aqi_found.address == 0x00) {
return disable();
}
#endif
}
if (scd4xSensor.hasSensor())
result = scd4xSensor.runOnce();
if (pmsa0031Sensor.hasSensor())
result = pmsa0031Sensor.runOnce();
return result;
} else {
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
if (!moduleConfig.telemetry.air_quality_enabled)
return disable(); return disable();
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
sendTelemetry(NODENUM_BROADCAST, true);
lastSentToPhone = millis();
} }
return setStartDelay(); return setStartDelay();
} }
@ -115,6 +164,7 @@ int32_t AirQualityTelemetryModule::runOnce()
default: default:
return disable(); return disable();
} }
return min(sendToPhoneIntervalMs, result);
} }
} }
@ -124,9 +174,9 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE) #if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
const char *sender = getSenderShortName(mp); const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender, LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i, co2=%i ppm", sender,
t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard, t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
t->variant.air_quality_metrics.pm100_standard); t->variant.air_quality_metrics.pm100_standard, t->variant.air_quality_metrics.co2);
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", 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.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental,
@ -142,13 +192,38 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
return false; // Let others look at this message also if they want return false; // Let others look at this message also if they want
} }
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m) bool AirQualityTelemetryModule::wantUIFrame()
{ {
if (!aqi.read(&data)) { return moduleConfig.telemetry.environment_screen_enabled;
LOG_WARN("Skip send measurements. Could not read AQIn"); }
return false;
void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
if (lastMeasurementPacket == nullptr) {
// If there's no valid packet, display "Environment"
display->drawString(x, y, "Air Quality");
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
return;
} }
// Decode the last measurement packet
meshtastic_Telemetry lastMeasurement;
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
const char *lastSender = getSenderShortName(*lastMeasurementPacket);
const meshtastic_Data &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
display->drawString(x, y, "Measurement Error");
LOG_ERROR("Unable to decode last packet");
return;
}
// Display "Env. From: ..." on its own
display->drawString(x, y, "AQ. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
m->time = getTime(); m->time = getTime();
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag; m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m->variant.air_quality_metrics.has_pm10_standard = true; m->variant.air_quality_metrics.has_pm10_standard = true;
@ -168,11 +243,42 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", m->variant.air_quality_metrics.pm10_standard, LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", 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.pm25_standard, m->variant.air_quality_metrics.pm100_standard);
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i", if (lastMeasurement.variant.air_quality_metrics.has_pm10_standard) {
m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental, display->drawString(x, y += _fontHeight(FONT_SMALL),
m->variant.air_quality_metrics.pm100_environmental); "PM1.0(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm10_standard, 0));
}
if (lastMeasurement.variant.air_quality_metrics.has_pm25_standard) {
display->drawString(x, y += _fontHeight(FONT_SMALL),
"PM2.5(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm25_standard, 0));
}
if (lastMeasurement.variant.air_quality_metrics.has_pm10_environmental) {
display->drawString(x, y += _fontHeight(FONT_SMALL),
"PM10.0(Standard): " + String(lastMeasurement.variant.air_quality_metrics.pm100_standard, 0));
}
if (lastMeasurement.variant.air_quality_metrics.has_co2) {
display->drawString(x, y += _fontHeight(FONT_SMALL),
"CO2: " + String(lastMeasurement.variant.air_quality_metrics.co2, 0) + " ppm");
}
}
return true; bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
{
bool valid = true;
bool hasSensor = false;
m->time = getTime();
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
if (scd4xSensor.hasSensor()) {
valid = valid && scd4xSensor.getMetrics(m);
hasSensor = true;
}
if (pmsa0031Sensor.hasSensor()) {
valid = valid && pmsa0031Sensor.getMetrics(m);
hasSensor = true;
}
return valid && hasSensor;
} }
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply() meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
@ -207,6 +313,14 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{ {
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero; meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
if (getAirQualityTelemetry(&m)) { if (getAirQualityTelemetry(&m)) {
LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i, cO2=%i ppm",
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.co2);
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental,
m.variant.air_quality_metrics.pm100_environmental);
meshtastic_MeshPacket *p = allocDataProtobuf(m); meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest; p->to = dest;
p->decoded.want_response = false; p->decoded.want_response = false;

View File

@ -4,9 +4,10 @@
#pragma once #pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h" #include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Adafruit_PM25AQI.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "ProtobufModule.h" #include "ProtobufModule.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry> class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
{ {
@ -20,9 +21,8 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg) ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{ {
lastMeasurementPacket = nullptr; lastMeasurementPacket = nullptr;
setIntervalFromNow(10 * 1000);
aqi = Adafruit_PM25AQI();
nodeStatusObserver.observe(&nodeStatus->onNewStatus); nodeStatusObserver.observe(&nodeStatus->onNewStatus);
setIntervalFromNow(10 * 1000);
#ifdef PMSA003I_ENABLE_PIN #ifdef PMSA003I_ENABLE_PIN
// the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking // the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking
@ -32,6 +32,12 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
state = State::ACTIVE; state = State::ACTIVE;
#endif #endif
} }
virtual bool wantUIFrame() override;
#if !HAS_SCREEN
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#else
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
protected: protected:
/** Called to handle a particular incoming message /** Called to handle a particular incoming message
@ -62,6 +68,8 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
meshtastic_MeshPacket *lastMeasurementPacket; meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0; uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
}; };
#endif #endif

View File

@ -510,6 +510,7 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current, sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current,
t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity, t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity,
t->variant.environment_metrics.temperature); t->variant.environment_metrics.temperature);
LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f, white_lux=%f", sender, LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f, white_lux=%f", sender,
t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq, t->variant.environment_metrics.voltage, t->variant.environment_metrics.iaq,
t->variant.environment_metrics.distance, t->variant.environment_metrics.lux, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux,

View File

@ -0,0 +1,47 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "PMSA0031Sensor.h"
#include "TelemetrySensor.h"
#include <Adafruit_PM25AQI.h>
PMSA0031Sensor::PMSA0031Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA0031") {}
int32_t PMSA0031Sensor::runOnce()
{
LOG_INFO("Init sensor: %s\n", sensorName);
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
aqi = Adafruit_PM25AQI();
delay(10000);
aqi.begin_I2C();
/* nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
i2cScanner->fetchI2CBus(found.address););*/
return initI2CSensor();
}
void PMSA0031Sensor::setup() {}
bool PMSA0031Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
uint16_t co2, error;
float temperature, humidity;
if (!aqi.read(&data)) {
LOG_WARN("Skipping send measurements. Could not read AQIn");
return false;
}
measurement->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
measurement->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
measurement->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
measurement->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
measurement->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
measurement->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
return true;
}
#endif

View File

@ -0,0 +1,24 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <Adafruit_PM25AQI.h>
class PMSA0031Sensor : public TelemetrySensor
{
private:
Adafruit_PM25AQI aqi;
PM25_AQI_Data data = {0};
protected:
virtual void setup() override;
public:
PMSA0031Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
#endif

View File

@ -0,0 +1,52 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "SCD4XSensor.h"
#include "TelemetrySensor.h"
#include <SensirionI2CScd4x.h>
SCD4XSensor::SCD4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD4X, "SCD4X") {}
int32_t SCD4XSensor::runOnce()
{
LOG_INFO("Init sensor: %s\n", sensorName);
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
// scd4x = SensirionI2CScd4x(nodeTelemetrySensorsMap[sensorType].second);
// status = scd4x.begin(nodeTelemetrySensorsMap[sensorType].first);
scd4x.begin(*nodeTelemetrySensorsMap[sensorType].second);
scd4x.stopPeriodicMeasurement();
status = scd4x.startLowPowerPeriodicMeasurement();
if (status == 0) {
status = 1;
} else {
status = 0;
}
return initI2CSensor();
}
void SCD4XSensor::setup() {}
bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement)
{
uint16_t co2, error;
float temperature, humidity;
error = scd4x.readMeasurement(co2, temperature, humidity);
if (error || co2 == 0) {
LOG_DEBUG("Skipping invalid SCD4X measurement.\n");
return false;
} else {
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.has_relative_humidity = true;
measurement->variant.air_quality_metrics.has_co2 = true;
measurement->variant.environment_metrics.temperature = temperature;
measurement->variant.environment_metrics.relative_humidity = humidity;
measurement->variant.air_quality_metrics.co2 = co2;
return true;
}
}
#endif

View File

@ -0,0 +1,23 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SensirionI2CScd4x.h>
class SCD4XSensor : public TelemetrySensor
{
private:
SensirionI2CScd4x scd4x = SensirionI2CScd4x();
protected:
virtual void setup() override;
public:
SCD4XSensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
#endif