Skip to content

Commit

Permalink
Release 3.2.0-alpha
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelbles07 committed Feb 7, 2025
2 parents 92e74fe + 615c238 commit 93f7917
Show file tree
Hide file tree
Showing 25 changed files with 952 additions and 525 deletions.
61 changes: 51 additions & 10 deletions docs/local-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

#### Discovery
### Discovery

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

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

The following requests are possible:

#### Get Current Air Quality (GET)
### Get Current Air Quality (GET)

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

Expand Down Expand Up @@ -80,7 +80,7 @@ You get the following response:

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.

#### Get Configuration Parameters (GET)
### Get Configuration Parameters (GET)

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

Expand Down Expand Up @@ -111,7 +111,7 @@ Compensated values apply correction algorithms to make the sensor values more ac
}
```

#### Set Configuration Parameters (PUT)
### Set Configuration Parameters (PUT)

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

Expand All @@ -131,11 +131,11 @@ Example to set monitor to Celsius

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

#### Avoiding Conflicts with Configuration on AirGradient Server
### Avoiding Conflicts with Configuration on AirGradient Server

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.

#### Configuration Parameters (GET/PUT)
### Configuration Parameters (GET/PUT)

| Properties | Description | Type | Accepted Values | Example |
|-----------------------------------|:-----------------------------------------------------------------|---------|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------|
Expand All @@ -154,15 +154,18 @@ 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}` |
| `offlineMode` | Set monitor to run without WiFi. | Boolean | `false`: Disabled (default) <br> `true`: Enabled | `{"offlineMode": true}` |
| `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**

#### Corrections
- `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.
- `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.

The `corrections` object allows configuring PM2.5 correction algorithms and parameters locally. This affects both the display and local server response values.
### Corrections

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.

Example correction configuration:

Expand All @@ -176,11 +179,29 @@ Example correction configuration:
"scalingFactor": 0,
"useEpa2021": false
}
}
},
"atmp": {
"correctionAlgorithm": "<Option In String>",
"slr": {
"intercept": 0,
"scalingFactor": 0
}
},
"rhum": {
"correctionAlgorithm": "<Option In String>",
"slr": {
"intercept": 0,
"scalingFactor": 0
}
},
}
}
```

#### PM 2.5

Field Name: `pm02`

| Algorithm | Value | Description | SLR required |
|------------|-------------|------|---------|
| Raw | `"none"` | No correction (default) | No |
Expand Down Expand Up @@ -214,3 +235,23 @@ curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header '
```bash
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}}}}'
```

#### Temperature & Humidity

Field Name:
- Temperature: `atmp`
- Humidity: `rhum`

| Algorithm | Value | Description | SLR required |
|------------|-------------|------|---------|
| Raw | `"none"` | No correction (default) | No |
| AirGradient Standard Correction | `"ag_pms5003t_2024"` | Using standard airgradient correction (for outdoor monitor)| No |
| Custom | `"custom"` | custom corrections constant, set `intercept` and `scalingFactor` manually | Yes |

*Table above apply for both Temperature and Humidity*

**Example**

```bash
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}}}}'
```
38 changes: 27 additions & 11 deletions examples/BASIC/BASIC.ino
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
static AirGradient ag(DIY_BASIC);
static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements;
static Measurements measurements(configuration);
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration);
Expand Down Expand Up @@ -124,6 +124,7 @@ void setup() {
apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);

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

apiClient.fetchServerConfiguration();
if (configuration.getConfigurationControl() !=
ConfigurationControl::ConfigurationControlLocal) {
apiClient.fetchServerConfiguration();
}
configSchedule.update();
if (apiClient.isFetchConfigureFailed()) {
if (apiClient.isFetchConfigurationFailed()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
Expand Down Expand Up @@ -316,7 +320,7 @@ static void mqttHandle(void) {
}

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

static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline "
"or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}

if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
Expand Down Expand Up @@ -472,7 +484,7 @@ static void appDispHandler(void) {
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
} else if (apiClient.isFetchConfigurationFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
Expand Down Expand Up @@ -521,17 +533,21 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);

/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
"or post data to server disabled");
return;
}

String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}

String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println();
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/BASIC/LocalServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
}

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

Expand Down
12 changes: 6 additions & 6 deletions examples/BASIC/OpenMetrics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ String OpenMetrics::getPayload(void) {
"1 if the AirGradient device was able to successfully fetch its "
"configuration from the server",
"gauge");
add_metric_point("", apiClient.isFetchConfigureFailed() ? "0" : "1");
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");

