mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-06-26 16:21:33 +02:00
Compare commits
70 Commits
Author | SHA1 | Date | |
---|---|---|---|
831c844c24 | |||
060a7f6815 | |||
d8eb6b3c1a | |||
969858b5cb | |||
09b5805686 | |||
b09b753339 | |||
ddb3dba131 | |||
e780b0ace6 | |||
e82da5401e | |||
50a98acde4 | |||
7049d21a41 | |||
d5cdeaa9f3 | |||
09207c6923 | |||
0a64424196 | |||
5b38ca222b | |||
9ee35341a5 | |||
cec0514444 | |||
626a2240fa | |||
174ec6568f | |||
6b55719399 | |||
e2084f0738 | |||
5e07923690 | |||
04049439b1 | |||
c148d256d7 | |||
02849a1938 | |||
074337a96d | |||
4daa817a0b | |||
81a4502952 | |||
764e2eae38 | |||
79bf9811be | |||
9475724d0c | |||
e7603a7659 | |||
9bba89722e | |||
81945a358e | |||
3d26a54d69 | |||
b70ee75d50 | |||
c6846c818a | |||
0b1c901a76 | |||
83504c8628 | |||
4487992748 | |||
3c8a65a329 | |||
673d564ddb | |||
423eb4808f | |||
18a710ffc2 | |||
040cb79a4d | |||
52d3dc03f1 | |||
1c6bc3ec55 | |||
34d7c93e14 | |||
fee1dc25d6 | |||
9fb01d42f4 | |||
7bb013939c | |||
0da21155e7 | |||
7a153cc0ea | |||
b079c35e6b | |||
6051e183b8 | |||
c95379b957 | |||
0cae8bc185 | |||
5902a4c8e4 | |||
66818cd075 | |||
c1a6ddc68f | |||
20a32dd22c | |||
263dc9934e | |||
61b863b7f1 | |||
e01c1029fe | |||
ba5d817739 | |||
a91747e379 | |||
029457c3fa | |||
55710dd4d9 | |||
4886163cda | |||
7c57477238 |
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -1,6 +1,6 @@
|
||||
[submodule "src/Libraries/airgradient-client"]
|
||||
path = src/Libraries/airgradient-client
|
||||
url = git@github.com:airgradienthq/airgradient-client.git
|
||||
url = ../../airgradienthq/airgradient-client.git
|
||||
[submodule "src/Libraries/airgradient-ota"]
|
||||
path = src/Libraries/airgradient-ota
|
||||
url = git@github.com:airgradienthq/airgradient-ota.git
|
||||
url = ../../airgradienthq/airgradient-ota.git
|
||||
|
@ -93,6 +93,7 @@ Compensated values apply correction algorithms to make the sensor values more ac
|
||||
"tvocLearningOffset": 12,
|
||||
"noxLearningOffset": 12,
|
||||
"mqttBrokerUrl": "",
|
||||
"httpDomain": "",
|
||||
"temperatureUnit": "c",
|
||||
"configurationControl": "local",
|
||||
"postDataToAirGradient": true,
|
||||
@ -146,7 +147,8 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
|
||||
| `displayBrightness` | Brightness of the Display. | Number | 0-100 | `{"displayBrightness": 50}` |
|
||||
| `ledBarBrightness` | Brightness of the LEDBar. | Number | 0-100 | `{"ledBarBrightness": 40}` |
|
||||
| `abcDays` | Number of days for CO2 automatic baseline calibration. | Number | Maximum 200 days. Default 8 days. | `{"abcDays": 8}` |
|
||||
| `mqttBrokerUrl` | MQTT broker URL. | String | | `{"mqttBrokerUrl": "mqtt://192.168.0.18:1883"}` |
|
||||
| `mqttBrokerUrl` | MQTT broker URL. | String | Maximum 255 characters. Set value to empty string to disable mqtt connection. | `{"mqttBrokerUrl": "mqtt://192.168.0.18:1883"}` |
|
||||
| `httpDomain` | Domain name for http request. (version > 3.3.2) | String | Maximum 255 characters. Set value to empty string to set http domain to default airgradient | `{"httpDomain": "sub.domain.com"}` |
|
||||
| `temperatureUnit` | Temperature unit shown on the display. | String | `c` or `C`: Degree Celsius °C <br>`f` or `F`: Degree Fahrenheit °F | `{"temperatureUnit": "c"}` |
|
||||
| `configurationControl` | The configuration source of the device. | String | `both`: Accept local and cloud configuration <br>`local`: Accept only local configuration <br>`cloud`: Accept only cloud configuration | `{"configurationControl": "both"}` |
|
||||
| `postDataToAirGradient` | Send data to AirGradient cloud. | Boolean | `true`: Enabled <br>`false`: Disabled | `{"postDataToAirGradient": true}` |
|
||||
@ -154,8 +156,8 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
|
||||
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | `{"ledBarTestRequested": true}` |
|
||||
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
|
||||
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | `{"tvocLearningOffset": 12}` |
|
||||
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on [3.1.9]()) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
|
||||
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= [3.1.11]()) | Object | _see corrections section_ | _see corrections section_ |
|
||||
| `monitorDisplayCompensatedValues` | Set the display show the PM value with/without compensate value (only on 3.1.9) | Boolean | `false`: Without compensate (default) <br> `true`: with compensate | `{"monitorDisplayCompensatedValues": false }` |
|
||||
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= 3.1.11) | Object | _see corrections section_ | _see corrections section_ |
|
||||
|
||||
|
||||
**Notes**
|
||||
|
@ -26,7 +26,6 @@ https://forum.airgradient.com/
|
||||
CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include "AgConfigure.h"
|
||||
#include "AgSchedule.h"
|
||||
#include "AgStateMachine.h"
|
||||
@ -37,6 +36,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include "Arduino.h"
|
||||
#include "EEPROM.h"
|
||||
#include "ESPmDNS.h"
|
||||
#include "Libraries/airgradient-client/src/common.h"
|
||||
#include "LocalServer.h"
|
||||
#include "MqttClient.h"
|
||||
#include "OpenMetrics.h"
|
||||
@ -45,6 +45,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include <HardwareSerial.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Libraries/airgradient-client/src/agSerial.h"
|
||||
@ -65,7 +66,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#define WIFI_TRANSMISSION_INTERVAL 1 * 60000 /** ms */
|
||||
#define CELLULAR_SERVER_CONFIG_SYNC_INTERVAL 30 * 60000 /** ms */
|
||||
#define CELLULAR_MEASUREMENT_INTERVAL 3 * 60000 /** ms */
|
||||
#define CELLULAR_TRANSMISSION_INTERVAL 9 * 60000 /** ms */
|
||||
#define CELLULAR_TRANSMISSION_INTERVAL 3 * 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
@ -74,7 +75,10 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#define SENSOR_TEMP_HUM_UPDATE_INTERVAL 6000 /** ms */
|
||||
#define DISPLAY_DELAY_SHOW_CONTENT_MS 2000 /** ms */
|
||||
#define FIRMWARE_CHECK_FOR_UPDATE_MS (60 * 60 * 1000) /** ms */
|
||||
#define TIME_TO_START_POWER_CYCLE_CELLULAR_MODULE (1 * 60) /** minutes */
|
||||
#define TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY (2 * 60) /** minutes */
|
||||
|
||||
#define MEASUREMENT_TRANSMIT_CYCLE 3
|
||||
#define MAXIMUM_MEASUREMENT_CYCLE_QUEUE 80
|
||||
#define RESERVED_MEASUREMENT_CYCLE_CAPACITY 10
|
||||
|
||||
@ -88,6 +92,8 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#define GPIO_EXPANSION_CARD_POWER 4
|
||||
#define GPIO_IIC_RESET 3
|
||||
|
||||
#define MINUTES() ((uint32_t)(esp_timer_get_time() / 1000 / 1000 / 60))
|
||||
|
||||
static MqttClient mqttClient(Serial);
|
||||
static TaskHandle_t mqttTask = NULL;
|
||||
static Configuration configuration(Serial);
|
||||
@ -102,7 +108,7 @@ static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static AgSerial *agSerial;
|
||||
static CellularModule *cell;
|
||||
static CellularModule *cellularCard;
|
||||
static AirgradientClient *agClient;
|
||||
|
||||
enum NetworkOption {
|
||||
@ -111,12 +117,17 @@ enum NetworkOption {
|
||||
};
|
||||
NetworkOption networkOption;
|
||||
TaskHandle_t handleNetworkTask = NULL;
|
||||
static bool otaInProgress = false;
|
||||
static bool firmwareUpdateInProgress = false;
|
||||
|
||||
static uint32_t factoryBtnPressTime = 0;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
||||
static bool ledBarButtonTest = false;
|
||||
static String fwNewVersion;
|
||||
static int lastCellSignalQuality = 99; // CSQ
|
||||
|
||||
// Default value is 0, indicate its not started yet
|
||||
// In minutes
|
||||
uint32_t agCeClientProblemDetectedTime = 0;
|
||||
|
||||
SemaphoreHandle_t mutexMeasurementCycleQueue;
|
||||
static std::vector<Measurements::Measures> measurementCycleQueue;
|
||||
@ -132,6 +143,7 @@ static void updatePm(void);
|
||||
static void sendDataToServer(void);
|
||||
static void tempHumUpdate(void);
|
||||
static void co2Update(void);
|
||||
static void printMeasurements();
|
||||
static void mdnsInit(void);
|
||||
static void createMqttTask(void);
|
||||
static void initMqtt(void);
|
||||
@ -145,6 +157,7 @@ static void displayExecuteOta(AirgradientOTA::OtaResult result, String msg, int
|
||||
static int calculateMaxPeriod(int updateInterval);
|
||||
static void setMeasurementMaxPeriod();
|
||||
static void newMeasurementCycle();
|
||||
static void restartIfCeClientIssueOverTwoHours();
|
||||
static void networkSignalCheck();
|
||||
static void networkingTask(void *args);
|
||||
|
||||
@ -160,6 +173,7 @@ AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmwareUpdate);
|
||||
AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck);
|
||||
AgSchedule printMeasurementsSchedule(6000, printMeasurements);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
@ -179,6 +193,7 @@ void setup() {
|
||||
|
||||
/** Initialize local configure */
|
||||
configuration.begin();
|
||||
configuration.setConfigurationUpdatedCallback(configUpdateHandle);
|
||||
|
||||
/** Init I2C */
|
||||
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
|
||||
@ -198,7 +213,7 @@ void setup() {
|
||||
oledDisplay.setAirGradient(ag);
|
||||
stateMachine.setAirGradient(ag);
|
||||
wifiConnector.setAirGradient(ag);
|
||||
openMetrics.setAirGradient(ag, agClient);
|
||||
openMetrics.setAirGradient(ag);
|
||||
localServer.setAirGraident(ag);
|
||||
measurements.setAirGradient(ag);
|
||||
|
||||
@ -206,9 +221,6 @@ void setup() {
|
||||
boardInit();
|
||||
setMeasurementMaxPeriod();
|
||||
|
||||
// Comment below line to disable debug measurement readings
|
||||
measurements.setDebug(true);
|
||||
|
||||
bool connectToNetwork = true;
|
||||
if (ag->isOne()) { // Offline mode only available for indoor monitor
|
||||
/** Show message confirm offline mode, should me perform if LED bar button
|
||||
@ -259,6 +271,7 @@ void setup() {
|
||||
|
||||
Serial.println("Display brightness: " + String(configuration.getDisplayBrightness()));
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
|
||||
@ -297,11 +310,17 @@ void setup() {
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (networkOption == UseCellular) {
|
||||
// Check if cellular client not ready until certain time
|
||||
// Redundant check in both task to make sure its executed
|
||||
restartIfCeClientIssueOverTwoHours();
|
||||
}
|
||||
|
||||
// Schedule to feed external watchdog
|
||||
watchdogFeedSchedule.run();
|
||||
|
||||
if (otaInProgress) {
|
||||
// OTA currently in progress, temporarily disable running sensor schedules
|
||||
if (firmwareUpdateInProgress) {
|
||||
// Firmare update currently in progress, temporarily disable running sensor schedules
|
||||
delay(10000);
|
||||
return;
|
||||
}
|
||||
@ -334,7 +353,7 @@ void loop() {
|
||||
static bool pmsConnected = false;
|
||||
if (pmsConnected != ag->pms5003.connected()) {
|
||||
pmsConnected = ag->pms5003.connected();
|
||||
Serial.printf("PMS sensor %s ", pmsConnected?"connected":"removed");
|
||||
Serial.printf("PMS sensor %s \n", pmsConnected?"connected":"removed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -346,11 +365,17 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Run measurement schedule */
|
||||
printMeasurementsSchedule.run();
|
||||
|
||||
/** factory reset handle */
|
||||
factoryConfigReset();
|
||||
|
||||
/** check that local configuration changed then do some action */
|
||||
configUpdateHandle();
|
||||
if (configuration.isCommandRequested()) {
|
||||
// Each state machine already has an independent request command check
|
||||
stateMachine.executeCo2Calibration();
|
||||
stateMachine.executeLedBarTest();
|
||||
}
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
@ -367,6 +392,10 @@ static void co2Update(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void printMeasurements() {
|
||||
measurements.printCurrentAverage();
|
||||
}
|
||||
|
||||
static void mdnsInit(void) {
|
||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||
Serial.println("Init mDNS failed");
|
||||
@ -537,35 +566,37 @@ static bool sgp41Init(void) {
|
||||
}
|
||||
|
||||
void checkForFirmwareUpdate(void) {
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
Serial.println("Cloud connection is disabled, skip firmware update");
|
||||
return;
|
||||
}
|
||||
|
||||
AirgradientOTA *agOta;
|
||||
if (networkOption == UseWifi) {
|
||||
agOta = new AirgradientOTAWifi;
|
||||
} else {
|
||||
agOta = new AirgradientOTACellular(cell);
|
||||
agOta = new AirgradientOTACellular(cellularCard, agClient->getICCID());
|
||||
}
|
||||
|
||||
// Indicate main task that ota is performing
|
||||
Serial.println("Check for firmware update, disabling main task");
|
||||
otaInProgress = true;
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
// Only for cellular because it can disturb i2c line
|
||||
Serial.println("Disable SGP41 task for cellular OTA");
|
||||
ag->sgp41.end();
|
||||
}
|
||||
// Indicate main task that firmware update is in progress
|
||||
firmwareUpdateInProgress = true;
|
||||
|
||||
agOta->setHandlerCallback(otaHandlerCallback);
|
||||
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION);
|
||||
|
||||
// Only goes to this line if OTA is not success
|
||||
String httpDomain = configuration.getHttpDomain();
|
||||
if (httpDomain != "") {
|
||||
Serial.printf("httpDomain configuration available, start OTA with custom domain\n",
|
||||
httpDomain.c_str());
|
||||
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION, httpDomain.c_str());
|
||||
} else {
|
||||
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION);
|
||||
}
|
||||
|
||||
// Only goes to this line if firmware update is not success
|
||||
// Handled by otaHandlerCallback
|
||||
|
||||
otaInProgress = false;
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
// Re-start SGP41 task
|
||||
if (!sgp41Init()) {
|
||||
Serial.println("Failed re-start SGP41 task");
|
||||
}
|
||||
}
|
||||
// Indicate main task that firmware update finish
|
||||
firmwareUpdateInProgress = false;
|
||||
|
||||
delete agOta;
|
||||
Serial.println();
|
||||
@ -573,14 +604,25 @@ void checkForFirmwareUpdate(void) {
|
||||
|
||||
void otaHandlerCallback(AirgradientOTA::OtaResult result, const char *msg) {
|
||||
switch (result) {
|
||||
case AirgradientOTA::Starting:
|
||||
case AirgradientOTA::Starting: {
|
||||
Serial.println("Firmware update starting...");
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
// Temporary pause SGP41 task while cellular firmware update is in progress
|
||||
ag->sgp41.pause();
|
||||
}
|
||||
displayExecuteOta(result, fwNewVersion, 0);
|
||||
break;
|
||||
}
|
||||
case AirgradientOTA::InProgress:
|
||||
Serial.printf("OTA progress: %s\n", msg);
|
||||
displayExecuteOta(result, "", std::stoi(msg));
|
||||
break;
|
||||
case AirgradientOTA::Failed:
|
||||
displayExecuteOta(result, "", 0);
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
ag->sgp41.resume();
|
||||
}
|
||||
break;
|
||||
case AirgradientOTA::Skipped:
|
||||
case AirgradientOTA::AlreadyUpToDate:
|
||||
displayExecuteOta(result, "", 0);
|
||||
@ -647,7 +689,11 @@ static void displayExecuteOta(AirgradientOTA::OtaResult result, String msg, int
|
||||
}
|
||||
delay(1000);
|
||||
}
|
||||
oledDisplay.setAirGradient(0);
|
||||
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.setAirGradient(0);
|
||||
oledDisplay.setBrightness(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -813,8 +859,6 @@ static void openAirInit(void) {
|
||||
Serial.println("CO2 S8 sensor not found");
|
||||
Serial.println("Can not detect S8 run mode 'PPT'");
|
||||
fwMode = FW_MODE_O_1PPT;
|
||||
|
||||
Serial0.end();
|
||||
delay(200);
|
||||
} else {
|
||||
Serial.println("Found S8 on Serial0");
|
||||
@ -927,8 +971,8 @@ void initializeNetwork() {
|
||||
if (agSerial->open()) {
|
||||
Serial.println("Cellular module found");
|
||||
// Initialize cellular module and use cellular as agClient
|
||||
cell = new CellularModuleA7672XX(agSerial, GPIO_POWER_MODULE_PIN);
|
||||
agClient = new AirgradientCellularClient(cell);
|
||||
cellularCard = new CellularModuleA7672XX(agSerial, GPIO_POWER_MODULE_PIN);
|
||||
agClient = new AirgradientCellularClient(cellularCard);
|
||||
networkOption = UseCellular;
|
||||
} else {
|
||||
Serial.println("Cellular module not available, using wifi");
|
||||
@ -944,6 +988,14 @@ void initializeNetwork() {
|
||||
agSerial->setDebug(true);
|
||||
}
|
||||
|
||||
String httpDomain = configuration.getHttpDomain();
|
||||
if (httpDomain != "") {
|
||||
agClient->setHttpDomain(httpDomain.c_str());
|
||||
Serial.printf("HTTP domain name is set to: %s\n", httpDomain.c_str());
|
||||
oledDisplay.setText("HTTP domain name", "using local", "configuration");
|
||||
delay(2500);
|
||||
}
|
||||
|
||||
if (!agClient->begin(ag->deviceId().c_str())) {
|
||||
oledDisplay.setText("Client", "initialization", "failed");
|
||||
delay(5000);
|
||||
@ -953,6 +1005,9 @@ void initializeNetwork() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// Provide openmetrics to have access to last transmission result
|
||||
openMetrics.setAirgradientClient(agClient);
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
// Disabling it again
|
||||
agSerial->setDebug(false);
|
||||
@ -988,10 +1043,17 @@ void initializeNetwork() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send data for the first time to AG server at boot
|
||||
sendDataToAg();
|
||||
// Send data for the first time to AG server at boot only if postDataToAirgradient is enabled
|
||||
if (configuration.isPostDataToAirGradient()) {
|
||||
sendDataToAg();
|
||||
}
|
||||
}
|
||||
|
||||
// Skip fetch configuration if configuration control is set to "local" only
|
||||
if (configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||
ledBarEnabledUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string config = agClient->httpFetchConfig();
|
||||
configSchedule.update();
|
||||
@ -1023,8 +1085,8 @@ static void configurationUpdateSchedule(void) {
|
||||
}
|
||||
|
||||
std::string config = agClient->httpFetchConfig();
|
||||
if (agClient->isLastFetchConfigSucceed() && configuration.parse(config.c_str(), false)) {
|
||||
configUpdateHandle();
|
||||
if (agClient->isLastFetchConfigSucceed()) {
|
||||
configuration.parse(config.c_str(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1033,14 +1095,22 @@ static void configUpdateHandle() {
|
||||
return;
|
||||
}
|
||||
|
||||
stateMachine.executeCo2Calibration();
|
||||
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||
mqttClient.end();
|
||||
initMqtt();
|
||||
}
|
||||
|
||||
String httpDomain = configuration.getHttpDomain();
|
||||
if (httpDomain != "") {
|
||||
Serial.printf("HTTP domain name set to: %s\n", httpDomain.c_str());
|
||||
agClient->setHttpDomain(httpDomain.c_str());
|
||||
} else {
|
||||
// Its empty, set to default
|
||||
Serial.println("HTTP domain name from configuration empty, set to default");
|
||||
agClient->setHttpDomainDefault();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
@ -1096,11 +1166,6 @@ static void configUpdateHandle() {
|
||||
if (configuration.isDisplayBrightnessChanged()) {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
stateMachine.executeLedBarTest();
|
||||
}
|
||||
else if(ag->isOpenAir()) {
|
||||
stateMachine.executeLedBarTest();
|
||||
}
|
||||
|
||||
// Update display and led bar notification based on updated configuration
|
||||
@ -1324,7 +1389,10 @@ void postUsingWifi() {
|
||||
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void postUsingCellular() {
|
||||
/**
|
||||
* forcePost to force post without checking transmit cycle
|
||||
*/
|
||||
void postUsingCellular(bool forcePost) {
|
||||
// Aquire queue mutex to get queue size
|
||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||
|
||||
@ -1336,6 +1404,14 @@ void postUsingCellular() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check queue size if its ready to transmit
|
||||
// It is ready if size is divisible by 3
|
||||
if (!forcePost && (queueSize % MEASUREMENT_TRANSMIT_CYCLE) > 0) {
|
||||
Serial.printf("Not ready to transmit, queue size are %d\n", queueSize);
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build payload include all measurements from queue
|
||||
std::string payload;
|
||||
payload += std::to_string(CELLULAR_MEASUREMENT_INTERVAL / 1000); // Convert to seconds
|
||||
@ -1357,13 +1433,17 @@ void postUsingCellular() {
|
||||
|
||||
// Post success, remove the data that previously sent from queue
|
||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||
measurementCycleQueue.erase(measurementCycleQueue.begin(),
|
||||
measurementCycleQueue.begin() + queueSize);
|
||||
|
||||
if (measurementCycleQueue.capacity() > RESERVED_MEASUREMENT_CYCLE_CAPACITY) {
|
||||
Serial.println("measurementCycleQueue capacity more than reserved space, resizing..");
|
||||
measurementCycleQueue.resize(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||
std::vector<Measurements::Measures> tmp;
|
||||
tmp.reserve(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||
measurementCycleQueue.swap(tmp);
|
||||
} else {
|
||||
// If not more than the capacity, then just clear all the values
|
||||
measurementCycleQueue.clear();
|
||||
}
|
||||
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
}
|
||||
|
||||
@ -1377,7 +1457,7 @@ void sendDataToServer(void) {
|
||||
if (networkOption == UseWifi) {
|
||||
postUsingWifi();
|
||||
} else if (networkOption == UseCellular) {
|
||||
postUsingCellular();
|
||||
postUsingCellular(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1447,12 +1527,8 @@ void setMeasurementMaxPeriod() {
|
||||
|
||||
int calculateMaxPeriod(int updateInterval) {
|
||||
// 0.8 is 80% reduced interval for max period
|
||||
if (networkOption == UseWifi) {
|
||||
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
} else {
|
||||
// Cellular
|
||||
return (CELLULAR_MEASUREMENT_INTERVAL - (CELLULAR_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
}
|
||||
// NOTE: Both network option use the same measurement interval
|
||||
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
}
|
||||
|
||||
|
||||
@ -1460,44 +1536,75 @@ void networkSignalCheck() {
|
||||
if (networkOption == UseWifi) {
|
||||
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
|
||||
} else if (networkOption == UseCellular) {
|
||||
auto result = cell->retrieveSignal();
|
||||
auto result = cellularCard->retrieveSignal();
|
||||
if (result.status != CellReturnStatus::Ok) {
|
||||
agClient->setClientReady(false);
|
||||
lastCellSignalQuality = 99;
|
||||
return;
|
||||
}
|
||||
|
||||
// Save last signal quality
|
||||
lastCellSignalQuality = result.data;
|
||||
|
||||
if (result.data == 99) {
|
||||
// 99 indicate cellular not attached to network
|
||||
agClient->setClientReady(false);
|
||||
return;
|
||||
}
|
||||
Serial.printf("Cellular signal strength %d\n", result.data);
|
||||
|
||||
Serial.printf("Cellular signal quality %d\n", result.data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If in 2 hours cellular client still not ready, then restart system
|
||||
*/
|
||||
void restartIfCeClientIssueOverTwoHours() {
|
||||
if (agCeClientProblemDetectedTime > 0 &&
|
||||
(MINUTES() - agCeClientProblemDetectedTime) >
|
||||
TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY) {
|
||||
// Give up wait
|
||||
Serial.println("Rebooting because CE client issues for 2 hours detected");
|
||||
int i = 3;
|
||||
while (i != 0) {
|
||||
if (ag->isOne()) {
|
||||
String tmp = "Rebooting in " + String(i);
|
||||
oledDisplay.setText("CE error", "since 2h", tmp.c_str());
|
||||
} else {
|
||||
Serial.println("Rebooting... " + String(i));
|
||||
}
|
||||
i = i - 1;
|
||||
delay(1000);
|
||||
}
|
||||
oledDisplay.setBrightness(0);
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
|
||||
void networkingTask(void *args) {
|
||||
// OTA check on boot
|
||||
#ifdef ESP8266
|
||||
// ota not supported
|
||||
#else
|
||||
// because cellular it takes too long, watchdog triggered
|
||||
checkForFirmwareUpdate();
|
||||
checkForUpdateSchedule.update();
|
||||
// If cloud connection enabled, run first transmission to server at boot
|
||||
if (configuration.isCloudConnectionDisabled() == false) {
|
||||
// OTA check on boot
|
||||
#ifndef ESP8266
|
||||
checkForFirmwareUpdate();
|
||||
checkForUpdateSchedule.update();
|
||||
#endif
|
||||
|
||||
// Because cellular interval is longer, needs to send first measures cycle on
|
||||
// boot to indicate that its online
|
||||
if (networkOption == UseCellular) {
|
||||
Serial.println("Prepare first measures cycle to send on boot for 20s");
|
||||
delay(20000);
|
||||
newMeasurementCycle();
|
||||
sendDataToServer();
|
||||
measurementSchedule.update();
|
||||
// Because cellular interval is longer, needs to send first measures cycle on
|
||||
// boot to indicate that its online
|
||||
if (networkOption == UseCellular) {
|
||||
Serial.println("Prepare first measures cycle to send on boot for 20s");
|
||||
delay(20000);
|
||||
networkSignalCheck();
|
||||
newMeasurementCycle();
|
||||
postUsingCellular(true);
|
||||
measurementSchedule.update();
|
||||
}
|
||||
// Reset scheduler
|
||||
configSchedule.update();
|
||||
transmissionSchedule.update();
|
||||
}
|
||||
|
||||
// Reset scheduler
|
||||
configSchedule.update();
|
||||
transmissionSchedule.update();
|
||||
|
||||
while (1) {
|
||||
// Handle reconnection based on mode
|
||||
if (networkOption == UseWifi) {
|
||||
@ -1509,21 +1616,50 @@ void networkingTask(void *args) {
|
||||
}
|
||||
else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
Serial.println("Cellular client not ready, ensuring connection...");
|
||||
// Start time if value still default
|
||||
if (agCeClientProblemDetectedTime == 0) {
|
||||
agCeClientProblemDetectedTime = MINUTES();
|
||||
}
|
||||
|
||||
// Enable at command debug
|
||||
agSerial->setDebug(true);
|
||||
if (agClient->ensureClientConnection() == false) {
|
||||
Serial.println("Cellular client connection not ready, retry in 5s...");
|
||||
delay(5000);
|
||||
|
||||
// Check if cellular client not ready until certain time
|
||||
// Redundant check in both task to make sure its executed
|
||||
restartIfCeClientIssueOverTwoHours();
|
||||
|
||||
// Power cycling cellular module due to network issues for more than 1 hour
|
||||
bool resetModule = true;
|
||||
if ((MINUTES() - agCeClientProblemDetectedTime) >
|
||||
TIME_TO_START_POWER_CYCLE_CELLULAR_MODULE) {
|
||||
Serial.println("The CE client hasn't recovered in more than 1 hour, "
|
||||
"performing a power cycle");
|
||||
cellularCard->powerOff();
|
||||
delay(2000);
|
||||
cellularCard->powerOn();
|
||||
delay(10000);
|
||||
// no need to reset module when calling ensureClientConnection()
|
||||
resetModule = false;
|
||||
}
|
||||
|
||||
// Attempt to reconnect
|
||||
Serial.println("Cellular client not ready, ensuring connection...");
|
||||
if (agClient->ensureClientConnection(resetModule) == false) {
|
||||
Serial.println("Cellular client connection not ready, retry in 30s...");
|
||||
delay(30000); // before retry, wait for 30s
|
||||
continue;
|
||||
}
|
||||
agSerial->setDebug(false);
|
||||
|
||||
// Client is ready
|
||||
agCeClientProblemDetectedTime = 0; // reset to default
|
||||
agSerial->setDebug(false); // disable at command debug
|
||||
}
|
||||
}
|
||||
|
||||
// If connection to AirGradient server disable don't run config and transmission schedule
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
delay(1000);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Run scheduler
|
||||
@ -1546,7 +1682,10 @@ void newMeasurementCycle() {
|
||||
measurementCycleQueue.erase(measurementCycleQueue.begin());
|
||||
}
|
||||
|
||||
// Get current measures
|
||||
auto mc = measurements.getMeasures();
|
||||
mc.signal = cellularCard->csqToDbm(lastCellSignalQuality); // convert to RSSI
|
||||
|
||||
measurementCycleQueue.push_back(mc);
|
||||
Serial.println("New measurement cycle added to queue");
|
||||
// Release mutex
|
||||
|
@ -6,8 +6,11 @@ OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag, AirgradientClient *client) {
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) {
|
||||
this->ag = ag;
|
||||
}
|
||||
|
||||
void OpenMetrics::setAirgradientClient(AirgradientClient *client) {
|
||||
this->agClient = client;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ public:
|
||||
OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag, AirgradientClient *client);
|
||||
void setAirGradient(AirGradient *ag);
|
||||
void setAirgradientClient(AirgradientClient *client);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.3.2
|
||||
version=3.3.9
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
@ -12,7 +12,7 @@
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D CORE_DEBUG_LEVEL=3 -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
||||
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D AG_LOG_LEVEL=AG_LOG_LEVEL_INFO -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
||||
board_build.partitions = partitions.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
|
@ -46,6 +46,7 @@ JSON_PROP_DEF(abcDays);
|
||||
JSON_PROP_DEF(tvocLearningOffset);
|
||||
JSON_PROP_DEF(noxLearningOffset);
|
||||
JSON_PROP_DEF(mqttBrokerUrl);
|
||||
JSON_PROP_DEF(httpDomain);
|
||||
JSON_PROP_DEF(temperatureUnit);
|
||||
JSON_PROP_DEF(configurationControl);
|
||||
JSON_PROP_DEF(postDataToAirGradient);
|
||||
@ -68,6 +69,7 @@ JSON_PROP_DEF(rhum);
|
||||
#define jprop_tvocLearningOffset_default 12
|
||||
#define jprop_noxLearningOffset_default 12
|
||||
#define jprop_mqttBrokerUrl_default ""
|
||||
#define jprop_httpDomain_default ""
|
||||
#define jprop_temperatureUnit_default "c"
|
||||
#define jprop_configurationControl_default String(CONFIGURATION_CONTROL_NAME[ConfigurationControl::ConfigurationControlBoth])
|
||||
#define jprop_postDataToAirGradient_default true
|
||||
@ -377,6 +379,7 @@ void Configuration::defaultConfig(void) {
|
||||
|
||||
jconfig[jprop_country] = jprop_country_default;
|
||||
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
||||
jconfig[jprop_httpDomain] = jprop_httpDomain_default;
|
||||
jconfig[jprop_configurationControl] = jprop_configurationControl_default;
|
||||
jconfig[jprop_pmStandard] = jprop_pmStandard_default;
|
||||
jconfig[jprop_temperatureUnit] = jprop_temperatureUnit_default;
|
||||
@ -453,6 +456,10 @@ bool Configuration::begin(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Configuration::setConfigurationUpdatedCallback(ConfigurationUpdatedCallback_t callback) {
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parse JSON configura string to local configure
|
||||
*
|
||||
@ -735,7 +742,7 @@ bool Configuration::parse(String data, bool isLocal) {
|
||||
jconfig[jprop_mqttBrokerUrl] = broker;
|
||||
}
|
||||
} else {
|
||||
failedMessage = "\"mqttBrokerUrl\" length should <= 255";
|
||||
failedMessage = "\"mqttBrokerUrl\" length should less than 255 character";
|
||||
jsonInvalid();
|
||||
return false;
|
||||
}
|
||||
@ -754,6 +761,32 @@ bool Configuration::parse(String data, bool isLocal) {
|
||||
}
|
||||
}
|
||||
|
||||
if (isLocal) {
|
||||
if (JSON.typeof_(root[jprop_httpDomain]) == "string") {
|
||||
String httpDomain = root[jprop_httpDomain];
|
||||
String oldHttpDomain = jconfig[jprop_httpDomain];
|
||||
if (httpDomain.length() <= 255) {
|
||||
if (httpDomain != oldHttpDomain) {
|
||||
changed = true;
|
||||
configLogInfo(String(jprop_httpDomain), oldHttpDomain, httpDomain);
|
||||
jconfig[jprop_httpDomain] = httpDomain;
|
||||
}
|
||||
} else {
|
||||
failedMessage = "\"httpDomain\" length should less than 255 character";
|
||||
jsonInvalid();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (jsonTypeInvalid(root[jprop_httpDomain], "string")) {
|
||||
failedMessage =
|
||||
jsonTypeInvalidMessage(String(jprop_httpDomain), "string");
|
||||
jsonInvalid();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.typeof_(root[jprop_temperatureUnit]) == "string") {
|
||||
String unit = root[jprop_temperatureUnit];
|
||||
String oldUnit = jconfig[jprop_temperatureUnit];
|
||||
@ -922,15 +955,18 @@ bool Configuration::parse(String data, bool isLocal) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (ledBarTestRequested || co2CalibrationRequested) {
|
||||
commandRequested = true;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
updated = true;
|
||||
saveConfig();
|
||||
printConfig();
|
||||
} else {
|
||||
if (ledBarTestRequested || co2CalibrationRequested) {
|
||||
updated = true;
|
||||
}
|
||||
_callback();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1036,6 +1072,16 @@ String Configuration::getMqttBrokerUri(void) {
|
||||
return broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get HTTP domain for post measures and get configuration
|
||||
*
|
||||
* @return String http domain, might be empty string
|
||||
*/
|
||||
String Configuration::getHttpDomain(void) {
|
||||
String httpDomain = jconfig[jprop_httpDomain];
|
||||
return httpDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get configuratoin post data to AirGradient cloud
|
||||
*
|
||||
@ -1120,8 +1166,14 @@ bool Configuration::isUpdated(void) {
|
||||
return updated;
|
||||
}
|
||||
|
||||
bool Configuration::isCommandRequested(void) {
|
||||
bool oldState = this->commandRequested;
|
||||
this->commandRequested = false;
|
||||
return oldState;
|
||||
}
|
||||
|
||||
String Configuration::jsonTypeInvalidMessage(String name, String type) {
|
||||
return "'" + name + "' type invalid, it's should '" + type + "'";
|
||||
return "'" + name + "' type is invalid, expecting '" + type + "'";
|
||||
}
|
||||
|
||||
String Configuration::jsonValueInvalidMessage(String name, String value) {
|
||||
@ -1275,6 +1327,18 @@ void Configuration::toConfig(const char *buf) {
|
||||
logInfo("toConfig: mqttBroker changed");
|
||||
}
|
||||
|
||||
/** validate http domain */
|
||||
if (JSON.typeof_(jconfig[jprop_httpDomain]) != "string") {
|
||||
isConfigFieldInvalid = true;
|
||||
} else {
|
||||
isConfigFieldInvalid = false;
|
||||
}
|
||||
if (isConfigFieldInvalid) {
|
||||
changed = true;
|
||||
jconfig[jprop_httpDomain] = jprop_httpDomain_default;
|
||||
logInfo("toConfig: httpDomain changed");
|
||||
}
|
||||
|
||||
/** Validate temperature unit */
|
||||
if (JSON.typeof_(jconfig[jprop_temperatureUnit]) != "string") {
|
||||
isConfigFieldInvalid = true;
|
||||
|
@ -28,6 +28,7 @@ private:
|
||||
bool co2CalibrationRequested;
|
||||
bool ledBarTestRequested;
|
||||
bool updated;
|
||||
bool commandRequested = false;
|
||||
String failedMessage;
|
||||
bool _noxLearnOffsetChanged;
|
||||
bool _tvocLearningOffsetChanged;
|
||||
@ -70,6 +71,9 @@ public:
|
||||
bool hasSensorSGP = true;
|
||||
bool hasSensorSHT = true;
|
||||
|
||||
typedef void (*ConfigurationUpdatedCallback_t)();
|
||||
void setConfigurationUpdatedCallback(ConfigurationUpdatedCallback_t callback);
|
||||
|
||||
bool begin(void);
|
||||
bool parse(String data, bool isLocal);
|
||||
String toString(void);
|
||||
@ -82,6 +86,7 @@ public:
|
||||
String getLedBarModeName(void);
|
||||
bool getDisplayMode(void);
|
||||
String getMqttBrokerUri(void);
|
||||
String getHttpDomain(void);
|
||||
bool isPostDataToAirGradient(void);
|
||||
ConfigurationControl getConfigurationControl(void);
|
||||
bool isCo2CalibrationRequested(void);
|
||||
@ -89,6 +94,7 @@ public:
|
||||
void reset(void);
|
||||
String getModel(void);
|
||||
bool isUpdated(void);
|
||||
bool isCommandRequested(void);
|
||||
String getFailedMesage(void);
|
||||
void setPostToAirGradient(bool enable);
|
||||
bool noxLearnOffsetChanged(void);
|
||||
@ -115,6 +121,8 @@ public:
|
||||
PMCorrection getPMCorrection(void);
|
||||
TempHumCorrection getTempCorrection(void);
|
||||
TempHumCorrection getHumCorrection(void);
|
||||
private:
|
||||
ConfigurationUpdatedCallback_t _callback;
|
||||
};
|
||||
|
||||
#endif /** _AG_CONFIG_H_ */
|
||||
|
163
src/AgValue.cpp
163
src/AgValue.cpp
@ -5,7 +5,7 @@
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
#define json_prop_pmFirmware "firmware"
|
||||
#define json_prop_pmFirmware "firmware"
|
||||
#define json_prop_pm01Ae "pm01"
|
||||
#define json_prop_pm25Ae "pm02"
|
||||
#define json_prop_pm10Ae "pm10"
|
||||
@ -33,7 +33,7 @@ Measurements::Measurements(Configuration &config) : config(config) {
|
||||
#ifndef ESP8266
|
||||
_resetReason = (int)ESP_RST_UNKNOWN;
|
||||
#endif
|
||||
|
||||
|
||||
/* Set invalid value for each measurements as default value when initialized*/
|
||||
_temperature[0].update.avg = utils::getInvalidTemperature();
|
||||
_temperature[1].update.avg = utils::getInvalidTemperature();
|
||||
@ -51,7 +51,7 @@ Measurements::Measurements(Configuration &config) : config(config) {
|
||||
_pm_05_pc[1].update.avg = utils::getInvalidPmValue();
|
||||
_pm_5_pc[0].update.avg = utils::getInvalidPmValue();
|
||||
_pm_5_pc[1].update.avg = utils::getInvalidPmValue();
|
||||
|
||||
|
||||
_pm_01[0].update.avg = utils::getInvalidPmValue();
|
||||
_pm_01_sp[0].update.avg = utils::getInvalidPmValue();
|
||||
_pm_01_pc[0].update.avg = utils::getInvalidPmValue();
|
||||
@ -76,6 +76,86 @@ Measurements::Measurements(Configuration &config) : config(config) {
|
||||
|
||||
void Measurements::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
void Measurements::printCurrentAverage() {
|
||||
Serial.println();
|
||||
if (config.hasSensorS8) {
|
||||
if (utils::isValidCO2(_co2.update.avg)) {
|
||||
Serial.printf("CO2 = %.2f ppm\n", _co2.update.avg);
|
||||
} else {
|
||||
Serial.printf("CO2 = -\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSHT) {
|
||||
if (utils::isValidTemperature(_temperature[0].update.avg)) {
|
||||
Serial.printf("Temperature = %.2f C\n", _temperature[0].update.avg);
|
||||
} else {
|
||||
Serial.printf("Temperature = -\n");
|
||||
}
|
||||
if (utils::isValidHumidity(_humidity[0].update.avg)) {
|
||||
Serial.printf("Relative Humidity = %.2f\n", _humidity[0].update.avg);
|
||||
} else {
|
||||
Serial.printf("Relative Humidity = -\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorSGP) {
|
||||
if (utils::isValidVOC(_tvoc.update.avg)) {
|
||||
Serial.printf("TVOC Index = %.1f\n", _tvoc.update.avg);
|
||||
} else {
|
||||
Serial.printf("TVOC Index = -\n");
|
||||
}
|
||||
if (utils::isValidVOC(_tvoc_raw.update.avg)) {
|
||||
Serial.printf("TVOC Raw = %.1f\n", _tvoc_raw.update.avg);
|
||||
} else {
|
||||
Serial.printf("TVOC Raw = -\n");
|
||||
}
|
||||
if (utils::isValidNOx(_nox.update.avg)) {
|
||||
Serial.printf("NOx Index = %.1f\n", _nox.update.avg);
|
||||
} else {
|
||||
Serial.printf("NOx Index = -\n");
|
||||
}
|
||||
if (utils::isValidNOx(_nox_raw.update.avg)) {
|
||||
Serial.printf("NOx Raw = %.1f\n", _nox_raw.update.avg);
|
||||
} else {
|
||||
Serial.printf("NOx Raw = -\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.hasSensorPMS1) {
|
||||
printCurrentPMAverage(1);
|
||||
if (!config.hasSensorSHT) {
|
||||
if (utils::isValidTemperature(_temperature[0].update.avg)) {
|
||||
Serial.printf("[1] Temperature = %.2f C\n", _temperature[0].update.avg);
|
||||
} else {
|
||||
Serial.printf("[1] Temperature = -\n");
|
||||
}
|
||||
if (utils::isValidHumidity(_humidity[0].update.avg)) {
|
||||
Serial.printf("[1] Relative Humidity = %.2f\n", _humidity[0].update.avg);
|
||||
} else {
|
||||
Serial.printf("[1] Relative Humidity = -\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (config.hasSensorPMS2) {
|
||||
printCurrentPMAverage(2);
|
||||
if (!config.hasSensorSHT) {
|
||||
if (utils::isValidTemperature(_temperature[1].update.avg)) {
|
||||
Serial.printf("[2] Temperature = %.2f C\n", _temperature[1].update.avg);
|
||||
} else {
|
||||
Serial.printf("[2] Temperature = -\n");
|
||||
}
|
||||
if (utils::isValidHumidity(_humidity[1].update.avg)) {
|
||||
Serial.printf("[2] Relative Humidity = %.2f\n", _humidity[1].update.avg);
|
||||
} else {
|
||||
Serial.printf("[2] Relative Humidity = -\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void Measurements::maxPeriod(MeasurementType type, int max) {
|
||||
switch (type) {
|
||||
case Temperature:
|
||||
@ -570,6 +650,77 @@ String Measurements::measurementTypeStr(MeasurementType type) {
|
||||
return str;
|
||||
}
|
||||
|
||||
void Measurements::printCurrentPMAverage(int ch) {
|
||||
int idx = ch - 1;
|
||||
|
||||
if (utils::isValidPm(_pm_01[idx].update.avg)) {
|
||||
Serial.printf("[%d] Atmospheric PM 1.0 = %.2f ug/m3\n", ch, _pm_01[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Atmospheric PM 1.0 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm(_pm_25[idx].update.avg)) {
|
||||
Serial.printf("[%d] Atmospheric PM 2.5 = %.2f ug/m3\n", ch, _pm_25[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Atmospheric PM 2.5 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm(_pm_10[idx].update.avg)) {
|
||||
Serial.printf("[%d] Atmospheric PM 10 = %.2f ug/m3\n", ch, _pm_10[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Atmospheric PM 10 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm(_pm_01_sp[idx].update.avg)) {
|
||||
Serial.printf("[%d] Standard Particle PM 1.0 = %.2f ug/m3\n", ch, _pm_01_sp[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Standard Particle PM 1.0 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm(_pm_25_sp[idx].update.avg)) {
|
||||
Serial.printf("[%d] Standard Particle PM 2.5 = %.2f ug/m3\n", ch, _pm_25_sp[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Standard Particle PM 2.5 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm(_pm_10_sp[idx].update.avg)) {
|
||||
Serial.printf("[%d] Standard Particle PM 10 = %.2f ug/m3\n", ch, _pm_10_sp[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Standard Particle PM 10 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm03Count(_pm_03_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 0.3 = %.1f\n", ch, _pm_03_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 0.3 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm03Count(_pm_05_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 0.5 = %.1f\n", ch, _pm_05_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 0.5 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm03Count(_pm_01_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 1.0 = %.1f\n", ch, _pm_01_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 1.0 = -\n", ch);
|
||||
}
|
||||
if (utils::isValidPm03Count(_pm_25_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 2.5 = %.1f\n", ch, _pm_25_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 2.5 = -\n", ch);
|
||||
}
|
||||
|
||||
if (_pm_5_pc[idx].listValues.empty() == false) {
|
||||
if (utils::isValidPm03Count(_pm_5_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 5.0 = %.1f\n", ch, _pm_5_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 5.0 = -\n", ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (_pm_10_pc[idx].listValues.empty() == false) {
|
||||
if (utils::isValidPm03Count(_pm_10_pc[idx].update.avg)) {
|
||||
Serial.printf("[%d] Particle Count 10 = %.1f\n", ch, _pm_10_pc[idx].update.avg);
|
||||
} else {
|
||||
Serial.printf("[%d] Particle Count 10 = -\n", ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Measurements::validateChannel(int ch) {
|
||||
if (ch != 1 && ch != 2) {
|
||||
Serial.printf("ERROR! Channel %d is undefined. Only channel 1 or 2 is the optional value!", ch);
|
||||
@ -827,6 +978,12 @@ std::string Measurements::buildMeasuresPayload(Measures &mc) {
|
||||
oss << std::round(mc.pm_03_pc[1]);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
if (mc.signal < 0) {
|
||||
oss << mc.signal;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,8 @@ public:
|
||||
PM10_PC, // Particle 10 count
|
||||
};
|
||||
|
||||
void printCurrentAverage();
|
||||
|
||||
/**
|
||||
* @brief Set each MeasurementType maximum period length for moving average
|
||||
*
|
||||
@ -258,6 +260,8 @@ private:
|
||||
*/
|
||||
void validateChannel(int ch);
|
||||
|
||||
void printCurrentPMAverage(int ch);
|
||||
|
||||
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode);
|
||||
JSONVar buildIndoor(bool localServer);
|
||||
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate);
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "Main/utils.h"
|
||||
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "3.3.2-snap"
|
||||
#define GIT_VERSION "3.3.9-snap"
|
||||
#endif
|
||||
|
||||
|
||||
|
Submodule src/Libraries/airgradient-client updated: 938a92830d...e32a6a4cde
Submodule src/Libraries/airgradient-ota updated: 24d2dc537c...7b103e9073
@ -131,6 +131,22 @@ void Sgp41::handle(void) {
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void Sgp41::pause() {
|
||||
onPause = true;
|
||||
Serial.println("Pausing SGP41 handler task");
|
||||
// Set latest value to invalid
|
||||
tvocRaw = utils::getInvalidVOC();
|
||||
tvoc = utils::getInvalidVOC();
|
||||
noxRaw = utils::getInvalidNOx();
|
||||
nox = utils::getInvalidNOx();
|
||||
}
|
||||
|
||||
void Sgp41::resume() {
|
||||
onPause = false;
|
||||
Serial.println("Resuming SGP41 handler task");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle the sensor conditioning and run time udpate value, This method
|
||||
* must not call, it's called on private task
|
||||
@ -152,6 +168,11 @@ void Sgp41::_handle(void) {
|
||||
uint16_t srawVoc, srawNox;
|
||||
for (;;) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
if (onPause) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getRawSignal(srawVoc, srawNox)) {
|
||||
tvocRaw = srawVoc;
|
||||
noxRaw = srawNox;
|
||||
|
@ -18,6 +18,10 @@ public:
|
||||
bool begin(TwoWire &wire, Stream &stream);
|
||||
void handle(void);
|
||||
#else
|
||||
/* pause _handle task to read sensor */
|
||||
void pause();
|
||||
/* resume _handle task to read sensor */
|
||||
void resume();
|
||||
void _handle(void);
|
||||
#endif
|
||||
void end(void);
|
||||
@ -32,6 +36,7 @@ public:
|
||||
int getTvocLearningOffset(void);
|
||||
|
||||
private:
|
||||
bool onPause = false;
|
||||
bool onConditioning = true;
|
||||
bool ready = false;
|
||||
bool _isBegin = false;
|
||||
|
Reference in New Issue
Block a user