mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-09 21:42:17 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a3afc756a | ||
|
|
bcc24705a5 | ||
|
|
8137200cb8 | ||
|
|
f86291546d | ||
|
|
640dd5f7d7 | ||
|
|
0adcadb1ce | ||
|
|
21d5a34fec | ||
|
|
5b03b1fe52 | ||
|
|
4954723f97 | ||
|
|
e047397642 | ||
|
|
6b73ac3850 | ||
|
|
6df0449408 | ||
|
|
df0ee74b30 | ||
|
|
139e45dff8 | ||
|
|
0df8719fc8 | ||
|
|
28c4acf5c4 | ||
|
|
579a0af516 | ||
|
|
bb3d6d5326 | ||
|
|
9d222c9a55 | ||
|
|
2dd4ca8c52 | ||
|
|
2a50ae05dd |
+3
-3
@@ -8,10 +8,10 @@ plugins:
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.500
|
||||
- renovate@43.4.0
|
||||
- checkov@3.2.501
|
||||
- renovate@43.8.5
|
||||
- prettier@3.8.1
|
||||
- trufflehog@3.93.0
|
||||
- trufflehog@3.93.3
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.3
|
||||
- trivy@0.69.1
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.20" date="2026-02-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20</url>
|
||||
</release>
|
||||
<release version="2.7.19" date="2026-01-22">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
|
||||
</release>
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Minesemi ME25LS01",
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "MeshLink",
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Minimesh Lite",
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "MINEWSEMI_MS24SF1_SX1262",
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "BQ nRF52840",
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ProMicro compatible nRF52840",
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed T1000-E",
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino", "freertos"],
|
||||
"name": "Seeed WIO WM1110",
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed WIO WM1110",
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed WIO WM1110",
|
||||
|
||||
Vendored
+6
@@ -1,3 +1,9 @@
|
||||
meshtasticd (2.7.20.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.20
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 11 Feb 2026 12:19:54 +0000
|
||||
|
||||
meshtasticd (2.7.19.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.19
|
||||
|
||||
+3
-3
@@ -144,7 +144,7 @@ lib_deps =
|
||||
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
|
||||
adafruit/Adafruit INA219@1.2.3
|
||||
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
|
||||
adafruit/Adafruit MPU6050@2.2.8
|
||||
adafruit/Adafruit MPU6050@2.2.9
|
||||
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
|
||||
adafruit/Adafruit LIS3DH@1.3.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
|
||||
@@ -210,7 +210,7 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||
sensirion/Sensirion Core@0.7.2
|
||||
sensirion/Sensirion Core@0.7.3
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
|
||||
@@ -238,6 +238,6 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
|
||||
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
|
||||
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
|
||||
sensirion/Sensirion Core@0.7.2
|
||||
sensirion/Sensirion Core@0.7.3
|
||||
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
|
||||
sensirion/Sensirion I2C SCD4x@1.1.0
|
||||
|
||||
+1
-1
Submodule protobufs updated: e80cb2e410...e1a6b3a868
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
|
||||
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X};
|
||||
return firstOfOrNONE(2, types);
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -374,9 +374,10 @@ void setup()
|
||||
#endif
|
||||
|
||||
concurrency::hasBeenSetup = true;
|
||||
|
||||
#if HAS_SCREEN
|
||||
meshtastic_Config_DisplayConfig_OledType screen_model =
|
||||
meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
||||
#endif
|
||||
OLEDDISPLAY_GEOMETRY screen_geometry = GEOMETRY_128_64;
|
||||
|
||||
#ifdef USE_SEGGER
|
||||
@@ -601,6 +602,7 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_SCREEN
|
||||
auto screenInfo = i2cScanner->firstScreen();
|
||||
screen_found = screenInfo.type != ScanI2C::DeviceType::NONE ? screenInfo.address : ScanI2C::ADDRESS_NONE;
|
||||
|
||||
@@ -618,6 +620,7 @@ void setup()
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType::meshtastic_Config_DisplayConfig_OledType_OLED_AUTO;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#define UPDATE_FROM_SCANNER(FIND_FN)
|
||||
#if defined(USE_VIRTUAL_KEYBOARD)
|
||||
@@ -763,18 +766,23 @@ void setup()
|
||||
else
|
||||
playStartMelody();
|
||||
|
||||
#if HAS_SCREEN
|
||||
// fixed screen override?
|
||||
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
|
||||
screen_model = config.display.oled;
|
||||
|
||||
#if defined(USE_SH1107)
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
|
||||
screen_geometry = GEOMETRY_128_128;
|
||||
#endif
|
||||
|
||||
#if defined(USE_SH1107_128_64)
|
||||
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
|
||||
screen_geometry = GEOMETRY_128_64;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// if we have one of the fixed overrides in the settings, adjust display type accordingly.
|
||||
if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107) {
|
||||
screen_geometry = GEOMETRY_128_64;
|
||||
} else if (screen_model == meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128) {
|
||||
screen_geometry = GEOMETRY_128_128;
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#if !defined(ARCH_STM32WL)
|
||||
|
||||
@@ -943,9 +943,9 @@ void RadioInterface::limitPower(int8_t loraMaxPower)
|
||||
}
|
||||
} else if (!devicestate.owner.is_licensed) {
|
||||
// we have an array of PA gain values. Find the highest power setting that works.
|
||||
for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) {
|
||||
for (int radio_dbm = 0; radio_dbm < (int)num_pa_points; radio_dbm++) {
|
||||
if (((radio_dbm + tx_gain[radio_dbm]) > power) ||
|
||||
((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
((radio_dbm == (int)(num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) {
|
||||
// we've exceeded the power limit, or hit the max we can do
|
||||
LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]);
|
||||
power -= tx_gain[radio_dbm];
|
||||
|
||||
@@ -79,7 +79,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12,
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13
|
||||
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13,
|
||||
/* Traffic management module config */
|
||||
meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14
|
||||
} meshtastic_AdminMessage_ModuleConfigType;
|
||||
|
||||
typedef enum _meshtastic_AdminMessage_BackupLocation {
|
||||
@@ -369,8 +371,8 @@ extern "C" {
|
||||
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1))
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD
|
||||
|
||||
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||
#define meshtastic_BackupPreferences_size 2364
|
||||
#define meshtastic_BackupPreferences_size 2419
|
||||
#define meshtastic_ChannelFile_size 718
|
||||
#define meshtastic_DeviceState_size 1737
|
||||
#define meshtastic_NodeInfoLite_size 196
|
||||
|
||||
@@ -90,6 +90,9 @@ typedef struct _meshtastic_LocalModuleConfig {
|
||||
/* StatusMessage Config */
|
||||
bool has_statusmessage;
|
||||
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
|
||||
/* The part of the config that is specific to the Traffic Management module */
|
||||
bool has_traffic_management;
|
||||
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
|
||||
} meshtastic_LocalModuleConfig;
|
||||
|
||||
|
||||
@@ -99,9 +102,9 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default}
|
||||
#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_LocalConfig_device_tag 1
|
||||
@@ -128,6 +131,7 @@ extern "C" {
|
||||
#define meshtastic_LocalModuleConfig_detection_sensor_tag 13
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_tag 14
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_tag 15
|
||||
#define meshtastic_LocalModuleConfig_traffic_management_tag 16
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_LocalConfig_FIELDLIST(X, a) \
|
||||
@@ -166,7 +170,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15)
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16)
|
||||
#define meshtastic_LocalModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_LocalModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -183,6 +188,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15)
|
||||
#define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
#define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_LocalConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
@@ -194,7 +200,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
|
||||
#define meshtastic_LocalConfig_size 751
|
||||
#define meshtastic_LocalModuleConfig_size 758
|
||||
#define meshtastic_LocalModuleConfig_size 813
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -30,6 +30,9 @@ PB_BIND(meshtastic_ModuleConfig_AudioConfig, meshtastic_ModuleConfig_AudioConfig
|
||||
PB_BIND(meshtastic_ModuleConfig_PaxcounterConfig, meshtastic_ModuleConfig_PaxcounterConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_TrafficManagementConfig, meshtastic_ModuleConfig_TrafficManagementConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_SerialConfig, meshtastic_ModuleConfig_SerialConfig, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -228,6 +228,39 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig {
|
||||
int32_t ble_threshold;
|
||||
} meshtastic_ModuleConfig_PaxcounterConfig;
|
||||
|
||||
/* Config for the Traffic Management module.
|
||||
Provides packet inspection and traffic shaping to help reduce channel utilization */
|
||||
typedef struct _meshtastic_ModuleConfig_TrafficManagementConfig {
|
||||
/* Master enable for traffic management module */
|
||||
bool enabled;
|
||||
/* Enable position deduplication to drop redundant position broadcasts */
|
||||
bool position_dedup_enabled;
|
||||
/* Number of bits of precision for position deduplication (0-32) */
|
||||
uint32_t position_precision_bits;
|
||||
/* Minimum interval in seconds between position updates from the same node */
|
||||
uint32_t position_min_interval_secs;
|
||||
/* Enable direct response to NodeInfo requests from local cache */
|
||||
bool nodeinfo_direct_response;
|
||||
/* Minimum hop distance from requestor before responding to NodeInfo requests */
|
||||
uint32_t nodeinfo_direct_response_max_hops;
|
||||
/* Enable per-node rate limiting to throttle chatty nodes */
|
||||
bool rate_limit_enabled;
|
||||
/* Time window in seconds for rate limiting calculations */
|
||||
uint32_t rate_limit_window_secs;
|
||||
/* Maximum packets allowed per node within the rate limit window */
|
||||
uint32_t rate_limit_max_packets;
|
||||
/* Enable dropping of unknown/undecryptable packets per rate_limit_window_secs */
|
||||
bool drop_unknown_enabled;
|
||||
/* Number of unknown packets before dropping from a node */
|
||||
uint32_t unknown_packet_threshold;
|
||||
/* Set hop_limit to 0 for relayed telemetry broadcasts (own packets unaffected) */
|
||||
bool exhaust_hop_telemetry;
|
||||
/* Set hop_limit to 0 for relayed position broadcasts (own packets unaffected) */
|
||||
bool exhaust_hop_position;
|
||||
/* Preserve hop_limit for router-to-router traffic */
|
||||
bool router_preserve_hops;
|
||||
} meshtastic_ModuleConfig_TrafficManagementConfig;
|
||||
|
||||
/* Serial Config */
|
||||
typedef struct _meshtastic_ModuleConfig_SerialConfig {
|
||||
/* Preferences for the SerialModule */
|
||||
@@ -468,6 +501,8 @@ typedef struct _meshtastic_ModuleConfig {
|
||||
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
|
||||
/* Traffic management module config for mesh network optimization */
|
||||
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
|
||||
} payload_variant;
|
||||
} meshtastic_ModuleConfig;
|
||||
|
||||
@@ -511,6 +546,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_AudioConfig_bitrate_ENUMTYPE meshtastic_ModuleConfig_AudioConfig_Audio_Baud
|
||||
|
||||
|
||||
|
||||
#define meshtastic_ModuleConfig_SerialConfig_baud_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Baud
|
||||
#define meshtastic_ModuleConfig_SerialConfig_mode_ENUMTYPE meshtastic_ModuleConfig_SerialConfig_Serial_Mode
|
||||
|
||||
@@ -536,6 +572,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
|
||||
#define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
|
||||
@@ -553,6 +590,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, _meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_MIN, 0}
|
||||
#define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
|
||||
@@ -600,6 +638,20 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_enabled_tag 1
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_dedup_enabled_tag 2
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_precision_bits_tag 3
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_position_min_interval_secs_tag 4
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_tag 5
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_nodeinfo_direct_response_max_hops_tag 6
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_enabled_tag 7
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_window_secs_tag 8
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_rate_limit_max_packets_tag 9
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_drop_unknown_enabled_tag 10
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_unknown_packet_threshold_tag 11
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_telemetry_tag 12
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_exhaust_hop_position_tag 13
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_router_preserve_hops_tag 14
|
||||
#define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1
|
||||
#define meshtastic_ModuleConfig_SerialConfig_echo_tag 2
|
||||
#define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3
|
||||
@@ -685,6 +737,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_detection_sensor_tag 12
|
||||
#define meshtastic_ModuleConfig_paxcounter_tag 13
|
||||
#define meshtastic_ModuleConfig_statusmessage_tag 14
|
||||
#define meshtastic_ModuleConfig_traffic_management_tag 15
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_ModuleConfig_FIELDLIST(X, a) \
|
||||
@@ -701,7 +754,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_varian
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15)
|
||||
#define meshtastic_ModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -718,6 +772,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_varian
|
||||
#define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
|
||||
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
@@ -788,6 +843,24 @@ X(a, STATIC, SINGULAR, INT32, ble_threshold, 4)
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
X(a, STATIC, SINGULAR, BOOL, position_dedup_enabled, 2) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_precision_bits, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_min_interval_secs, 4) \
|
||||
X(a, STATIC, SINGULAR, BOOL, nodeinfo_direct_response, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, nodeinfo_direct_response_max_hops, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, rate_limit_enabled, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, rate_limit_window_secs, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, rate_limit_max_packets, 9) \
|
||||
X(a, STATIC, SINGULAR, BOOL, drop_unknown_enabled, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, unknown_packet_threshold, 11) \
|
||||
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_telemetry, 12) \
|
||||
X(a, STATIC, SINGULAR, BOOL, exhaust_hop_position, 13) \
|
||||
X(a, STATIC, SINGULAR, BOOL, router_preserve_hops, 14)
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_ModuleConfig_SerialConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
X(a, STATIC, SINGULAR, BOOL, echo, 2) \
|
||||
@@ -900,6 +973,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_NeighborInfoConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_DetectionSensorConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_AudioConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_PaxcounterConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_TrafficManagementConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_SerialConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_ExternalNotificationConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_StoreForwardConfig_msg;
|
||||
@@ -919,6 +993,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_DetectionSensorConfig_fields &meshtastic_ModuleConfig_DetectionSensorConfig_msg
|
||||
#define meshtastic_ModuleConfig_AudioConfig_fields &meshtastic_ModuleConfig_AudioConfig_msg
|
||||
#define meshtastic_ModuleConfig_PaxcounterConfig_fields &meshtastic_ModuleConfig_PaxcounterConfig_msg
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_fields &meshtastic_ModuleConfig_TrafficManagementConfig_msg
|
||||
#define meshtastic_ModuleConfig_SerialConfig_fields &meshtastic_ModuleConfig_SerialConfig_msg
|
||||
#define meshtastic_ModuleConfig_ExternalNotificationConfig_fields &meshtastic_ModuleConfig_ExternalNotificationConfig_msg
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_fields &meshtastic_ModuleConfig_StoreForwardConfig_msg
|
||||
@@ -946,6 +1021,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52
|
||||
#define meshtastic_ModuleConfig_size 227
|
||||
#define meshtastic_RemoteHardwarePin_size 21
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO)
|
||||
PB_BIND(meshtastic_LocalStats, meshtastic_LocalStats, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_TrafficManagementStats, meshtastic_TrafficManagementStats, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_HealthMetrics, meshtastic_HealthMetrics, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -103,7 +103,13 @@ typedef enum _meshtastic_TelemetrySensorType {
|
||||
/* TSL2561 light sensor */
|
||||
meshtastic_TelemetrySensorType_TSL2561 = 44,
|
||||
/* BH1750 light sensor */
|
||||
meshtastic_TelemetrySensorType_BH1750 = 45
|
||||
meshtastic_TelemetrySensorType_BH1750 = 45,
|
||||
/* HDC1080 Temperature and Humidity Sensor */
|
||||
meshtastic_TelemetrySensorType_HDC1080 = 46,
|
||||
/* STH21 Temperature and R. Humidity sensor */
|
||||
meshtastic_TelemetrySensorType_SHT21 = 47,
|
||||
/* Sensirion STC31 CO2 sensor */
|
||||
meshtastic_TelemetrySensorType_STC31 = 48
|
||||
} meshtastic_TelemetrySensorType;
|
||||
|
||||
/* Struct definitions */
|
||||
@@ -365,6 +371,24 @@ typedef struct _meshtastic_LocalStats {
|
||||
int32_t noise_floor;
|
||||
} meshtastic_LocalStats;
|
||||
|
||||
/* Traffic management statistics for mesh network optimization */
|
||||
typedef struct _meshtastic_TrafficManagementStats {
|
||||
/* Total number of packets inspected by traffic management */
|
||||
uint32_t packets_inspected;
|
||||
/* Number of position packets dropped due to deduplication */
|
||||
uint32_t position_dedup_drops;
|
||||
/* Number of NodeInfo requests answered from cache */
|
||||
uint32_t nodeinfo_cache_hits;
|
||||
/* Number of packets dropped due to rate limiting */
|
||||
uint32_t rate_limit_drops;
|
||||
/* Number of unknown/undecryptable packets dropped */
|
||||
uint32_t unknown_packet_drops;
|
||||
/* Number of packets with hop_limit exhausted for local-only broadcast */
|
||||
uint32_t hop_exhausted_packets;
|
||||
/* Number of times router hop preservation was applied */
|
||||
uint32_t router_hops_preserved;
|
||||
} meshtastic_TrafficManagementStats;
|
||||
|
||||
/* Health telemetry metrics */
|
||||
typedef struct _meshtastic_HealthMetrics {
|
||||
/* Heart rate (beats per minute) */
|
||||
@@ -424,6 +448,8 @@ typedef struct _meshtastic_Telemetry {
|
||||
meshtastic_HealthMetrics health_metrics;
|
||||
/* Linux host metrics */
|
||||
meshtastic_HostMetrics host_metrics;
|
||||
/* Traffic management statistics */
|
||||
meshtastic_TrafficManagementStats traffic_management_stats;
|
||||
} variant;
|
||||
} meshtastic_Telemetry;
|
||||
|
||||
@@ -461,8 +487,9 @@ extern "C" {
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_BH1750
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_BH1750+1))
|
||||
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_STC31
|
||||
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_STC31+1))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -481,6 +508,7 @@ extern "C" {
|
||||
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_TrafficManagementStats_init_default {0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
|
||||
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
|
||||
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
|
||||
@@ -491,6 +519,7 @@ extern "C" {
|
||||
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
|
||||
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_TrafficManagementStats_init_zero {0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
|
||||
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
|
||||
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
|
||||
@@ -581,6 +610,13 @@ extern "C" {
|
||||
#define meshtastic_LocalStats_heap_free_bytes_tag 13
|
||||
#define meshtastic_LocalStats_num_tx_dropped_tag 14
|
||||
#define meshtastic_LocalStats_noise_floor_tag 15
|
||||
#define meshtastic_TrafficManagementStats_packets_inspected_tag 1
|
||||
#define meshtastic_TrafficManagementStats_position_dedup_drops_tag 2
|
||||
#define meshtastic_TrafficManagementStats_nodeinfo_cache_hits_tag 3
|
||||
#define meshtastic_TrafficManagementStats_rate_limit_drops_tag 4
|
||||
#define meshtastic_TrafficManagementStats_unknown_packet_drops_tag 5
|
||||
#define meshtastic_TrafficManagementStats_hop_exhausted_packets_tag 6
|
||||
#define meshtastic_TrafficManagementStats_router_hops_preserved_tag 7
|
||||
#define meshtastic_HealthMetrics_heart_bpm_tag 1
|
||||
#define meshtastic_HealthMetrics_spO2_tag 2
|
||||
#define meshtastic_HealthMetrics_temperature_tag 3
|
||||
@@ -601,6 +637,7 @@ extern "C" {
|
||||
#define meshtastic_Telemetry_local_stats_tag 6
|
||||
#define meshtastic_Telemetry_health_metrics_tag 7
|
||||
#define meshtastic_Telemetry_host_metrics_tag 8
|
||||
#define meshtastic_Telemetry_traffic_management_stats_tag 9
|
||||
#define meshtastic_Nau7802Config_zeroOffset_tag 1
|
||||
#define meshtastic_Nau7802Config_calibrationFactor_tag 2
|
||||
#define meshtastic_SEN5XState_last_cleaning_time_tag 1
|
||||
@@ -714,6 +751,17 @@ X(a, STATIC, SINGULAR, INT32, noise_floor, 15)
|
||||
#define meshtastic_LocalStats_CALLBACK NULL
|
||||
#define meshtastic_LocalStats_DEFAULT NULL
|
||||
|
||||
#define meshtastic_TrafficManagementStats_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, packets_inspected, 1) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_dedup_drops, 2) \
|
||||
X(a, STATIC, SINGULAR, UINT32, nodeinfo_cache_hits, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, rate_limit_drops, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, unknown_packet_drops, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, hop_exhausted_packets, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, router_hops_preserved, 7)
|
||||
#define meshtastic_TrafficManagementStats_CALLBACK NULL
|
||||
#define meshtastic_TrafficManagementStats_DEFAULT NULL
|
||||
|
||||
#define meshtastic_HealthMetrics_FIELDLIST(X, a) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, heart_bpm, 1) \
|
||||
X(a, STATIC, OPTIONAL, UINT32, spO2, 2) \
|
||||
@@ -742,7 +790,8 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,air_quality_metrics,variant.air_qual
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics), 5) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,local_stats,variant.local_stats), 6) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,health_metrics,variant.health_metrics), 7) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics), 8) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (variant,traffic_management_stats,variant.traffic_management_stats), 9)
|
||||
#define meshtastic_Telemetry_CALLBACK NULL
|
||||
#define meshtastic_Telemetry_DEFAULT NULL
|
||||
#define meshtastic_Telemetry_variant_device_metrics_MSGTYPE meshtastic_DeviceMetrics
|
||||
@@ -752,6 +801,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,host_metrics,variant.host_metrics),
|
||||
#define meshtastic_Telemetry_variant_local_stats_MSGTYPE meshtastic_LocalStats
|
||||
#define meshtastic_Telemetry_variant_health_metrics_MSGTYPE meshtastic_HealthMetrics
|
||||
#define meshtastic_Telemetry_variant_host_metrics_MSGTYPE meshtastic_HostMetrics
|
||||
#define meshtastic_Telemetry_variant_traffic_management_stats_MSGTYPE meshtastic_TrafficManagementStats
|
||||
|
||||
#define meshtastic_Nau7802Config_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \
|
||||
@@ -774,6 +824,7 @@ extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_PowerMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_LocalStats_msg;
|
||||
extern const pb_msgdesc_t meshtastic_TrafficManagementStats_msg;
|
||||
extern const pb_msgdesc_t meshtastic_HealthMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_HostMetrics_msg;
|
||||
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
|
||||
@@ -786,6 +837,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
|
||||
#define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg
|
||||
#define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg
|
||||
#define meshtastic_LocalStats_fields &meshtastic_LocalStats_msg
|
||||
#define meshtastic_TrafficManagementStats_fields &meshtastic_TrafficManagementStats_msg
|
||||
#define meshtastic_HealthMetrics_fields &meshtastic_HealthMetrics_msg
|
||||
#define meshtastic_HostMetrics_fields &meshtastic_HostMetrics_msg
|
||||
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
|
||||
@@ -804,6 +856,7 @@ extern const pb_msgdesc_t meshtastic_SEN5XState_msg;
|
||||
#define meshtastic_PowerMetrics_size 81
|
||||
#define meshtastic_SEN5XState_size 27
|
||||
#define meshtastic_Telemetry_size 272
|
||||
#define meshtastic_TrafficManagementStats_size 42
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -106,4 +106,15 @@ const std::string vformat(const char *const zcFormat, ...)
|
||||
std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
|
||||
va_end(vaArgs);
|
||||
return std::string(zc.data(), iLen);
|
||||
}
|
||||
|
||||
size_t pb_string_length(const char *str, size_t max_len)
|
||||
{
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; i < max_len; i++) {
|
||||
if (str[i] != '\0') {
|
||||
len = i + 1;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -35,4 +35,7 @@ bool isOneOf(int item, int count, ...);
|
||||
|
||||
const std::string vformat(const char *const zcFormat, ...);
|
||||
|
||||
// Get actual string length for nanopb char array fields.
|
||||
size_t pb_string_length(const char *str, size_t max_len);
|
||||
|
||||
#define IS_ONE_OF(item, ...) isOneOf(item, sizeof((int[]){__VA_ARGS__}) / sizeof(int), __VA_ARGS__)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh/compression/unishox2.h"
|
||||
#include "meshUtils.h"
|
||||
#include "meshtastic/atak.pb.h"
|
||||
|
||||
AtakPluginModule *atakPluginModule;
|
||||
@@ -70,16 +71,17 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
auto compressed = cloneTAKPacketData(t);
|
||||
compressed.is_compressed = true;
|
||||
if (t->has_contact) {
|
||||
auto length = unishox2_compress_lines(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign,
|
||||
sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
auto length = unishox2_compress_lines(
|
||||
t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)),
|
||||
compressed.contact.callsign, sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Compress overflow contact.callsign. Revert to uncompressed packet");
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Compressed callsign: %d bytes", length);
|
||||
length = unishox2_compress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign),
|
||||
compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1,
|
||||
USX_PSET_DFLT, NULL);
|
||||
length = unishox2_compress_lines(
|
||||
t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)),
|
||||
compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Compress overflow contact.device_callsign. Revert to uncompressed packet");
|
||||
return;
|
||||
@@ -87,9 +89,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
LOG_DEBUG("Compressed device_callsign: %d bytes", length);
|
||||
}
|
||||
if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) {
|
||||
auto length = unishox2_compress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message),
|
||||
compressed.payload_variant.chat.message,
|
||||
sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL);
|
||||
auto length = unishox2_compress_lines(
|
||||
t->payload_variant.chat.message,
|
||||
pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)),
|
||||
compressed.payload_variant.chat.message, sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT,
|
||||
NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Compress overflow chat.message. Revert to uncompressed packet");
|
||||
return;
|
||||
@@ -98,9 +102,9 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
|
||||
if (t->payload_variant.chat.has_to) {
|
||||
compressed.payload_variant.chat.has_to = true;
|
||||
length = unishox2_compress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to),
|
||||
compressed.payload_variant.chat.to,
|
||||
sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
|
||||
length = unishox2_compress_lines(
|
||||
t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)),
|
||||
compressed.payload_variant.chat.to, sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Compress overflow chat.to. Revert to uncompressed packet");
|
||||
return;
|
||||
@@ -110,9 +114,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
|
||||
if (t->payload_variant.chat.has_to_callsign) {
|
||||
compressed.payload_variant.chat.has_to_callsign = true;
|
||||
length = unishox2_compress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign),
|
||||
compressed.payload_variant.chat.to_callsign,
|
||||
sizeof(compressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
length = unishox2_compress_lines(
|
||||
t->payload_variant.chat.to_callsign,
|
||||
pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)),
|
||||
compressed.payload_variant.chat.to_callsign, sizeof(compressed.payload_variant.chat.to_callsign) - 1,
|
||||
USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Compress overflow chat.to_callsign. Revert to uncompressed packet");
|
||||
return;
|
||||
@@ -134,18 +140,18 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
auto uncompressed = cloneTAKPacketData(t);
|
||||
uncompressed.is_compressed = false;
|
||||
if (t->has_contact) {
|
||||
auto length =
|
||||
unishox2_decompress_lines(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign,
|
||||
sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
auto length = unishox2_decompress_lines(
|
||||
t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)),
|
||||
uncompressed.contact.callsign, sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Decompress overflow contact.callsign. Bailing out");
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG("Decompressed callsign: %d bytes", length);
|
||||
|
||||
length = unishox2_decompress_lines(t->contact.device_callsign, strlen(t->contact.device_callsign),
|
||||
uncompressed.contact.device_callsign,
|
||||
sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
length = unishox2_decompress_lines(
|
||||
t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)),
|
||||
uncompressed.contact.device_callsign, sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Decompress overflow contact.device_callsign. Bailing out");
|
||||
return;
|
||||
@@ -153,9 +159,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
LOG_DEBUG("Decompressed device_callsign: %d bytes", length);
|
||||
}
|
||||
if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) {
|
||||
auto length = unishox2_decompress_lines(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message),
|
||||
uncompressed.payload_variant.chat.message,
|
||||
sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT, NULL);
|
||||
auto length = unishox2_decompress_lines(
|
||||
t->payload_variant.chat.message,
|
||||
pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)),
|
||||
uncompressed.payload_variant.chat.message, sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT,
|
||||
NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Decompress overflow chat.message. Bailing out");
|
||||
return;
|
||||
@@ -164,9 +172,9 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
|
||||
if (t->payload_variant.chat.has_to) {
|
||||
uncompressed.payload_variant.chat.has_to = true;
|
||||
length = unishox2_decompress_lines(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to),
|
||||
uncompressed.payload_variant.chat.to,
|
||||
sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
|
||||
length = unishox2_decompress_lines(
|
||||
t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)),
|
||||
uncompressed.payload_variant.chat.to, sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Decompress overflow chat.to. Bailing out");
|
||||
return;
|
||||
@@ -176,10 +184,11 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
|
||||
|
||||
if (t->payload_variant.chat.has_to_callsign) {
|
||||
uncompressed.payload_variant.chat.has_to_callsign = true;
|
||||
length =
|
||||
unishox2_decompress_lines(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign),
|
||||
uncompressed.payload_variant.chat.to_callsign,
|
||||
sizeof(uncompressed.payload_variant.chat.to_callsign) - 1, USX_PSET_DFLT, NULL);
|
||||
length = unishox2_decompress_lines(
|
||||
t->payload_variant.chat.to_callsign,
|
||||
pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)),
|
||||
uncompressed.payload_variant.chat.to_callsign, sizeof(uncompressed.payload_variant.chat.to_callsign) - 1,
|
||||
USX_PSET_DFLT, NULL);
|
||||
if (length < 0) {
|
||||
LOG_WARN("Decompress overflow chat.to_callsign. Bailing out");
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "Sensor/AddI2CSensorTemplate.h"
|
||||
#include "Sensor/PMSA003ISensor.h"
|
||||
#include "Sensor/SEN5XSensor.h"
|
||||
#if __has_include(<SensirionI2cScd4x.h>)
|
||||
#include "Sensor/SCD4XSensor.h"
|
||||
#endif
|
||||
|
||||
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
{
|
||||
@@ -44,6 +47,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
// order by priority of metrics/values (low top, high bottom)
|
||||
addSensor<PMSA003ISensor>(i2cScanner, ScanI2C::DeviceType::PMSA003I);
|
||||
addSensor<SEN5XSensor>(i2cScanner, ScanI2C::DeviceType::SEN5X);
|
||||
#if __has_include(<SensirionI2cScd4x.h>)
|
||||
addSensor<SCD4XSensor>(i2cScanner, ScanI2C::DeviceType::SCD4X);
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t AirQualityTelemetryModule::runOnce()
|
||||
@@ -131,9 +137,9 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
LOG_DEBUG("Sending sensors to sleep");
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
if (sensor->isActive() && sensor->canSleep()) {
|
||||
if (sensor->wakeUpTimeMs() < Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes)) {
|
||||
if (sensor->wakeUpTimeMs() <
|
||||
(int32_t)Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes)) {
|
||||
LOG_DEBUG("Disabling %s until next period", sensor->sensorName);
|
||||
sensor->sleep();
|
||||
} else {
|
||||
@@ -186,7 +192,7 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
const auto &m = telemetry.variant.air_quality_metrics;
|
||||
|
||||
// Check if any telemetry field has valid data
|
||||
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard;
|
||||
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_co2;
|
||||
|
||||
if (!hasAny) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
@@ -213,6 +219,8 @@ void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3");
|
||||
if (m.has_pm100_standard)
|
||||
entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
|
||||
if (m.has_co2)
|
||||
entries.push_back("CO2: " + String(m.co2) + "ppm");
|
||||
|
||||
// === Show first available metric on top-right of first line ===
|
||||
if (!entries.empty()) {
|
||||
@@ -256,6 +264,9 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
|
||||
// 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);
|
||||
|
||||
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);
|
||||
#endif
|
||||
// release previous packet before occupying a new spot
|
||||
if (lastMeasurementPacket != nullptr)
|
||||
@@ -269,15 +280,20 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
|
||||
|
||||
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
|
||||
{
|
||||
bool valid = true;
|
||||
// Note: this is different to the case in EnvironmentTelemetryModule
|
||||
// There, if any sensor fails to read - valid = false.
|
||||
bool valid = false;
|
||||
bool hasSensor = false;
|
||||
m->time = getTime();
|
||||
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
|
||||
m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
|
||||
|
||||
bool sensor_get = false;
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
LOG_DEBUG("Reading %s", sensor->sensorName);
|
||||
valid = valid && sensor->getMetrics(m);
|
||||
// Note - this function doesn't get properly called if within a conditional
|
||||
sensor_get = sensor->getMetrics(m);
|
||||
valid = valid || sensor_get;
|
||||
hasSensor = true;
|
||||
}
|
||||
|
||||
@@ -319,12 +335,28 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
|
||||
m.time = getTime();
|
||||
|
||||
if (getAirQualityTelemetry(&m)) {
|
||||
LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", m.variant.air_quality_metrics.pm10_standard,
|
||||
m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm100_standard);
|
||||
if (m.variant.air_quality_metrics.has_pm10_environmental)
|
||||
LOG_INFO("pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u",
|
||||
m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental,
|
||||
m.variant.air_quality_metrics.pm100_environmental);
|
||||
|
||||
bool hasAnyPM =
|
||||
m.variant.air_quality_metrics.has_pm10_standard || m.variant.air_quality_metrics.has_pm25_standard ||
|
||||
m.variant.air_quality_metrics.has_pm100_standard || m.variant.air_quality_metrics.has_pm10_environmental ||
|
||||
m.variant.air_quality_metrics.has_pm25_environmental || m.variant.air_quality_metrics.has_pm100_environmental;
|
||||
|
||||
if (hasAnyPM) {
|
||||
LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u", m.variant.air_quality_metrics.pm10_standard,
|
||||
m.variant.air_quality_metrics.pm25_standard, m.variant.air_quality_metrics.pm100_standard);
|
||||
if (m.variant.air_quality_metrics.has_pm10_environmental)
|
||||
LOG_INFO("pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u",
|
||||
m.variant.air_quality_metrics.pm10_environmental, m.variant.air_quality_metrics.pm25_environmental,
|
||||
m.variant.air_quality_metrics.pm100_environmental);
|
||||
}
|
||||
|
||||
bool hasAnyCO2 = m.variant.air_quality_metrics.has_co2 || m.variant.air_quality_metrics.has_co2_temperature ||
|
||||
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,
|
||||
m.variant.air_quality_metrics.co2_temperature, m.variant.air_quality_metrics.co2_humidity);
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(m);
|
||||
p->to = dest;
|
||||
|
||||
@@ -0,0 +1,893 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cScd4x.h>)
|
||||
|
||||
#include "../detect/reClockI2C.h"
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "SCD4XSensor.h"
|
||||
|
||||
#define SCD4X_NO_ERROR 0
|
||||
|
||||
SCD4XSensor::SCD4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SCD4X, "SCD4X") {}
|
||||
|
||||
bool SCD4XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
{
|
||||
LOG_INFO("Init sensor: %s", sensorName);
|
||||
|
||||
_bus = bus;
|
||||
_address = dev->address.address;
|
||||
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_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 /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
scd4x.begin(*_bus, _address);
|
||||
|
||||
// From SCD4X library
|
||||
delay(30);
|
||||
|
||||
// Stop periodic measurement
|
||||
if (!stopMeasurement()) {
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get sensor variant
|
||||
scd4x.getSensorVariant(sensorVariant);
|
||||
|
||||
if (sensorVariant == SCD4X_SENSOR_VARIANT_SCD41) {
|
||||
LOG_INFO("%s: Found SCD41", sensorName);
|
||||
if (!powerUp()) {
|
||||
LOG_ERROR("%s: Error trying to execute powerUp()", sensorName);
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!getASC(ascActive)) {
|
||||
LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName);
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start measurement in selected power mode (low power by default)
|
||||
if (!startMeasurement()) {
|
||||
LOG_ERROR("%s: Couldn't start measurement", sensorName);
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
|
||||
if (state == SCD4X_MEASUREMENT) {
|
||||
status = 1;
|
||||
} else {
|
||||
status = 0;
|
||||
}
|
||||
|
||||
initI2CSensor();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SCD4XSensor::getMetrics(meshtastic_Telemetry *measurement)
|
||||
{
|
||||
|
||||
if (state != SCD4X_MEASUREMENT) {
|
||||
LOG_ERROR("%s: Not in measurement mode", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t co2, error;
|
||||
float temperature, humidity;
|
||||
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_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 /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
bool dataReady;
|
||||
error = scd4x.getDataReadyStatus(dataReady);
|
||||
if (!dataReady) {
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
LOG_ERROR("SCD4X: Data is not ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.readMeasurement(co2, temperature, humidity);
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
|
||||
LOG_DEBUG("%s readings: %u ppm, %.2f degC, %.2f %rh", sensorName, co2, temperature, humidity);
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_DEBUG("%s: Error while getting measurements: %u", sensorName, error);
|
||||
if (co2 == 0) {
|
||||
LOG_ERROR("%s: Skipping invalid measurement.", sensorName);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
measurement->variant.air_quality_metrics.has_co2_temperature = true;
|
||||
measurement->variant.air_quality_metrics.has_co2_humidity = true;
|
||||
measurement->variant.air_quality_metrics.has_co2 = true;
|
||||
measurement->variant.air_quality_metrics.co2_temperature = temperature;
|
||||
measurement->variant.air_quality_metrics.co2_humidity = humidity;
|
||||
measurement->variant.air_quality_metrics.co2 = co2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Perform a forced recalibration (FRC) of the CO₂ concentration.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library
|
||||
*
|
||||
* 1. Operate the SCD4x in the operation mode later used for normal sensor
|
||||
* operation (e.g. periodic measurement) for at least 3 minutes in an
|
||||
* environment with a homogenous and constant CO2 concentration. The sensor
|
||||
* must be operated at the voltage desired for the application when
|
||||
* performing the FRC sequence. 2. Issue the stop_periodic_measurement
|
||||
* command. 3. Issue the perform_forced_recalibration command.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::performFRC(uint32_t targetCO2)
|
||||
{
|
||||
uint16_t error, frcCorr;
|
||||
|
||||
LOG_INFO("%s: Issuing FRC. Ensure device has been working at least 3 minutes in stable target environment", sensorName);
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("%s: Target CO2: %u ppm", sensorName, targetCO2);
|
||||
error = scd4x.performForcedRecalibration((uint16_t)targetCO2, frcCorr);
|
||||
|
||||
// SCD4X Sensirion datasheet
|
||||
delay(400);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to perform forced recalibration.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frcCorr == 0xFFFF) {
|
||||
LOG_ERROR("%s: Error while performing forced recalibration.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("%s: FRC Correction successful. Correction output: %u", sensorName, (uint16_t)(frcCorr - 0x8000));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start measurement mode
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::startMeasurement()
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
if (state == SCD4X_MEASUREMENT) {
|
||||
LOG_DEBUG("%s: Already in measurement mode", sensorName);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowPower) {
|
||||
error = scd4x.startLowPowerPeriodicMeasurement();
|
||||
} else {
|
||||
error = scd4x.startPeriodicMeasurement();
|
||||
}
|
||||
|
||||
if (error == SCD4X_NO_ERROR) {
|
||||
LOG_INFO("%s: Started measurement mode", sensorName);
|
||||
if (lowPower) {
|
||||
LOG_INFO("%s: Low power mode", sensorName);
|
||||
} else {
|
||||
LOG_INFO("%s: Normal power mode", sensorName);
|
||||
}
|
||||
|
||||
state = SCD4X_MEASUREMENT;
|
||||
return true;
|
||||
} else {
|
||||
LOG_ERROR("%s: Couldn't start measurement mode", sensorName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop measurement mode
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::stopMeasurement()
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
error = scd4x.stopPeriodicMeasurement();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to set idle mode on SCD4X.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
state = SCD4X_IDLE;
|
||||
co2MeasureStarted = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set power mode
|
||||
* Pass true to set low power mode
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setPowerMode(bool _lowPower)
|
||||
{
|
||||
lowPower = _lowPower;
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lowPower) {
|
||||
LOG_DEBUG("%s: Set low power mode", sensorName);
|
||||
} else {
|
||||
LOG_DEBUG("%s: Set normal power mode", sensorName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check the current mode (ASC or FRC)
|
||||
* From Sensirion SCD4X I2C Library
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::getASC(uint16_t &_ascActive)
|
||||
{
|
||||
uint16_t error;
|
||||
LOG_INFO("%s: Getting ASC", sensorName);
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
error = scd4x.getAutomaticSelfCalibrationEnabled(_ascActive);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to send command.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_ascActive) {
|
||||
LOG_INFO("%s: ASC is enabled", sensorName);
|
||||
} else {
|
||||
LOG_INFO("%s: FRC is enabled", sensorName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable or disable automatic self calibration (ASC).
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library
|
||||
*
|
||||
* Sets the current state (enabled / disabled) of the ASC. By default, ASC
|
||||
* is enabled.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setASC(bool ascEnabled)
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
if (ascEnabled) {
|
||||
LOG_INFO("%s: Enabling ASC", sensorName);
|
||||
} else {
|
||||
LOG_INFO("%s: Disabling ASC", sensorName);
|
||||
}
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.setAutomaticSelfCalibrationEnabled((uint16_t)ascEnabled);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to send command.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.persistSettings();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to make settings persistent.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!getASC(ascActive)) {
|
||||
LOG_ERROR("%s: Unable to check if ASC is enabled", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ascActive) {
|
||||
LOG_INFO("%s: ASC is enabled", sensorName);
|
||||
} else {
|
||||
LOG_INFO("%s: ASC is disabled", sensorName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the value of ASC baseline target in ppm.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Sets the value of the ASC baseline target, i.e. the CO₂ concentration in
|
||||
* ppm which the ASC algorithm will assume as lower-bound background to
|
||||
* which the SCD4x is exposed to regularly within one ASC period of
|
||||
* operation. To save the setting to the EEPROM, the persist_settings
|
||||
* command must be issued subsequently. The factory default value is 400
|
||||
* ppm.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setASCBaseline(uint32_t targetCO2)
|
||||
{
|
||||
// TODO - Remove?
|
||||
// Available in library, but not described in datasheet.
|
||||
uint16_t error;
|
||||
LOG_INFO("%s: Setting ASC baseline to: %u", sensorName, targetCO2);
|
||||
|
||||
getASC(ascActive);
|
||||
if (!ascActive) {
|
||||
LOG_ERROR("%s: Can't set ASC baseline. ASC is not active", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.setAutomaticSelfCalibrationTarget((uint16_t)targetCO2);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to send command.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.persistSettings();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to make settings persistent.", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("%s: Setting ASC baseline successful", sensorName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the temperature compensation reference.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Setting the temperature offset of the SCD4x inside the customer device
|
||||
* allows the user to optimize the RH and T output signal.
|
||||
* By default, the temperature offset is set to 4 °C. To save
|
||||
* the setting to the EEPROM, the persist_settings command may be issued.
|
||||
* Equation (1) details how the characteristic temperature offset can be
|
||||
* calculated using the current temperature output of the sensor (TSCD4x), a
|
||||
* reference temperature value (TReference), and the previous temperature
|
||||
* offset (Toffset_pervious) obtained using the get_temperature_offset_raw
|
||||
* command:
|
||||
*
|
||||
* Toffset_actual = TSCD4x - TReference + Toffset_pervious.
|
||||
*
|
||||
* Recommended temperature offset values are between 0 °C and 20 °C. The
|
||||
* temperature offset does not impact the accuracy of the CO2 output.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setTemperature(float tempReference)
|
||||
{
|
||||
uint16_t error;
|
||||
float prevTempOffset;
|
||||
float updatedTempOffset;
|
||||
float tempOffset;
|
||||
bool dataReady;
|
||||
uint16_t co2;
|
||||
float temperature;
|
||||
float humidity;
|
||||
|
||||
LOG_INFO("%s: Setting reference temperature at: %.2f", sensorName, tempReference);
|
||||
|
||||
error = scd4x.getDataReadyStatus(dataReady);
|
||||
if (!dataReady) {
|
||||
LOG_ERROR("%s: Data is not ready", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.readMeasurement(co2, temperature, humidity);
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to read current temperature. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("%s: Current sensor temperature: %.2f", sensorName, temperature);
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.getTemperatureOffset(prevTempOffset);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to get temperature offset. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("%s: Current sensor temperature offset: %.2f", sensorName, prevTempOffset);
|
||||
|
||||
tempOffset = temperature - tempReference + prevTempOffset;
|
||||
|
||||
LOG_INFO("%s: Setting temperature offset: %.2f", sensorName, tempOffset);
|
||||
error = scd4x.setTemperatureOffset(tempOffset);
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to set temperature offset. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.persistSettings();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
scd4x.getTemperatureOffset(updatedTempOffset);
|
||||
LOG_INFO("%s: Updated sensor temperature offset: %.2f", sensorName, updatedTempOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the sensor altitude.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Altitude in meters above sea level can be set after device installation.
|
||||
* Valid value between 0 and 3000m. This overrides pressure offset.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::getAltitude(uint16_t &altitude)
|
||||
{
|
||||
uint16_t error;
|
||||
LOG_INFO("%s: Requesting sensor altitude", sensorName);
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.getSensorAltitude(altitude);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("%s: Sensor altitude: %u", sensorName, altitude);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the ambient pressure around the sensor.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Gets the ambient pressure in Pa.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::getAmbientPressure(uint32_t &ambientPressure)
|
||||
{
|
||||
uint16_t error;
|
||||
LOG_INFO("%s: Requesting sensor ambient pressure", sensorName);
|
||||
|
||||
error = scd4x.getAmbientPressure(ambientPressure);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to get altitude. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
LOG_INFO("%s: Sensor ambient pressure: %u", sensorName, ambientPressure);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the sensor altitude.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Altitude in meters above sea level can be set after device installation.
|
||||
* Valid value between 0 and 3000m. This overrides pressure offset.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setAltitude(uint32_t altitude)
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.setSensorAltitude(altitude);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.persistSettings();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the ambient pressure around the sensor.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* The set_ambient_pressure command can be sent during periodic measurements
|
||||
* to enable continuous pressure compensation. Note that setting an ambient
|
||||
* pressure overrides any pressure compensation based on a previously set
|
||||
* sensor altitude. Use of this command is highly recommended for
|
||||
* applications experiencing significant ambient pressure changes to ensure
|
||||
* sensor accuracy. Valid input values are between 70000 - 120000 Pa. The
|
||||
* default value is 101300 Pa.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::setAmbientPressure(uint32_t ambientPressure)
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
error = scd4x.setAmbientPressure(ambientPressure);
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to set altitude. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sensirion doesn't indicate if this is necessary. We send it anyway
|
||||
error = scd4x.persistSettings();
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to make settings persistent. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Perform factory reset to erase the settings stored in the EEPROM.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* The perform_factory_reset command resets all configuration settings
|
||||
* stored in the EEPROM and erases the FRC and ASC algorithm history.
|
||||
* @note This function should not change the clock
|
||||
*/
|
||||
bool SCD4XSensor::factoryReset()
|
||||
{
|
||||
uint16_t error;
|
||||
|
||||
LOG_INFO("%s: Requesting factory reset", sensorName);
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
error = scd4x.performFactoryReset();
|
||||
|
||||
if (error != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Unable to do factory reset. Error code: %u", sensorName, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("%s: Factory reset successful", sensorName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Put the sensor into sleep mode from idle mode.
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Put the sensor from idle to sleep to reduce power consumption. Can be
|
||||
* used to power down when operating the sensor in power-cycled single shot
|
||||
* mode.
|
||||
* @note This command is only available in idle mode. Only for SCD41.
|
||||
*/
|
||||
bool SCD4XSensor::powerDown()
|
||||
{
|
||||
LOG_INFO("%s: Trying to send sensor to sleep", sensorName);
|
||||
|
||||
if (sensorVariant != SCD4X_SENSOR_VARIANT_SCD41) {
|
||||
LOG_WARN("SCD4X: Can't send sensor to sleep. Incorrect variant. Ignoring");
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_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 /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
if (!stopMeasurement()) {
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scd4x.powerDown() != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Error trying to execute sleep()", sensorName);
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
|
||||
state = SCD4X_OFF;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Wake up sensor from sleep mode to idle mode (powerUp)
|
||||
*
|
||||
* From Sensirion SCD4X I2C Library.
|
||||
*
|
||||
* Wake up the sensor from sleep mode into idle mode. Note that the SCD4x
|
||||
* does not acknowledge the wake_up command. The sensor's idle state after
|
||||
* wake up can be verified by reading out the serial number.
|
||||
* @note This command is only available for SCD41.
|
||||
* @note This function can't change clock (used in init)
|
||||
*/
|
||||
bool SCD4XSensor::powerUp()
|
||||
{
|
||||
LOG_INFO("%s: Waking up", sensorName);
|
||||
|
||||
if (scd4x.wakeUp() != SCD4X_NO_ERROR) {
|
||||
LOG_ERROR("%s: Error trying to execute wakeUp()", sensorName);
|
||||
return false;
|
||||
}
|
||||
|
||||
state = SCD4X_IDLE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if sensor is in measurement mode
|
||||
*/
|
||||
bool SCD4XSensor::isActive()
|
||||
{
|
||||
return state == SCD4X_MEASUREMENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start measurement mode
|
||||
* @note Not used in admin comands, getMetrics or init, can change clock.
|
||||
*/
|
||||
uint32_t SCD4XSensor::wakeUp()
|
||||
{
|
||||
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true);
|
||||
#else
|
||||
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
|
||||
return 0;
|
||||
#endif /* CAN_RECLOCK_I2C */
|
||||
#endif /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
if (startMeasurement()) {
|
||||
co2MeasureStarted = getTime();
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
return SCD4X_WARMUP_MS;
|
||||
}
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stop measurement mode
|
||||
* @note Not used in admin comands, getMetrics or init, can change clock.
|
||||
*/
|
||||
void SCD4XSensor::sleep()
|
||||
{
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_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 /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
stopMeasurement();
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Can sleep function
|
||||
*
|
||||
* Power consumption is very low on lowPower mode, modify this function if
|
||||
* you still want to override this behaviour. Otherwise, sleep is disabled
|
||||
* routinely in low power mode
|
||||
*/
|
||||
bool SCD4XSensor::canSleep()
|
||||
{
|
||||
return lowPower ? false : true;
|
||||
}
|
||||
|
||||
int32_t SCD4XSensor::wakeUpTimeMs()
|
||||
{
|
||||
return SCD4X_WARMUP_MS;
|
||||
}
|
||||
|
||||
int32_t SCD4XSensor::pendingForReadyMs()
|
||||
{
|
||||
uint32_t now;
|
||||
now = getTime();
|
||||
uint32_t sinceCO2MeasureStarted = (now - co2MeasureStarted) * 1000;
|
||||
LOG_DEBUG("%s: Since measure started: %ums", sensorName, sinceCO2MeasureStarted);
|
||||
|
||||
if (sinceCO2MeasureStarted < SCD4X_WARMUP_MS) {
|
||||
LOG_INFO("%s: not enough time passed since starting measurement", sensorName);
|
||||
return SCD4X_WARMUP_MS - sinceCO2MeasureStarted;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
AdminMessageHandleResult SCD4XSensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response)
|
||||
{
|
||||
AdminMessageHandleResult result;
|
||||
|
||||
#ifdef SCD4X_I2C_CLOCK_SPEED
|
||||
#ifdef CAN_RECLOCK_I2C
|
||||
uint32_t currentClock = reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, false);
|
||||
#elif !HAS_SCREEN
|
||||
reClockI2C(SCD4X_I2C_CLOCK_SPEED, _bus, true);
|
||||
#else
|
||||
LOG_WARN("%s can't be used at this clock speed, with a screen", sensorName);
|
||||
return AdminMessageHandleResult::NOT_HANDLED;
|
||||
#endif /* CAN_RECLOCK_I2C */
|
||||
#endif /* SCD4X_I2C_CLOCK_SPEED */
|
||||
|
||||
// TODO: potentially add selftest command?
|
||||
switch (request->which_payload_variant) {
|
||||
case meshtastic_AdminMessage_sensor_config_tag:
|
||||
// Check for ASC-FRC request first
|
||||
if (!request->sensor_config.has_scd4x_config) {
|
||||
result = AdminMessageHandleResult::NOT_HANDLED;
|
||||
break;
|
||||
}
|
||||
|
||||
if (request->sensor_config.scd4x_config.has_factory_reset) {
|
||||
LOG_DEBUG("%s: Requested factory reset", sensorName);
|
||||
this->factoryReset();
|
||||
} else {
|
||||
|
||||
if (request->sensor_config.scd4x_config.has_set_asc) {
|
||||
this->setASC(request->sensor_config.scd4x_config.set_asc);
|
||||
if (request->sensor_config.scd4x_config.set_asc == false) {
|
||||
LOG_DEBUG("%s: Request for FRC", sensorName);
|
||||
if (request->sensor_config.scd4x_config.has_set_target_co2_conc) {
|
||||
this->performFRC(request->sensor_config.scd4x_config.set_target_co2_conc);
|
||||
} else {
|
||||
// FRC requested but no target CO2 provided
|
||||
LOG_ERROR("%s: target CO2 not provided", sensorName);
|
||||
result = AdminMessageHandleResult::NOT_HANDLED;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG("%s: Request for ASC", sensorName);
|
||||
if (request->sensor_config.scd4x_config.has_set_target_co2_conc) {
|
||||
LOG_DEBUG("%s: Request has target CO2", sensorName);
|
||||
// TODO - Remove? see setASCBaseline function
|
||||
this->setASCBaseline(request->sensor_config.scd4x_config.set_target_co2_conc);
|
||||
} else {
|
||||
LOG_DEBUG("%s: Request doesn't have target CO2", sensorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for temperature offset
|
||||
// NOTE: this requires to have a sensor working on stable environment
|
||||
// And to make it between readings
|
||||
if (request->sensor_config.scd4x_config.has_set_temperature) {
|
||||
this->setTemperature(request->sensor_config.scd4x_config.set_temperature);
|
||||
}
|
||||
|
||||
// Check for altitude or pressure offset
|
||||
if (request->sensor_config.scd4x_config.has_set_altitude) {
|
||||
this->setAltitude(request->sensor_config.scd4x_config.set_altitude);
|
||||
} else if (request->sensor_config.scd4x_config.has_set_ambient_pressure) {
|
||||
this->setAmbientPressure(request->sensor_config.scd4x_config.set_ambient_pressure);
|
||||
}
|
||||
|
||||
// Check for low power mode
|
||||
// NOTE: to switch from one mode to another do:
|
||||
// setPowerMode -> startMeasurement
|
||||
if (request->sensor_config.scd4x_config.has_set_power_mode) {
|
||||
this->setPowerMode(request->sensor_config.scd4x_config.set_power_mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Start measurement mode
|
||||
this->startMeasurement();
|
||||
|
||||
result = AdminMessageHandleResult::HANDLED;
|
||||
break;
|
||||
|
||||
default:
|
||||
result = AdminMessageHandleResult::NOT_HANDLED;
|
||||
}
|
||||
|
||||
#if defined(SCD4X_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
|
||||
reClockI2C(currentClock, _bus, false);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cScd4x.h>)
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "RTC.h"
|
||||
#include "TelemetrySensor.h"
|
||||
#include <SensirionI2cScd4x.h>
|
||||
|
||||
// Max speed 400kHz
|
||||
#define SCD4X_I2C_CLOCK_SPEED 100000
|
||||
#define SCD4X_WARMUP_MS 5000
|
||||
|
||||
class SCD4XSensor : public TelemetrySensor
|
||||
{
|
||||
private:
|
||||
SensirionI2cScd4x scd4x;
|
||||
TwoWire *_bus{};
|
||||
uint8_t _address{};
|
||||
|
||||
bool performFRC(uint32_t targetCO2);
|
||||
bool setASCBaseline(uint32_t targetCO2);
|
||||
bool getASC(uint16_t &ascEnabled);
|
||||
bool setASC(bool ascEnabled);
|
||||
bool setTemperature(float tempReference);
|
||||
bool getAltitude(uint16_t &altitude);
|
||||
bool setAltitude(uint32_t altitude);
|
||||
bool getAmbientPressure(uint32_t &ambientPressure);
|
||||
bool setAmbientPressure(uint32_t ambientPressure);
|
||||
bool factoryReset();
|
||||
bool setPowerMode(bool _lowPower);
|
||||
bool startMeasurement();
|
||||
bool stopMeasurement();
|
||||
|
||||
uint16_t ascActive = 1;
|
||||
// low power measurement mode (on sensirion side). Disables sleep mode
|
||||
// Improvement and testing needed for timings
|
||||
bool lowPower = true;
|
||||
uint32_t co2MeasureStarted = 0;
|
||||
|
||||
public:
|
||||
SCD4XSensor();
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
|
||||
|
||||
enum SCD4XState { SCD4X_OFF, SCD4X_IDLE, SCD4X_MEASUREMENT };
|
||||
SCD4XState state = SCD4X_OFF;
|
||||
SCD4xSensorVariant sensorVariant{};
|
||||
|
||||
virtual bool isActive() override;
|
||||
|
||||
virtual void sleep() override; // Stops measurement (measurement -> idle)
|
||||
virtual uint32_t wakeUp() override; // Starts measurement (idle -> measurement)
|
||||
bool powerDown(); // Powers down sensor (idle -> power-off)
|
||||
bool powerUp(); // Powers the sensor (power-off -> idle)
|
||||
virtual bool canSleep() override;
|
||||
virtual int32_t wakeUpTimeMs() override;
|
||||
virtual int32_t pendingForReadyMs() override;
|
||||
AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -572,7 +572,7 @@ bool SEN5XSensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
|
||||
|
||||
// Check if it is time to do a cleaning
|
||||
uint32_t now;
|
||||
int32_t passed;
|
||||
int32_t passed = 0;
|
||||
now = getValidTime(RTCQuality::RTCQualityDevice);
|
||||
|
||||
// If time is not RTCQualityNone, it will return non-zero
|
||||
|
||||
@@ -149,18 +149,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_standard) {
|
||||
msgPayload["pm100"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_standard);
|
||||
}
|
||||
// if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
// msgPayload["pm10_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm10_environmental);
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
// msgPayload["pm25_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm25_environmental);
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
// msgPayload["pm100_e"] =
|
||||
// new JSONValue((unsigned int)decoded->variant.air_quality_metrics.pm100_environmental);
|
||||
// }
|
||||
if (decoded->variant.air_quality_metrics.has_co2) {
|
||||
msgPayload["co2"] = new JSONValue((unsigned int)decoded->variant.air_quality_metrics.co2);
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_co2_temperature) {
|
||||
msgPayload["co2_temperature"] = new JSONValue(decoded->variant.air_quality_metrics.co2_temperature);
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
|
||||
msgPayload["co2_humidity"] = new JSONValue(decoded->variant.air_quality_metrics.co2_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);
|
||||
|
||||
@@ -120,15 +120,15 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
||||
if (decoded->variant.air_quality_metrics.has_pm100_standard) {
|
||||
jsonObj["payload"]["pm100"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_standard;
|
||||
}
|
||||
// if (decoded->variant.air_quality_metrics.has_pm10_environmental) {
|
||||
// jsonObj["payload"]["pm10_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm10_environmental;
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm25_environmental) {
|
||||
// jsonObj["payload"]["pm25_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm25_environmental;
|
||||
// }
|
||||
// if (decoded->variant.air_quality_metrics.has_pm100_environmental) {
|
||||
// jsonObj["payload"]["pm100_e"] = (unsigned int)decoded->variant.air_quality_metrics.pm100_environmental;
|
||||
// }
|
||||
if (decoded->variant.air_quality_metrics.has_co2) {
|
||||
jsonObj["payload"]["co2"] = (unsigned int)decoded->variant.air_quality_metrics.co2;
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_co2_temperature) {
|
||||
jsonObj["payload"]["co2_temperature"] = decoded->variant.air_quality_metrics.co2_temperature;
|
||||
}
|
||||
if (decoded->variant.air_quality_metrics.has_co2_humidity) {
|
||||
jsonObj["payload"]["co2_humidity"] = decoded->variant.air_quality_metrics.co2_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;
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
#include "TestUtil.h"
|
||||
|
||||
#if defined(ARDUINO)
|
||||
#include <Arduino.h>
|
||||
#else
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
void initializeTestEnvironment()
|
||||
{
|
||||
concurrency::hasBeenSetup = true;
|
||||
@@ -15,4 +22,13 @@ void initializeTestEnvironment()
|
||||
perhapsSetRTC(RTCQualityNTP, &tv);
|
||||
#endif
|
||||
concurrency::OSThread::setup();
|
||||
}
|
||||
|
||||
void testDelay(unsigned long ms)
|
||||
{
|
||||
#if defined(ARDUINO)
|
||||
::delay(ms);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
#endif
|
||||
}
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// Initialize testing environment.
|
||||
void initializeTestEnvironment();
|
||||
void initializeTestEnvironment();
|
||||
|
||||
// Portable delay for tests (Arduino or host).
|
||||
void testDelay(unsigned long ms);
|
||||
@@ -0,0 +1,216 @@
|
||||
#include <string.h>
|
||||
#include <unity.h>
|
||||
|
||||
#include "TestUtil.h"
|
||||
#include "meshUtils.h"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
// set stuff up here
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
// clean stuff up here
|
||||
}
|
||||
|
||||
/**
|
||||
* Test normal string without embedded nulls
|
||||
* Should behave the same as strlen() for regular strings
|
||||
*/
|
||||
void test_normal_string(void)
|
||||
{
|
||||
char test_str[32] = "Hello World";
|
||||
size_t expected = 11; // strlen("Hello World")
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(expected, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test empty string
|
||||
* Should return 0 for empty string
|
||||
*/
|
||||
void test_empty_string(void)
|
||||
{
|
||||
char test_str[32] = "";
|
||||
size_t expected = 0;
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(expected, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test string with only trailing nulls
|
||||
* Common case - string followed by null padding
|
||||
*/
|
||||
void test_trailing_nulls(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
strcpy(test_str, "Test");
|
||||
// test_str is now: "Test\0\0\0\0..." (4 chars + 28 nulls)
|
||||
size_t expected = 4;
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(expected, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test string with embedded null byte
|
||||
* This is the critical bug case - strlen() would truncate at first null
|
||||
*/
|
||||
void test_embedded_null(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
// Create string "ABC\0XYZ" (embedded null after C)
|
||||
test_str[0] = 'A';
|
||||
test_str[1] = 'B';
|
||||
test_str[2] = 'C';
|
||||
test_str[3] = '\0'; // embedded null
|
||||
test_str[4] = 'X';
|
||||
test_str[5] = 'Y';
|
||||
test_str[6] = 'Z';
|
||||
// Rest is already null from initialization
|
||||
|
||||
// strlen would return 3, but pb_string_length should return 7
|
||||
size_t strlen_result = strlen(test_str);
|
||||
size_t pb_result = pb_string_length(test_str, sizeof(test_str));
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(3, strlen_result); // strlen stops at first null
|
||||
TEST_ASSERT_EQUAL_size_t(7, pb_result); // pb_string_length finds last non-null
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Android UID with embedded null bytes
|
||||
* Real-world case from bug report: ANDROID-e7e455b40002429d
|
||||
* The "00" in the UID represents 0x00 bytes that were truncating the string
|
||||
*/
|
||||
void test_android_uid_pattern(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
// Simulate "ANDROID-e7e455b4" + 0x00 + 0x00 + "2429d"
|
||||
const char part1[] = "ANDROID-e7e455b4";
|
||||
strcpy(test_str, part1);
|
||||
size_t pos = strlen(part1);
|
||||
test_str[pos] = '\0'; // embedded null
|
||||
test_str[pos + 1] = '\0'; // another embedded null
|
||||
strcpy(test_str + pos + 2, "2429d");
|
||||
|
||||
// The full UID should be 24 characters
|
||||
size_t strlen_result = strlen(test_str);
|
||||
size_t pb_result = pb_string_length(test_str, sizeof(test_str));
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(16, strlen_result); // strlen truncates to "ANDROID-e7e455b4"
|
||||
TEST_ASSERT_EQUAL_size_t(23, pb_result); // pb_string_length gets full length
|
||||
}
|
||||
|
||||
/**
|
||||
* Test string with multiple embedded nulls
|
||||
* Edge case with several null bytes scattered through the string
|
||||
*/
|
||||
void test_multiple_embedded_nulls(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
// Create "A\0B\0C\0D" (3 embedded nulls)
|
||||
test_str[0] = 'A';
|
||||
test_str[1] = '\0';
|
||||
test_str[2] = 'B';
|
||||
test_str[3] = '\0';
|
||||
test_str[4] = 'C';
|
||||
test_str[5] = '\0';
|
||||
test_str[6] = 'D';
|
||||
|
||||
size_t strlen_result = strlen(test_str);
|
||||
size_t pb_result = pb_string_length(test_str, sizeof(test_str));
|
||||
|
||||
TEST_ASSERT_EQUAL_size_t(1, strlen_result); // strlen stops at first null
|
||||
TEST_ASSERT_EQUAL_size_t(7, pb_result); // pb_string_length finds all chars
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buffer completely filled with non-null characters
|
||||
* Edge case where string uses entire buffer
|
||||
*/
|
||||
void test_full_buffer(void)
|
||||
{
|
||||
char test_str[8];
|
||||
// Fill entire buffer with 'X'
|
||||
memset(test_str, 'X', sizeof(test_str));
|
||||
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(8, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test buffer with all nulls
|
||||
* Should return 0
|
||||
*/
|
||||
void test_all_nulls(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(0, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test single character followed by nulls
|
||||
* Minimal non-empty case
|
||||
*/
|
||||
void test_single_char(void)
|
||||
{
|
||||
char test_str[32] = {0};
|
||||
test_str[0] = 'X';
|
||||
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(1, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test callsign field typical size
|
||||
* Test with typical ATAK callsign field size (64 bytes)
|
||||
*/
|
||||
void test_callsign_field_size(void)
|
||||
{
|
||||
char test_str[64] = {0};
|
||||
strcpy(test_str, "CALLSIGN-123");
|
||||
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(12, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with data at end of buffer
|
||||
* String with embedded null and data at very end
|
||||
*/
|
||||
void test_data_at_buffer_end(void)
|
||||
{
|
||||
char test_str[10] = {0};
|
||||
test_str[0] = 'A';
|
||||
test_str[1] = '\0';
|
||||
test_str[8] = 'Z'; // Data near end
|
||||
test_str[9] = 'X'; // Data at end
|
||||
|
||||
size_t result = pb_string_length(test_str, sizeof(test_str));
|
||||
TEST_ASSERT_EQUAL_size_t(10, result); // Should find the 'X' at position 9
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
// NOTE!!! Wait for >2 secs
|
||||
// if board doesn't support software reset via Serial.DTR/RTS
|
||||
testDelay(10);
|
||||
testDelay(2000);
|
||||
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_normal_string);
|
||||
RUN_TEST(test_empty_string);
|
||||
RUN_TEST(test_trailing_nulls);
|
||||
RUN_TEST(test_embedded_null);
|
||||
RUN_TEST(test_android_uid_pattern);
|
||||
RUN_TEST(test_multiple_embedded_nulls);
|
||||
RUN_TEST(test_full_buffer);
|
||||
RUN_TEST(test_all_nulls);
|
||||
RUN_TEST(test_single_char);
|
||||
RUN_TEST(test_callsign_field_size);
|
||||
RUN_TEST(test_data_at_buffer_end);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -16,4 +16,4 @@ upload_speed = 460800
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
|
||||
@@ -19,7 +19,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.3.4
|
||||
lib_ignore =
|
||||
|
||||
@@ -7,4 +7,4 @@ build_flags =
|
||||
-I variants/esp32c3/heltec_hru_3601
|
||||
lib_deps = ${esp32c3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
|
||||
@@ -24,7 +24,7 @@ build_unflags =
|
||||
lib_deps =
|
||||
${esp32c6_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
|
||||
h2zero/NimBLE-Arduino@2.3.7
|
||||
build_flags =
|
||||
|
||||
@@ -11,9 +11,9 @@ upload_speed = 921600
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
build_unflags =
|
||||
${esp32s3_base.build_unflags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
@@ -11,7 +11,7 @@ upload_speed = 921600
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
build_unflags =
|
||||
${esp32s3_base.build_unflags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
@@ -23,6 +23,6 @@ build_flags = ${esp32s3_base.build_flags}
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
|
||||
@@ -11,4 +11,4 @@ build_flags =
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
|
||||
@@ -124,11 +124,8 @@ build_flags =
|
||||
-D SCREEN_TOUCH_RST=TOUCH_RST_PIN
|
||||
|
||||
lib_deps = ${heltec_v4_base.lib_deps}
|
||||
; ${device-ui_base.lib_deps}
|
||||
${device-ui_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
|
||||
lovyan03/LovyanGFX@1.2.19
|
||||
# renovate: datasource=git-refs depName=Quency-D_chsc6x packageName=https://github.com/Quency-D/chsc6x gitBranch=master
|
||||
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
|
||||
; TODO revert to official device-ui (when merged)
|
||||
# renovate: datasource=git-refs depName=Quency-D_device-ui packageName=https://github.com/Quency-D/device-ui gitBranch=heltec-v4-tft
|
||||
https://github.com/Quency-D/device-ui/archive/7c9870b8016641190b059bdd90fe16c1012a39eb.zip
|
||||
https://github.com/Quency-D/chsc6x/archive/5cbead829d6b432a8d621ed1aafd4eb474fd4f27.zip
|
||||
@@ -76,6 +76,8 @@
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#define ADC_MULTIPLIER 2.9333
|
||||
|
||||
#define OCV_ARRAY 7950, 7850, 7750, 7580, 7440, 7310, 7150, 7005, 6860, 6685, 6000
|
||||
|
||||
// NTC temperature sensor
|
||||
#define NTC_PIN 14
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
|
||||
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
|
||||
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
|
||||
|
||||
@@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps}
|
||||
# TODO renovate
|
||||
https://gitlab.com/hamishcunningham/unphonelibrary#meshtastic@9.0.0
|
||||
# renovate: datasource=custom.pio depName=NeoPixel packageName=adafruit/library/Adafruit NeoPixel
|
||||
adafruit/Adafruit NeoPixel@1.15.3
|
||||
adafruit/Adafruit NeoPixel@1.15.4
|
||||
|
||||
[env:unphone-tft]
|
||||
board_level = extra
|
||||
|
||||
@@ -28,7 +28,7 @@ lib_deps =
|
||||
rweather/Crypto@0.4.0
|
||||
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
|
||||
lovyan03/LovyanGFX@1.2.19
|
||||
# renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
|
||||
; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
|
||||
https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip
|
||||
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
|
||||
adafruit/Adafruit seesaw Library@1.7.9
|
||||
|
||||
@@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
debug_tool = jlink
|
||||
|
||||
@@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
||||
upload_protocol = nrfutil
|
||||
;upload_port = /dev/ttyACM1
|
||||
|
||||
@@ -15,6 +15,6 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
debug_tool = jlink
|
||||
;upload_port = /dev/ttyACM4
|
||||
@@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
debug_tool = jlink
|
||||
|
||||
@@ -15,7 +15,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
||||
melopero/Melopero RV3028@1.2.0
|
||||
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
|
||||
|
||||
@@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.6
|
||||
zinggjm/GxEPD2@1.6.7
|
||||
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
||||
melopero/Melopero RV3028@1.2.0
|
||||
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 2
|
||||
minor = 7
|
||||
build = 19
|
||||
build = 20
|
||||
|
||||
Reference in New Issue
Block a user