diff --git a/platformio.ini b/platformio.ini
index 1d8083f70..a833c42a5 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -2,7 +2,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
-;default_envs = tbeam
+default_envs = tbeam
;default_envs = pico
;default_envs = tbeam-s3-core
;default_envs = tbeam0.7
@@ -109,3 +109,4 @@ lib_deps =
adafruit/Adafruit SHTC3 Library@^1.0.0
adafruit/Adafruit LPS2X@^2.0.4
adafruit/Adafruit SHT31 Library@^2.2.0
+ adafruit/Adafruit PM25 AQI Sensor@^1.0.6
diff --git a/protobufs b/protobufs
index 516074f2e..384b664a7 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 516074f2e49743c234430abb2ea43ea3f66b0acb
+Subproject commit 384b664a759592d9393642ba98835f69bb8f2fb2
diff --git a/src/configuration.h b/src/configuration.h
index 6181034a5..f35fff4e0 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -116,6 +116,7 @@ along with this program. If not, see .
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_ADDR 0x44
+#define PMSA0031_ADDR 0x12
// -----------------------------------------------------------------------------
// Security
diff --git a/src/detect/i2cScan.h b/src/detect/i2cScan.h
index 87ca55957..a2d7b7baf 100644
--- a/src/detect/i2cScan.h
+++ b/src/detect/i2cScan.h
@@ -218,6 +218,10 @@ void scanI2Cdevice()
LOG_INFO("QMC5883L Highrate 3-Axis magnetic sensor found\n");
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_QMC5883L] = addr;
}
+ if (addr == PMSA0031_ADDR) {
+ LOG_INFO("PMSA0031 air quality sensor found\n");
+ nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I] = addr;
+ }
} else if (err == 4) {
LOG_ERROR("Unknow error at address 0x%x\n", addr);
}
diff --git a/src/main.cpp b/src/main.cpp
index 49963a415..89148c22b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -276,7 +276,6 @@ void setup()
LOG_INFO("PCF8563 RTC found\n");
}
#endif
-
// We need to scan here to decide if we have a screen for nodeDB.init()
scanI2Cdevice();
diff --git a/src/mesh/generated/meshtastic/admin.pb.c b/src/mesh/generated/meshtastic/admin.pb.c
index 1f668dabc..c897c1760 100644
--- a/src/mesh/generated/meshtastic/admin.pb.c
+++ b/src/mesh/generated/meshtastic/admin.pb.c
@@ -9,6 +9,9 @@
PB_BIND(meshtastic_AdminMessage, meshtastic_AdminMessage, 2)
+PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index e35b79a57..25bc77241 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -57,6 +57,18 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
} meshtastic_AdminMessage_ModuleConfigType;
/* Struct definitions */
+/* Parameters for setting up Meshtastic for ameteur radio usage */
+typedef struct _meshtastic_HamParameters {
+ /* Amateur radio call sign, eg. KD2ABC */
+ char call_sign[8];
+ /* Transmit power in dBm at the LoRA transceiver, not including any amplification */
+ int32_t tx_power;
+ /* The selected frequency of LoRA operation
+ Please respect your local laws, regulations, and band plans.
+ Ensure your radio is capable of operating of the selected frequency before setting this. */
+ float frequency;
+} meshtastic_HamParameters;
+
/* This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
This message is used to do settings operations to both remote AND local nodes.
(Prior to 1.2 these operations were done via special ToRadio operations) */
@@ -96,6 +108,8 @@ typedef struct _meshtastic_AdminMessage {
bool get_device_connection_status_request;
/* Device connection status response */
meshtastic_DeviceConnectionStatus get_device_connection_status_response;
+ /* Setup a node for licensed amateur (ham) radio operation */
+ meshtastic_HamParameters set_ham_mode;
/* Set the owner for this node */
meshtastic_User set_owner;
/* Set channels (using the new API).
@@ -152,11 +166,17 @@ extern "C" {
#define meshtastic_AdminMessage_payload_variant_get_module_config_request_ENUMTYPE meshtastic_AdminMessage_ModuleConfigType
+
/* Initializer values for message structs */
#define meshtastic_AdminMessage_init_default {0, {0}}
+#define meshtastic_HamParameters_init_default {"", 0, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}}
+#define meshtastic_HamParameters_init_zero {"", 0, 0}
/* Field tags (for use in manual encoding/decoding) */
+#define meshtastic_HamParameters_call_sign_tag 1
+#define meshtastic_HamParameters_tx_power_tag 2
+#define meshtastic_HamParameters_frequency_tag 3
#define meshtastic_AdminMessage_get_channel_request_tag 1
#define meshtastic_AdminMessage_get_channel_response_tag 2
#define meshtastic_AdminMessage_get_owner_request_tag 3
@@ -173,6 +193,7 @@ extern "C" {
#define meshtastic_AdminMessage_get_ringtone_response_tag 15
#define meshtastic_AdminMessage_get_device_connection_status_request_tag 16
#define meshtastic_AdminMessage_get_device_connection_status_response_tag 17
+#define meshtastic_AdminMessage_set_ham_mode_tag 18
#define meshtastic_AdminMessage_set_owner_tag 32
#define meshtastic_AdminMessage_set_channel_tag 33
#define meshtastic_AdminMessage_set_config_tag 34
@@ -206,6 +227,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,get_ringtone_request,get_rin
X(a, STATIC, ONEOF, STRING, (payload_variant,get_ringtone_response,get_ringtone_response), 15) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,get_device_connection_status_request,get_device_connection_status_request), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_device_connection_status_response,get_device_connection_status_response), 17) \
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_ham_mode,set_ham_mode), 18) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_owner,set_owner), 32) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 33) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \
@@ -228,18 +250,29 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,nodedb_reset,nodedb_reset),
#define meshtastic_AdminMessage_payload_variant_get_module_config_response_MSGTYPE meshtastic_ModuleConfig
#define meshtastic_AdminMessage_payload_variant_get_device_metadata_response_MSGTYPE meshtastic_DeviceMetadata
#define meshtastic_AdminMessage_payload_variant_get_device_connection_status_response_MSGTYPE meshtastic_DeviceConnectionStatus
+#define meshtastic_AdminMessage_payload_variant_set_ham_mode_MSGTYPE meshtastic_HamParameters
#define meshtastic_AdminMessage_payload_variant_set_owner_MSGTYPE meshtastic_User
#define meshtastic_AdminMessage_payload_variant_set_channel_MSGTYPE meshtastic_Channel
#define meshtastic_AdminMessage_payload_variant_set_config_MSGTYPE meshtastic_Config
#define meshtastic_AdminMessage_payload_variant_set_module_config_MSGTYPE meshtastic_ModuleConfig
+#define meshtastic_HamParameters_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
+X(a, STATIC, SINGULAR, INT32, tx_power, 2) \
+X(a, STATIC, SINGULAR, FLOAT, frequency, 3)
+#define meshtastic_HamParameters_CALLBACK NULL
+#define meshtastic_HamParameters_DEFAULT NULL
+
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
+extern const pb_msgdesc_t meshtastic_HamParameters_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
+#define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg
/* Maximum encoded size of messages (where known) */
#define meshtastic_AdminMessage_size 234
+#define meshtastic_HamParameters_size 25
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.c b/src/mesh/generated/meshtastic/telemetry.pb.c
index cdc01710a..cbcac3e20 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.c
+++ b/src/mesh/generated/meshtastic/telemetry.pb.c
@@ -12,6 +12,9 @@ PB_BIND(meshtastic_DeviceMetrics, meshtastic_DeviceMetrics, AUTO)
PB_BIND(meshtastic_EnvironmentMetrics, meshtastic_EnvironmentMetrics, AUTO)
+PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO)
+
+
PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, AUTO)
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index 188b27d77..6d4a5e1fc 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -10,7 +10,7 @@
#endif
/* Enum definitions */
-/* TODO: REPLACE */
+/* Supported I2C Sensors for telemetry in Meshtastic */
typedef enum _meshtastic_TelemetrySensorType {
/* No external telemetry sensor explicitly set */
meshtastic_TelemetrySensorType_SENSOR_UNSET = 0,
@@ -37,7 +37,9 @@ typedef enum _meshtastic_TelemetrySensorType {
/* 3-Axis magnetic sensor */
meshtastic_TelemetrySensorType_QMC5883L = 11,
/* High accuracy temperature and humidity */
- meshtastic_TelemetrySensorType_SHT31 = 12
+ meshtastic_TelemetrySensorType_SHT31 = 12,
+ /* PM2.5 air quality sensor */
+ meshtastic_TelemetrySensorType_PMSA003I = 13
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -69,6 +71,34 @@ typedef struct _meshtastic_EnvironmentMetrics {
float current;
} meshtastic_EnvironmentMetrics;
+/* Air quality metrics */
+typedef struct _meshtastic_AirQualityMetrics {
+ /* Standard PM1.0 */
+ uint32_t pm10_standard;
+ /* Standard PM2.5 */
+ uint32_t pm25_standard;
+ /* Standard PM10.0 */
+ uint32_t pm100_standard;
+ /* Environmental PM1.0 */
+ uint32_t pm10_environmental;
+ /* Environmental PM2.5 */
+ uint32_t pm25_environmental;
+ /* Environmental PM10.0 */
+ uint32_t pm100_environmental;
+ /* 0.3um Particle Count */
+ uint32_t particles_03um;
+ /* 0.5um Particle Count */
+ uint32_t particles_05um;
+ /* 1.0um Particle Count */
+ uint32_t particles_10um;
+ /* 2.5um Particle Count */
+ uint32_t particles_25um;
+ /* 5.0um Particle Count */
+ uint32_t particles_50um;
+ /* 10.0um Particle Count */
+ uint32_t particles_100um;
+} meshtastic_AirQualityMetrics;
+
/* Types of Measurements the telemetry module is equipped to handle */
typedef struct _meshtastic_Telemetry {
/* This is usually not sent over the mesh (to save space), but it is sent
@@ -83,6 +113,8 @@ typedef struct _meshtastic_Telemetry {
meshtastic_DeviceMetrics device_metrics;
/* Weather station or other environmental metrics */
meshtastic_EnvironmentMetrics environment_metrics;
+ /* Air quality metrics */
+ meshtastic_AirQualityMetrics air_quality_metrics;
} variant;
} meshtastic_Telemetry;
@@ -93,8 +125,9 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SHT31
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SHT31+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_PMSA003I
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_PMSA003I+1))
+
@@ -103,9 +136,11 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0}
#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0}
+#define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0}
#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0}
+#define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
/* Field tags (for use in manual encoding/decoding) */
@@ -119,9 +154,22 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_gas_resistance_tag 4
#define meshtastic_EnvironmentMetrics_voltage_tag 5
#define meshtastic_EnvironmentMetrics_current_tag 6
+#define meshtastic_AirQualityMetrics_pm10_standard_tag 1
+#define meshtastic_AirQualityMetrics_pm25_standard_tag 2
+#define meshtastic_AirQualityMetrics_pm100_standard_tag 3
+#define meshtastic_AirQualityMetrics_pm10_environmental_tag 4
+#define meshtastic_AirQualityMetrics_pm25_environmental_tag 5
+#define meshtastic_AirQualityMetrics_pm100_environmental_tag 6
+#define meshtastic_AirQualityMetrics_particles_03um_tag 7
+#define meshtastic_AirQualityMetrics_particles_05um_tag 8
+#define meshtastic_AirQualityMetrics_particles_10um_tag 9
+#define meshtastic_AirQualityMetrics_particles_25um_tag 10
+#define meshtastic_AirQualityMetrics_particles_50um_tag 11
+#define meshtastic_AirQualityMetrics_particles_100um_tag 12
#define meshtastic_Telemetry_time_tag 1
#define meshtastic_Telemetry_device_metrics_tag 2
#define meshtastic_Telemetry_environment_metrics_tag 3
+#define meshtastic_Telemetry_air_quality_metrics_tag 4
/* Struct field encoding specification for nanopb */
#define meshtastic_DeviceMetrics_FIELDLIST(X, a) \
@@ -142,28 +190,49 @@ X(a, STATIC, SINGULAR, FLOAT, current, 6)
#define meshtastic_EnvironmentMetrics_CALLBACK NULL
#define meshtastic_EnvironmentMetrics_DEFAULT NULL
+#define meshtastic_AirQualityMetrics_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, pm10_standard, 1) \
+X(a, STATIC, SINGULAR, UINT32, pm25_standard, 2) \
+X(a, STATIC, SINGULAR, UINT32, pm100_standard, 3) \
+X(a, STATIC, SINGULAR, UINT32, pm10_environmental, 4) \
+X(a, STATIC, SINGULAR, UINT32, pm25_environmental, 5) \
+X(a, STATIC, SINGULAR, UINT32, pm100_environmental, 6) \
+X(a, STATIC, SINGULAR, UINT32, particles_03um, 7) \
+X(a, STATIC, SINGULAR, UINT32, particles_05um, 8) \
+X(a, STATIC, SINGULAR, UINT32, particles_10um, 9) \
+X(a, STATIC, SINGULAR, UINT32, particles_25um, 10) \
+X(a, STATIC, SINGULAR, UINT32, particles_50um, 11) \
+X(a, STATIC, SINGULAR, UINT32, particles_100um, 12)
+#define meshtastic_AirQualityMetrics_CALLBACK NULL
+#define meshtastic_AirQualityMetrics_DEFAULT NULL
+
#define meshtastic_Telemetry_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, FIXED32, time, 1) \
X(a, STATIC, ONEOF, MESSAGE, (variant,device_metrics,variant.device_metrics), 2) \
-X(a, STATIC, ONEOF, MESSAGE, (variant,environment_metrics,variant.environment_metrics), 3)
+X(a, STATIC, ONEOF, MESSAGE, (variant,environment_metrics,variant.environment_metrics), 3) \
+X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_quality_metrics), 4)
#define meshtastic_Telemetry_CALLBACK NULL
#define meshtastic_Telemetry_DEFAULT NULL
#define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics
#define meshtastic_Telemetry_variant_environment_metrics_MSGTYPE meshtastic_EnvironmentMetrics
+#define meshtastic_Telemetry_variant_air_quality_metrics_MSGTYPE meshtastic_AirQualityMetrics
extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg;
extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
+extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg;
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg
#define meshtastic_EnvironmentMetrics_fields &meshtastic_EnvironmentMetrics_msg
+#define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
/* Maximum encoded size of messages (where known) */
+#define meshtastic_AirQualityMetrics_size 72
#define meshtastic_DeviceMetrics_size 21
#define meshtastic_EnvironmentMetrics_size 30
-#define meshtastic_Telemetry_size 37
+#define meshtastic_Telemetry_size 79
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index e8481356b..bc874a9a3 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -14,6 +14,7 @@
#include "modules/TraceRouteModule.h"
#include "modules/WaypointModule.h"
#if HAS_TELEMETRY
+#include "modules/Telemetry/AirQualityTelemetry.h"
#include "modules/Telemetry/DeviceTelemetry.h"
#include "modules/Telemetry/EnvironmentTelemetry.h"
#endif
@@ -63,6 +64,9 @@ void setupModules()
#if HAS_TELEMETRY
new DeviceTelemetryModule();
new EnvironmentTelemetryModule();
+ if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I] > 0) {
+ new AirQualityTelemetryModule();
+ }
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
new SerialModule();
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
new file mode 100644
index 000000000..2b744c489
--- /dev/null
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -0,0 +1,128 @@
+#include "AirQualityTelemetry.h"
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "MeshService.h"
+#include "NodeDB.h"
+#include "PowerFSM.h"
+#include "RTC.h"
+#include "Router.h"
+#include "configuration.h"
+#include "main.h"
+
+int32_t AirQualityTelemetryModule::runOnce()
+{
+#ifndef ARCH_PORTDUINO
+ int32_t result = INT32_MAX;
+ /*
+ Uncomment the preferences below if you want to use the module
+ without having to configure it from the PythonAPI or WebUI.
+ */
+
+ // moduleConfig.telemetry.environment_measurement_enabled = 1;
+
+ if (!(moduleConfig.telemetry.environment_measurement_enabled)) {
+ // If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
+ return disable();
+ }
+
+ if (firstTime) {
+ // This is the first time the OSThread library has called this function, so do some setup
+ firstTime = 0;
+
+ if (moduleConfig.telemetry.environment_measurement_enabled) {
+ LOG_INFO("Air quality Telemetry: Initializing\n");
+ if (!aqi.begin_I2C()) {
+ LOG_WARN("Could not establish i2c connection to AQI sensor\n");
+ return disable();
+ }
+ return 1000;
+ }
+ return result;
+ } else {
+ // if we somehow got to a second run of this module with measurement disabled, then just wait forever
+ if (!moduleConfig.telemetry.environment_measurement_enabled)
+ return result;
+
+ uint32_t now = millis();
+ if (((lastSentToMesh == 0) ||
+ ((now - lastSentToMesh) >= getConfiguredOrDefaultMs(moduleConfig.telemetry.environment_update_interval))) &&
+ airTime->isTxAllowedAirUtil()) {
+ sendTelemetry();
+ lastSentToMesh = now;
+ } else if (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);
+ }
+ }
+ return sendToPhoneIntervalMs;
+#endif
+}
+
+bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
+{
+ if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
+ const char *sender = getSenderShortName(mp);
+
+ LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i\n", sender,
+ t->variant.air_quality_metrics.pm10_standard, t->variant.air_quality_metrics.pm25_standard,
+ t->variant.air_quality_metrics.pm100_standard);
+
+ LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i\n",
+ t->variant.air_quality_metrics.pm10_environmental, t->variant.air_quality_metrics.pm25_environmental,
+ t->variant.air_quality_metrics.pm100_environmental);
+
+ // release previous packet before occupying a new spot
+ if (lastMeasurementPacket != nullptr)
+ packetPool.release(lastMeasurementPacket);
+
+ lastMeasurementPacket = packetPool.allocCopy(mp);
+ }
+
+ return false; // Let others look at this message also if they want
+}
+
+bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
+{
+ if (!aqi.read(&data)) {
+ LOG_WARN("Skipping send measurements. Could not read AQIn\n");
+ return false;
+ }
+
+ meshtastic_Telemetry m;
+ m.time = getTime();
+ m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
+ m.variant.air_quality_metrics.pm10_standard = data.pm10_standard;
+ m.variant.air_quality_metrics.pm25_standard = data.pm25_standard;
+ m.variant.air_quality_metrics.pm100_standard = data.pm100_standard;
+
+ m.variant.air_quality_metrics.pm10_environmental = data.pm10_env;
+ m.variant.air_quality_metrics.pm25_environmental = data.pm25_env;
+ m.variant.air_quality_metrics.pm100_environmental = data.pm100_env;
+
+ LOG_INFO("(Sending): PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i\n",
+ m.variant.air_quality_metrics.pm10_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\n",
+ 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);
+ p->to = dest;
+ p->decoded.want_response = false;
+ p->priority = meshtastic_MeshPacket_Priority_MIN;
+
+ // release previous packet before occupying a new spot
+ if (lastMeasurementPacket != nullptr)
+ packetPool.release(lastMeasurementPacket);
+
+ lastMeasurementPacket = packetPool.allocCopy(*p);
+ if (phoneOnly) {
+ LOG_INFO("Sending packet to phone\n");
+ service.sendToPhone(p);
+ } else {
+ LOG_INFO("Sending packet to mesh\n");
+ service.sendToMesh(p, RX_SRC_LOCAL, true);
+ }
+ return true;
+}
diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h
new file mode 100644
index 000000000..ab77d61e7
--- /dev/null
+++ b/src/modules/Telemetry/AirQualityTelemetry.h
@@ -0,0 +1,37 @@
+#pragma once
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "Adafruit_PM25AQI.h"
+#include "NodeDB.h"
+#include "ProtobufModule.h"
+
+class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule
+{
+ public:
+ AirQualityTelemetryModule()
+ : concurrency::OSThread("AirQualityTelemetryModule"),
+ ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
+ {
+ lastMeasurementPacket = nullptr;
+ setIntervalFromNow(10 * 1000);
+ aqi = Adafruit_PM25AQI();
+ }
+
+ protected:
+ /** Called to handle a particular incoming message
+ @return true if you've guaranteed you've handled this message and no other handlers should be considered for it
+ */
+ virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *p) override;
+ virtual int32_t runOnce() override;
+ /**
+ * Send our Telemetry into the mesh
+ */
+ bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
+
+ private:
+ Adafruit_PM25AQI aqi;
+ PM25_AQI_Data data = {0};
+ bool firstTime = 1;
+ meshtastic_MeshPacket *lastMeasurementPacket;
+ uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
+ uint32_t lastSentToMesh = 0;
+};
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index c1693a8c8..d9b129e70 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -118,6 +118,16 @@ int32_t EnvironmentTelemetryModule::runOnce()
#endif
}
+bool EnvironmentTelemetryModule::wantUIFrame()
+{
+ return moduleConfig.telemetry.environment_screen_enabled;
+}
+
+float EnvironmentTelemetryModule::CelsiusToFahrenheit(float c)
+{
+ return (c * 9) / 5 + 32;
+}
+
uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
{
uint32_t now = getTime();
@@ -130,16 +140,6 @@ uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
return delta;
}
-bool EnvironmentTelemetryModule::wantUIFrame()
-{
- return moduleConfig.telemetry.environment_screen_enabled;
-}
-
-float EnvironmentTelemetryModule::CelsiusToFahrenheit(float c)
-{
- return (c * 9) / 5 + 32;
-}
-
void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);