Skip to content

Commit 93f7917

Browse files
committed
Release 3.2.0-alpha
2 parents 92e74fe + 615c238 commit 93f7917

25 files changed

+952
-525
lines changed

docs/local-server.md

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
44

5-
#### Discovery
5+
### Discovery
66

77
The monitors run a mDNS discovery. So within the same network, the monitor can be accessed through:
88

@@ -11,7 +11,7 @@ http://airgradient_{{serialnumber}}.local
1111

1212
The following requests are possible:
1313

14-
#### Get Current Air Quality (GET)
14+
### Get Current Air Quality (GET)
1515

1616
With the path "/measures/current" you can get the current air quality data.
1717

@@ -80,7 +80,7 @@ You get the following response:
8080

8181
Compensated values apply correction algorithms to make the sensor values more accurate. Temperature and relative humidity correction is only applied on the outdoor monitor Open Air but the properties _compensated will still be send also for the indoor monitor AirGradient ONE.
8282

83-
#### Get Configuration Parameters (GET)
83+
### Get Configuration Parameters (GET)
8484

8585
"/config" path returns the current configuration of the monitor.
8686

@@ -111,7 +111,7 @@ Compensated values apply correction algorithms to make the sensor values more ac
111111
}
112112
```
113113

114-
#### Set Configuration Parameters (PUT)
114+
### Set Configuration Parameters (PUT)
115115

116116
Configuration parameters can be changed with a PUT request to the monitor, e.g.
117117

@@ -131,11 +131,11 @@ Example to set monitor to Celsius
131131

132132
``` -d "{\"param\":\"value\"}" ```
133133

134-
#### Avoiding Conflicts with Configuration on AirGradient Server
134+
### Avoiding Conflicts with Configuration on AirGradient Server
135135

136136
If the monitor is set up on the AirGradient dashboard, it will also receive the configuration parameters from there. In case you do not want this, please set `configurationControl` to `local`. In case you set it to `cloud` and want to change it to `local`, you need to make a factory reset.
137137

138-
#### Configuration Parameters (GET/PUT)
138+
### Configuration Parameters (GET/PUT)
139139

140140
| Properties | Description | Type | Accepted Values | Example |
141141
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
@@ -154,15 +154,18 @@ If the monitor is set up on the AirGradient dashboard, it will also receive the
154154
| `ledBarTestRequested` | Can be set to trigger a test. | Boolean | `true` : LEDs will run test sequence | `{"ledBarTestRequested": true}` |
155155
| `noxLearningOffset` | Set NOx learning gain offset. | Number | 0-720 (default 12) | `{"noxLearningOffset": 12}` |
156156
| `tvocLearningOffset` | Set VOC learning gain offset. | Number | 0-720 (default 12) | `{"tvocLearningOffset": 12}` |
157-
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | `{"offlineMode": true}` |
158157
| `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 }` |
159158
| `corrections` | Sets correction options to display and measurement values on local server response. (version >= [3.1.11]()) | Object | _see corrections section_ | _see corrections section_ |
160159

161160

161+
**Notes**
162162

163-
#### Corrections
163+
- `offlineMode` : the device will disable all network operation, and only show measurements on the display and ledbar; Read-Only; Change can be apply using reset button on boot.
164+
- `disableCloudConnection` : disable every request to AirGradient server, means features like post data to AirGradient server, configuration from AirGradient server and automatic firmware updates are disabled. This configuration overrides `configurationControl` and `postDataToAirGradient`; Read-Only; Change can be apply from wifi setup webpage.
164165

165-
The `corrections` object allows configuring PM2.5 correction algorithms and parameters locally. This affects both the display and local server response values.
166+
### Corrections
167+
168+
The `corrections` object allows configuring PM2.5, Temperature and Humidity correction algorithms and parameters locally. This affects both the display, local server response and open metrics values.
166169

167170
Example correction configuration:
168171

@@ -176,11 +179,29 @@ Example correction configuration:
176179
"scalingFactor": 0,
177180
"useEpa2021": false
178181
}
179-
}
182+
},
183+
"atmp": {
184+
"correctionAlgorithm": "<Option In String>",
185+
"slr": {
186+
"intercept": 0,
187+
"scalingFactor": 0
188+
}
189+
},
190+
"rhum": {
191+
"correctionAlgorithm": "<Option In String>",
192+
"slr": {
193+
"intercept": 0,
194+
"scalingFactor": 0
195+
}
196+
},
180197
}
181198
}
182199
```
183200

