Compare commits

..

23 Commits
3.3.1 ... 3.3.3

Author SHA1 Message Date
66818cd075 prepare 3.3.3 release 2025-04-04 11:31:19 +07:00
c1a6ddc68f Merge pull request #299 from airgradienthq/tmp/avg-max-period
Calculate measurement average max period use the same constant
2025-04-04 11:04:15 +07:00
20a32dd22c Measures average max period use the same constant
Cellular network options using wifi measurement interval as the constant reference to calculate max period
2025-04-04 10:54:03 +07:00
263dc9934e Merge pull request #298 from airgradienthq/fix/recover-cellular-connection
Restarting cellular module when cellular client is not ready
2025-04-04 10:43:59 +07:00
61b863b7f1 Fix esp_log logs not come out on O-PP 2025-04-04 10:21:50 +07:00
e01c1029fe Bump ag client
ensure client connection properly
2025-04-04 10:09:34 +07:00
ba5d817739 Merge pull request #297 from airgradienthq/feat/api-root
New local configuration to set HTTP domain name for monitor to post measures and fetch configuration from server
2025-04-03 16:51:04 +07:00
a91747e379 Update config sample 2025-04-02 16:30:13 +07:00
029457c3fa Add accepted value to http domain 2025-04-02 16:26:07 +07:00
55710dd4d9 Update docs for new configuration http domain 2025-04-02 16:18:55 +07:00
4886163cda Show on oled when httpDomain is set 2025-04-02 02:33:24 +07:00
7c57477238 Add local configuration to set http domain
change http domain by PUT from local server request
2025-04-02 02:12:13 +07:00
9ed58d1853 Prepare release 3.3.2 2025-03-31 17:12:15 +07:00
6c52b038e9 Merge pull request #295 from airgradienthq/feat/enable-at-debug
Enable cellular AT command debug when in network registration
2025-03-31 17:09:43 +07:00
2f69932ef7 add depth submodule update 2025-03-31 17:04:37 +07:00
1d96a274a6 Merge branch 'develop' into feat/enable-at-debug 2025-03-31 16:55:12 +07:00
df9f6dfc95 Fix bugs from 3.3.1 release 2025-03-31 16:52:09 +07:00
3fc02b3f54 Check signal when initialize cellular client 2025-03-31 16:51:29 +07:00
958ed0bd80 Fix TVOC and NOx payload position 2025-03-31 15:26:34 +07:00
e9be9dcc83 Fix mqtt host still exist on local when on server is disabled 2025-03-31 14:51:53 +07:00
7fbab82088 Change log level when correction not found 2025-03-31 14:07:30 +07:00
decdecdf22 Don't start mqtt when network option is cellular
Even when mqtt host is set
2025-03-31 14:01:49 +07:00
145c612867 Enable cellular at debug when registering network
On boot, airgradient-client change cellular init timeout to 5 mins
2025-03-31 13:53:56 +07:00
10 changed files with 116 additions and 28 deletions

View File

@ -38,6 +38,7 @@ jobs:
steps:
- uses: actions/checkout@v4.2.2
with:
fetch-depth: 0
submodules: 'true'
- uses: arduino/compile-sketches@v1.1.2
with:

2
.gitignore vendored
View File

@ -4,5 +4,7 @@ build
/.idea/
.pio
.cache
.clangd
logs
gen_compile_commands.py
compile_commands.json

View File

@ -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**

View File

@ -423,6 +423,11 @@ static void initMqtt(void) {
return;
}
if (networkOption == UseCellular) {
Serial.println("MQTT not available for cellular options");
return;
}
if (mqttClient.begin(mqttUri)) {
Serial.println("Successfully connected to MQTT broker");
createMqttTask();
@ -808,8 +813,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");
@ -934,6 +937,19 @@ void initializeNetwork() {
networkOption = UseWifi;
}
if (networkOption == UseCellular) {
// Enable serial stream debugging to check the AT command when doing registration
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);
@ -943,6 +959,11 @@ void initializeNetwork() {
ESP.restart();
}
if (networkOption == UseCellular) {
// Disabling it again
agSerial->setDebug(false);
}
if (networkOption == UseWifi) {
if (!wifiConnector.connect()) {
Serial.println("Cannot initiate wifi connection");
@ -1026,6 +1047,16 @@ static void configUpdateHandle() {
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()) {
@ -1432,12 +1463,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;
}
@ -1495,11 +1522,13 @@ void networkingTask(void *args) {
else if (networkOption == UseCellular) {
if (agClient->isClientReady() == false) {
Serial.println("Cellular client not ready, ensuring connection...");
agSerial->setDebug(true);
if (agClient->ensureClientConnection() == false) {
Serial.println("Cellular client connection not ready, retry in 5s...");
delay(5000);
continue;
}
agSerial->setDebug(false);
}
}

View File

@ -1,5 +1,5 @@
name=AirGradient Air Quality Sensor
version=3.3.1
version=3.3.3
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.

View File

@ -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
@ -240,7 +242,7 @@ bool Configuration::updateTempHumCorrection(JSONVar &json, TempHumCorrection &ta
JSONVar corrections = json[jprop_corrections];
if (!corrections.hasOwnProperty(correctionName)) {
logWarning(String(correctionName) + " correction field not found on configuration");
logInfo(String(correctionName) + " correction field not found on configuration");
return false;
}
@ -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;
@ -735,11 +738,17 @@ 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;
}
} else {
}
else if (JSON.typeof_(root[jprop_mqttBrokerUrl]) == "null" and !isLocal) {
// So if its not available on the json and json comes from aigradient server
// then set its value to default (empty)
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
}
else {
if (jsonTypeInvalid(root[jprop_mqttBrokerUrl], "string")) {
failedMessage =
jsonTypeInvalidMessage(String(jprop_mqttBrokerUrl), "string");
@ -748,6 +757,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];
@ -1030,6 +1065,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
*
@ -1115,7 +1160,7 @@ bool Configuration::isUpdated(void) {
}
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) {
@ -1269,6 +1314,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;

View File

@ -82,6 +82,7 @@ public:
String getLedBarModeName(void);
bool getDisplayMode(void);
String getMqttBrokerUri(void);
String getHttpDomain(void);
bool isPostDataToAirGradient(void);
ConfigurationControl getConfigurationControl(void);
bool isCo2CalibrationRequested(void);

View File

@ -804,16 +804,16 @@ std::string Measurements::buildMeasuresPayload(Measures &mc) {
oss << ",";
// NOx
if (utils::isValidNOx(mc.nox)) {
oss << std::round(mc.nox);
// TVOC
if (utils::isValidVOC(mc.tvoc)) {
oss << std::round(mc.tvoc);
}
oss << ",";
// TVOC
if (utils::isValidVOC(mc.tvoc)) {
oss << std::round(mc.tvoc);
// NOx
if (utils::isValidNOx(mc.nox)) {
oss << std::round(mc.nox);
}
oss << ",";
@ -827,10 +827,6 @@ std::string Measurements::buildMeasuresPayload(Measures &mc) {
oss << std::round(mc.pm_03_pc[1]);
}
// char datapoint[128] = {0};
// snprintf(datapoint, 128, "%d,%.0f,%.0f,%.0f,%.0f,%.0f,%d,%d,%d", co2, temp * 10,
// hum * 10, pm01 * 10, pm25 * 10, pm10 * 10, tvoc, nox, pm003Count);
return oss.str();
}

View File

@ -15,7 +15,7 @@
#include "Main/utils.h"
#ifndef GIT_VERSION
#define GIT_VERSION "3.3.1-snap"
#define GIT_VERSION "3.3.3-snap"
#endif