add_metric(
"post_ok",
Expand All @@ -66,7 +66,7 @@ String OpenMetrics::getPayload(void) {
int pm03PCount = utils::getInvalidPmValue();
int co2 = utils::getInvalidCO2();
int atmpCompensated = utils::getInvalidTemperature();
int ahumCompensated = utils::getInvalidHumidity();
int rhumCompensated = utils::getInvalidHumidity();
int tvoc = utils::getInvalidVOC();
int tvocRaw = utils::getInvalidVOC();
int nox = utils::getInvalidNOx();
Expand All @@ -76,12 +76,12 @@ String OpenMetrics::getPayload(void) {
_temp = measure.getFloat(Measurements::Temperature);
_hum = measure.getFloat(Measurements::Humidity);
atmpCompensated = _temp;
ahumCompensated = _hum;
rhumCompensated = _hum;
}

if (config.hasSensorPMS1) {
pm01 = measure.get(Measurements::PM01);
float correctedPm = measure.getCorrectedPM25(*ag, config, false, 1);
float correctedPm = measure.getCorrectedPM25(false, 1);
pm25 = round(correctedPm);
pm10 = measure.get(Measurements::PM10);
pm03PCount = measure.get(Measurements::PM03_PC);
Expand Down Expand Up @@ -191,12 +191,12 @@ String OpenMetrics::getPayload(void) {
"gauge", "percent");
add_metric_point("", String(_hum));
}
if (utils::isValidHumidity(ahumCompensated)) {
if (utils::isValidHumidity(rhumCompensated)) {
add_metric("humidity_compensated",
"The compensated relative humidity as measured by the "
"AirGradient SHT / PMS sensor",
"gauge", "percent");
add_metric_point("", String(ahumCompensated));
add_metric_point("", String(rhumCompensated));
}

response += "# EOF\n";
Expand Down
38 changes: 27 additions & 11 deletions examples/DiyProIndoorV3_3/DiyProIndoorV3_3.ino
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
static AirGradient ag(DIY_PRO_INDOOR_V3_3);
static Configuration configuration(Serial);
static AgApiClient apiClient(Serial, configuration);
static Measurements measurements;
static Measurements measurements(configuration);
static OledDisplay oledDisplay(configuration, measurements, Serial);
static StateMachine stateMachine(oledDisplay, Serial, measurements,
configuration);
Expand Down Expand Up @@ -124,6 +124,7 @@ void setup() {
apiClient.setAirGradient(&ag);
openMetrics.setAirGradient(&ag);
localServer.setAirGraident(&ag);
measurements.setAirGradient(&ag);

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

apiClient.fetchServerConfiguration();
if (configuration.getConfigurationControl() !=
ConfigurationControl::ConfigurationControlLocal) {
apiClient.fetchServerConfiguration();
}
configSchedule.update();
if (apiClient.isFetchConfigureFailed()) {
if (apiClient.isFetchConfigurationFailed()) {
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(
Expand Down Expand Up @@ -373,7 +377,7 @@ static void mqttHandle(void) {
}

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

static void configurationUpdateSchedule(void) {
if (configuration.isOfflineMode() ||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
Serial.println("Ignore fetch server configuration. Either mode is offline "
"or configurationControl set to local");
apiClient.resetFetchConfigurationStatus();
return;
}

if (apiClient.fetchServerConfiguration()) {
configUpdateHandle();
}
Expand Down Expand Up @@ -524,7 +536,7 @@ static void appDispHandler(void) {
if (configuration.isOfflineMode() == false) {
if (wifiConnector.isConnected() == false) {
state = AgStateMachineWiFiLost;
} else if (apiClient.isFetchConfigureFailed()) {
} else if (apiClient.isFetchConfigurationFailed()) {
state = AgStateMachineSensorConfigFailed;
if (apiClient.isNotAvailableOnDashboard()) {
stateMachine.displaySetAddToDashBoard();
Expand Down Expand Up @@ -573,17 +585,21 @@ static void sendDataToServer(void) {
int bootCount = measurements.bootCount() + 1;
measurements.setBootCount(bootCount);

/** Ignore send data to server if postToAirGradient disabled */
if (configuration.isPostDataToAirGradient() == false ||
configuration.isOfflineMode()) {
if (configuration.isOfflineMode() || !configuration.isPostDataToAirGradient()) {
Serial.println("Skipping transmission of data to AG server. Either mode is offline "
"or post data to server disabled");
return;
}

String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI(), ag, configuration);
if (wifiConnector.isConnected() == false) {
Serial.println("WiFi not connected, skipping data transmission to AG server");
return;
}

String syncData = measurements.toString(false, fwMode, wifiConnector.RSSI());
if (apiClient.postToServer(syncData)) {
Serial.println();
Serial.println(
"Online mode and isPostToAirGradient = true: watchdog reset");
Serial.println("Online mode and isPostToAirGradient = true");
Serial.println();
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/DiyProIndoorV3_3/LocalServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ void LocalServer::_GET_metrics(void) {
}

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

Expand Down
Loading

0 comments on commit 93f7917

Please sign in to comment.