201+
#### PM 2.5
202+
203+
Field Name: `pm02`
204+
184205
| Algorithm | Value | Description | SLR required |
185206
|------------|-------------|------|---------|
186207
| Raw | `"none"` | No correction (default) | No |
@@ -214,3 +235,23 @@ curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header '
214235
```bash
215236
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20240104","slr":{"intercept":0,"scalingFactor":0.02896,"useEpa2021":true}}}}'
216237
```
238+
239+
#### Temperature & Humidity
240+
241+
Field Name:
242+
- Temperature: `atmp`
243+
- Humidity: `rhum`
244+
245+
| Algorithm | Value | Description | SLR required |
246+
|------------|-------------|------|---------|
247+
| Raw | `"none"` | No correction (default) | No |
248+
| AirGradient Standard Correction | `"ag_pms5003t_2024"` | Using standard airgradient correction (for outdoor monitor)| No |
249+
| Custom | `"custom"` | custom corrections constant, set `intercept` and `scalingFactor` manually | Yes |
250+
251+
*Table above apply for both Temperature and Humidity*
252+
253+
**Example**
254+
255+
```bash
256+
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"atmp":{"correctionAlgorithm":"custom","slr":{"intercept":0.2,"scalingFactor":1.1}}}}'
257+
```

examples/BASIC/BASIC.ino

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
5555
static AirGradient ag(DIY_BASIC);
5656
static Configuration configuration(Serial);
5757
static AgApiClient apiClient(Serial, configuration);
58-
static Measurements measurements;
58+
static Measurements measurements(configuration);
5959
static OledDisplay oledDisplay(configuration, measurements, Serial);
6060
static StateMachine stateMachine(oledDisplay, Serial, measurements,
6161
configuration);
@@ -124,6 +124,7 @@ void setup() {
124124
apiClient.setAirGradient(&ag);
125125
openMetrics.setAirGradient(&ag);
126126
localServer.setAirGraident(&ag);
127+
measurements.setAirGradient(&ag);
127128

128129
/** Example set custom API root URL */
129130
// apiClient.setApiRoot("https://example.custom.api");
@@ -149,9 +150,12 @@ void setup() {
149150
initMqtt();
150151
sendDataToAg();
151152

152-
apiClient.fetchServerConfiguration();
153+
if (configuration.getConfigurationControl() !=
154+
ConfigurationControl::ConfigurationControlLocal) {
155+
apiClient.fetchServerConfiguration();
156+
}
153157
configSchedule.update();
154-
if (apiClient.isFetchConfigureFailed()) {
158+
if (apiClient.isFetchConfigurationFailed()) {
155159
if (apiClient.isNotAvailableOnDashboard()) {
156160
stateMachine.displaySetAddToDashBoard();
157161
stateMachine.displayHandle(
@@ -316,7 +320,7 @@ static void mqttHandle(void) {
316320
}
317321

318322
if (mqttClient.isConnected()) {
319-
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
323+
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
320324
String topic = "airgradient/readings/" + ag.deviceId();
321325
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
322326
Serial.println("MQTT sync success");
@@ -415,6 +419,14 @@ static void failedHandler(String msg) {
415419
}
416420

417421
static void configurationUpdateSchedule(void) {
422+
if (configuration.isOfflineMode() ||
423+
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
424+
Serial.println("Ignore fetch server configuration. Either mode is offline "
425+
"or configurationControl set to local");
426+
apiClient.resetFetchConfigurationStatus();
427+
return;
428+
}
429+
418430
if (apiClient.fetchServerConfiguration()) {
419431
configUpdateHandle();
420432
}
@@ -472,7 +484,7 @@ static void appDispHandler(void) {
472484
if (configuration.isOfflineMode() == false) {
473485
if (wifiConnector.isConnected() == false) {
474486
state = AgStateMachineWiFiLost;
475-
} else if (apiClient.isFetchConfigureFailed()) {
487+
} else if (apiClient.isFetchConfigurationFailed()) {
476488
state = AgStateMachineSensorConfigFailed;
477489
if (apiClient.isNotAvailableOnDashboard()) {
478490
stateMachine.displaySetAddToDashBoard();
@@ -521,17 +533,21 @@ static void sendDataToServer(void) {
521533
int bootCount = measurements.bootCount() + 1;
522534
measurements.setBootCount(bootCount);
523535

524-
/** Ignore send data to server if postToAirGradient disabled */
525-
if (configuration.isPostDataToAirGradient() == false ||
526-
configuration.isOfflineMode()) {
536+
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
537+
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
538+
"or post data to server disabled");
527539
return;
528540
}
529541

530-
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
542+
if (wifiConnector.isConnected() == false) {
543+
Serial.println("WiFi not connected, skipping data transmission to AG server");
544+
return;
545+
}
546+
547+
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
531548
if (apiClient.postToServer(syncData)) {
532549
Serial.println();
533-
Serial.println(
534-
"Online mode and isPostToAirGradient = true: watchdog reset");
550+
Serial.println("Online mode and isPostToAirGradient = true");
535551
Serial.println();
536552
}
537553
}

examples/BASIC/LocalServer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
5353
}
5454

5555
void LocalServer::_GET_measure(void) {
56-
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
56+
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI());
5757
server.send(200, "application/json", toSend);
5858
}
5959

examples/BASIC/OpenMetrics.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
4343
"1 if the AirGradient device was able to successfully fetch its "
4444
"configuration from the server",
4545
"gauge");
46-
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
46+
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
4747

4848
add_metric(
4949
"post_ok",
@@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
6666
int pm03PCount = utils::getInvalidPmValue();
6767
int co2 = utils::getInvalidCO2();
6868
int atmpCompensated = utils::getInvalidTemperature();
69-
int ahumCompensated = utils::getInvalidHumidity();
69+
int rhumCompensated = utils::getInvalidHumidity();
7070
int tvoc = utils::getInvalidVOC();
7171
int tvocRaw = utils::getInvalidVOC();
7272
int nox = utils::getInvalidNOx();
@@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
7676
_temp = measure.getFloat(Measurements::Temperature);
7777
_hum = measure.getFloat(Measurements::Humidity);
7878
atmpCompensated = _temp;
79-
ahumCompensated = _hum;
79+
rhumCompensated = _hum;
8080
}
8181

8282
if (config.hasSensorPMS1) {
8383
pm01 = measure.get(Measurements::PM01);
84-
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
84+
float correctedPm = measure.getCorrectedPM25(false, 1);
8585
pm25 = round(correctedPm);
8686
pm10 = measure.get(Measurements::PM10);
8787
pm03PCount = measure.get(Measurements::PM03_PC);
@@ -191,12 +191,12 @@ String OpenMetrics::getPayload(void) {
191191
"gauge", "percent");
192192
add_metric_point("", String(_hum));
193193
}
194-
if (utils::isValidHumidity(ahumCompensated)) {
194+
if (utils::isValidHumidity(rhumCompensated)) {
195195
add_metric("humidity_compensated",
196196
"The compensated relative humidity as measured by the "
197197
"AirGradient SHT / PMS sensor",
198198
"gauge", "percent");
199-
add_metric_point("", String(ahumCompensated));
199+
add_metric_point("", String(rhumCompensated));
200200
}
201201

202202
response += "# EOF\n";

examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
5555
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
5656
static Configuration configuration(Serial);
5757
static AgApiClient apiClient(Serial, configuration);
58-
static Measurements measurements;
58+
static Measurements measurements(configuration);
5959
static OledDisplay oledDisplay(configuration, measurements, Serial);
6060
static StateMachine stateMachine(oledDisplay, Serial, measurements,
6161
configuration);
@@ -124,6 +124,7 @@ void setup() {
124124
apiClient.setAirGradient(&ag);
125125
openMetrics.setAirGradient(&ag);
126126
localServer.setAirGraident(&ag);
127+
measurements.setAirGradient(&ag);
127128

128129
/** Example set custom API root URL */
129130
// apiClient.setApiRoot("https://example.custom.api");
@@ -149,9 +150,12 @@ void setup() {
149150
initMqtt();
150151
sendDataToAg();
151152

152-
apiClient.fetchServerConfiguration();
153+
if (configuration.getConfigurationControl() !=
154+
ConfigurationControl::ConfigurationControlLocal) {
155+
apiClient.fetchServerConfiguration();
156+
}
153157
configSchedule.update();
154-
if (apiClient.isFetchConfigureFailed()) {
158+
if (apiClient.isFetchConfigurationFailed()) {
155159
if (apiClient.isNotAvailableOnDashboard()) {
156160
stateMachine.displaySetAddToDashBoard();
157161
stateMachine.displayHandle(
@@ -373,7 +377,7 @@ static void mqttHandle(void) {
373377
}
374378

375379
if (mqttClient.isConnected()) {
376-
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI(), ag, configuration);
380+
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
377381
String topic = "airgradient/readings/" + ag.deviceId();
378382
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
379383
Serial.println("MQTT sync success");
@@ -467,6 +471,14 @@ static void failedHandler(String msg) {
467471
}
468472

469473
static void configurationUpdateSchedule(void) {
474+
if (configuration.isOfflineMode() ||
475+
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
476+
Serial.println("Ignore fetch server configuration. Either mode is offline "
477+
"or configurationControl set to local");
478+
apiClient.resetFetchConfigurationStatus();
479+
return;
480+
}
481+
470482
if (apiClient.fetchServerConfiguration()) {
471483
configUpdateHandle();
472484
}
@@ -524,7 +536,7 @@ static void appDispHandler(void) {
524536
if (configuration.isOfflineMode() == false) {
525537
if (wifiConnector.isConnected() == false) {
526538
state = AgStateMachineWiFiLost;
527-
} else if (apiClient.isFetchConfigureFailed()) {
539+
} else if (apiClient.isFetchConfigurationFailed()) {
528540
state = AgStateMachineSensorConfigFailed;
529541
if (apiClient.isNotAvailableOnDashboard()) {
530542
stateMachine.displaySetAddToDashBoard();
@@ -573,17 +585,21 @@ static void sendDataToServer(void) {
573585
int bootCount = measurements.bootCount() + 1;
574586
measurements.setBootCount(bootCount);
575587

576-
/** Ignore send data to server if postToAirGradient disabled */
577-
if (configuration.isPostDataToAirGradient() == false ||
578-
configuration.isOfflineMode()) {
588+
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
589+
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
590+
"or post data to server disabled");
579591
return;
580592
}
581593

582-
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
594+
if (wifiConnector.isConnected() == false) {
595+
Serial.println("WiFi not connected, skipping data transmission to AG server");
596+
return;
597+
}
598+
599+
String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
583600
if (apiClient.postToServer(syncData)) {
584601
Serial.println();
585-
Serial.println(
586-
"Online mode and isPostToAirGradient = true: watchdog reset");
602+
Serial.println("Online mode and isPostToAirGradient = true");
587603
Serial.println();
588604
}
589605
}

examples/DiyProIndoorV3_3/LocalServer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
5353
}
5454

5555
void LocalServer::_GET_measure(void) {
56-
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI(), *ag, config);
56+
String toSend = measure.toString(true, fwMode, wifiConnector.RSSI());
5757
server.send(200, "application/json", toSend);
5858
}
5959

0 commit comments

Comments
 (0)