mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-12-16 18:48:26 +01:00
Compare commits
22 Commits
3.4.0
...
feat/provi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f1846f040 | ||
|
|
b5d89cf118 | ||
|
|
b178ad12a5 | ||
|
|
a55677422b | ||
|
|
da694edcab | ||
|
|
ae247ead48 | ||
|
|
49eed70863 | ||
|
|
b8768e0ed6 | ||
|
|
94bd0adf8b | ||
|
|
38bcf4c05e | ||
|
|
f1110905ca | ||
|
|
e196e6c6b8 | ||
|
|
3a56ee448d | ||
|
|
2f13c8fa66 | ||
|
|
449817a384 | ||
|
|
19df574130 | ||
|
|
16339acb97 | ||
|
|
d075d12011 | ||
|
|
75be7d9fc5 | ||
|
|
309d322100 | ||
|
|
89ebe6c39f | ||
|
|
29db5469f5 |
31
.github/workflows/check.yml
vendored
31
.github/workflows/check.yml
vendored
@@ -1,5 +1,36 @@
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
trailing-whitespace:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Check for trailing whitespace
|
||||
run: |
|
||||
set -u
|
||||
|
||||
# Don't enforce checks on vendored libraries.
|
||||
readonly EXCLUDED_DIR='src/Libraries'
|
||||
|
||||
has_trailing_whitespace=false
|
||||
|
||||
while read -r line; do
|
||||
if grep \
|
||||
"\s$" \
|
||||
--line-number \
|
||||
--with-filename \
|
||||
--binary-files=without-match \
|
||||
"${line}"; then
|
||||
has_trailing_whitespace=true
|
||||
fi
|
||||
done < <(git ls-files | grep --invert-match "^${EXCLUDED_DIR}/")
|
||||
|
||||
if [ "$has_trailing_whitespace" = true ]; then
|
||||
echo "ERROR: Found trailing whitespace"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
compile:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
@@ -32,7 +32,7 @@ Each GitHub release note will also include the planned rollout date for wider av
|
||||
|
||||
## Help & Support
|
||||
|
||||
If you have any questions or problems, check out [our forum](https://forum.airgradient.com/).
|
||||
If you have any questions or problems, check out [our forum](https://forum.airgradient.com/).
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ Arduino IDE version 2.x ([download](https://www.arduino.cc/en/software))
|
||||
|
||||
#### Version < 3.2.0
|
||||
|
||||
Using library manager install the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||
Using library manager install the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||
|
||||

|
||||
|
||||
#### Version >= 3.3.0
|
||||
|
||||
- From your terminal, go to Arduino libraries folder (windows and mac: `Documents/Arduino/libraries` or linux: `~/Arduino/Libraries`).
|
||||
- With **git** cli, execute this command `git clone --recursive https://github.com/airgradienthq/arduino.git AirGradient_Air_Quality_Sensor`
|
||||
- Restart Arduino IDE
|
||||
- With **git** cli, execute this command `git clone --recursive https://github.com/airgradienthq/arduino.git AirGradient_Air_Quality_Sensor`
|
||||
- Restart Arduino IDE
|
||||
|
||||
3. On tools tab, follow settings below
|
||||
|
||||
@@ -57,7 +57,7 @@ Upload Speed ➝ 921600
|
||||
|
||||

|
||||
|
||||
3. Install AirGradient library on library manager using the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||
3. Install AirGradient library on library manager using the latest version (Tools ➝ Manage Libraries... ➝ search for `"airgradient"`)
|
||||
|
||||

|
||||
|
||||
@@ -65,7 +65,7 @@ Upload Speed ➝ 921600
|
||||
|
||||

|
||||
|
||||
5. Open sketch to compile (File ➝ Examples ➝ AirGradient Air Quality Sensor ➝ `<Model Option>`). Depends on the DIY model, either `BASIC`, `DiyProIndoorV3_3` and `DiyProIndoorV4_2`
|
||||
5. Open sketch to compile (File ➝ Examples ➝ AirGradient Air Quality Sensor ➝ `<Model Option>`). Depends on the DIY model, either `BASIC`, `DiyProIndoorV3_3` and `DiyProIndoorV4_2`
|
||||
6. Compile
|
||||
|
||||

|
||||
@@ -78,13 +78,13 @@ ModuleNotFoundError: No module named ‘serial’
|
||||
|
||||

|
||||
|
||||
Make sure python pyserial module installed globally in the environment by executing:
|
||||
Make sure python pyserial module installed globally in the environment by executing:
|
||||
|
||||
`$ sudo apt install -y python3-pyserial`
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
`$ pip install pyserial`
|
||||
`$ pip install pyserial`
|
||||
|
||||
Choose based on how python installed on your machine. But most user, using `apt` is better.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Local Server API
|
||||
|
||||
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
|
||||
From [firmware version 3.0.10](firmwares) onwards, the AirGradient ONE and Open Air monitors have below API available.
|
||||
|
||||
### Discovery
|
||||
|
||||
@@ -20,7 +20,7 @@ http://airgradient_ecda3b1eaaaf.local/measures/current
|
||||
“ecda3b1eaaaf” being the serial number of your monitor.
|
||||
|
||||
You get the following response:
|
||||
```json
|
||||
```json
|
||||
{
|
||||
"wifi": -46,
|
||||
"serialno": "ecda3b1eaaaf",
|
||||
@@ -84,7 +84,7 @@ Compensated values apply correction algorithms to make the sensor values more ac
|
||||
|
||||
"/config" path returns the current configuration of the monitor.
|
||||
|
||||
```json
|
||||
```json
|
||||
{
|
||||
"country": "TH",
|
||||
"pmStandard": "ugm3",
|
||||
@@ -118,22 +118,22 @@ Configuration parameters can be changed with a PUT request to the monitor, e.g.
|
||||
Example to force CO2 calibration
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"co2CalibrationRequested":true}' http://airgradient_84fce612eff4.local/config
|
||||
```
|
||||
|
||||
Example to set monitor to Celsius
|
||||
|
||||
```bash
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config
|
||||
curl -X PUT -H "Content-Type: application/json" -d '{"temperatureUnit":"c"}' http://airgradient_84fce612eff4.local/config
|
||||
```
|
||||
|
||||
If you use command prompt on Windows, you need to escape the quotes:
|
||||
|
||||
|
||||
``` -d "{\"param\":\"value\"}" ```
|
||||
|
||||
### 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.
|
||||
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)
|
||||
|
||||
@@ -204,34 +204,34 @@ Example correction configuration:
|
||||
Field Name: `pm02`
|
||||
|
||||
| Algorithm | Value | Description | SLR required |
|
||||
|------------|-------------|------|---------|
|
||||
|------------|-------------|------|---------|
|
||||
| Raw | `"none"` | No correction (default) | No |
|
||||
| EPA 2021 | `"epa_2021"` | Use EPA 2021 correction factors on top of raw value | No |
|
||||
| PMS5003_20240104 | `"slr_PMS5003_20240104"` | Correction for PMS5003 sensor batch 20240104| Yes |
|
||||
| PMS5003_20240104 | `"slr_PMS5003_20240104"` | Correction for PMS5003 sensor batch 20240104| Yes |
|
||||
| PMS5003_20231218 | `"slr_PMS5003_20231218"` | Correction for PMS5003 sensor batch 20231218| Yes |
|
||||
| PMS5003_20231030 | `"slr_PMS5003_20231030"` | Correction for PMS5003 sensor batch 20231030| Yes |
|
||||
|
||||
**NOTES**:
|
||||
**NOTES**:
|
||||
|
||||
- Set `useEpa2021` to `true` if want to apply EPA 2021 correction factors on top of SLR correction value, otherwise `false`
|
||||
- `intercept` and `scalingFactor` values can be obtained from [this article](https://www.airgradient.com/blog/low-readings-from-pms5003/)
|
||||
- If `configurationControl` is set to `local` (eg. when using Home Assistant), correction need to be set manually, see examples below
|
||||
- If `configurationControl` is set to `local` (eg. when using Home Assistant), correction need to be set manually, see examples below
|
||||
|
||||
**Examples**:
|
||||
|
||||
- PMS5003_20231030
|
||||
- PMS5003_20231030
|
||||
|
||||
```bash
|
||||
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231030","slr":{"intercept":0,"scalingFactor":0.02838,"useEpa2021":true}}}}'
|
||||
```
|
||||
|
||||
- PMS5003_20231218
|
||||
- PMS5003_20231218
|
||||
|
||||
```bash
|
||||
curl --location -X PUT 'http://airgradient_84fce612eff4.local/config' --header 'Content-Type: application/json' --data '{"corrections":{"pm02":{"correctionAlgorithm":"slr_PMS5003_20231218","slr":{"intercept":0,"scalingFactor":0.03525,"useEpa2021":true}}}}'
|
||||
```
|
||||
|
||||
- PMS5003_20240104
|
||||
- PMS5003_20240104
|
||||
|
||||
```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}}}}'
|
||||
@@ -244,10 +244,10 @@ Field Name:
|
||||
- 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 |
|
||||
| Custom | `"custom"` | custom corrections constant, set `intercept` and `scalingFactor` manually | Yes |
|
||||
|
||||
*Table above apply for both Temperature and Humidity*
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## OTA Updates
|
||||
|
||||
From [firmware version 3.1.1](https://github.com/airgradienthq/arduino/tree/3.1.1) onwards, the AirGradient ONE and Open Air monitors support over the air (OTA) updates.
|
||||
From [firmware version 3.1.1](https://github.com/airgradienthq/arduino/tree/3.1.1) onwards, the AirGradient ONE and Open Air monitors support over the air (OTA) updates.
|
||||
|
||||
#### Mechanism
|
||||
|
||||
@@ -10,7 +10,7 @@ The device attempts to update to the latest version on startup and in regular in
|
||||
|
||||
http://hw.airgradient.com/sensors/{deviceId}/generic/os/firmware.bin?current_firmware={GIT_VERSION}
|
||||
|
||||
If does pass the version it is currently running on along to the server through URL parameter 'current_firmware'.
|
||||
If does pass the version it is currently running on along to the server through URL parameter 'current_firmware'.
|
||||
This allows the server to identify if the device is already running on the latest version or should update.
|
||||
|
||||
The following scenarios are possible
|
||||
|
||||
@@ -294,7 +294,7 @@ static bool sgp41Init(void) {
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
Serial.println("Init SGP41 failure");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -351,7 +351,7 @@ static bool sgp41Init(void) {
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
Serial.println("Init SGP41 failure");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -249,7 +249,7 @@ void loop() {
|
||||
configUpdateHandle();
|
||||
|
||||
localServer._handle();
|
||||
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
ag.sgp41.handle();
|
||||
}
|
||||
@@ -374,7 +374,7 @@ static bool sgp41Init(void) {
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
Serial.println("Init SGP41 failure");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -59,24 +59,24 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#include "esp_system.h"
|
||||
#include "freertos/projdefs.h"
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define WIFI_SERVER_CONFIG_SYNC_INTERVAL 1 * 60000 /** ms */
|
||||
#define WIFI_MEASUREMENT_INTERVAL 1 * 60000 /** ms */
|
||||
#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 3 * 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||
#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 LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define WIFI_SERVER_CONFIG_SYNC_INTERVAL 1 * 60000 /** ms */
|
||||
#define WIFI_MEASUREMENT_INTERVAL 1 * 60000 /** ms */
|
||||
#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 3 * 60000 /** ms */
|
||||
#define MQTT_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
#define SENSOR_TVOC_UPDATE_INTERVAL 1000 /** ms */
|
||||
#define SENSOR_CO2_UPDATE_INTERVAL 4000 /** ms */
|
||||
#define SENSOR_PM_UPDATE_INTERVAL 2000 /** ms */
|
||||
#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
|
||||
@@ -88,7 +88,7 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
#define OLED_I2C_ADDR 0x3C
|
||||
|
||||
/** Power pin */
|
||||
#define GPIO_POWER_MODULE_PIN 5
|
||||
#define GPIO_POWER_MODULE_PIN 5
|
||||
#define GPIO_EXPANSION_CARD_POWER 4
|
||||
#define GPIO_IIC_RESET 3
|
||||
|
||||
@@ -100,21 +100,15 @@ static Configuration configuration(Serial);
|
||||
static Measurements measurements(configuration);
|
||||
static AirGradient *ag;
|
||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||
configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
||||
configuration);
|
||||
static StateMachine stateMachine(oledDisplay, Serial, measurements, configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration);
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector);
|
||||
static AgSerial *agSerial;
|
||||
static CellularModule *cellularCard;
|
||||
static AirgradientClient *agClient;
|
||||
|
||||
enum NetworkOption {
|
||||
UseWifi,
|
||||
UseCellular
|
||||
};
|
||||
enum NetworkOption { UseWifi, UseCellular };
|
||||
NetworkOption networkOption;
|
||||
TaskHandle_t handleNetworkTask = NULL;
|
||||
static bool firmwareUpdateInProgress = false;
|
||||
@@ -123,7 +117,7 @@ static uint32_t factoryBtnPressTime = 0;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
||||
static bool ledBarButtonTest = false;
|
||||
static String fwNewVersion;
|
||||
static int lastCellSignalQuality = 99; // CSQ
|
||||
static int lastCellSignalQuality = 99; // CSQ
|
||||
|
||||
// Default value is 0, indicate its not started yet
|
||||
// In minutes
|
||||
@@ -136,7 +130,7 @@ static void boardInit(void);
|
||||
static void initializeNetwork();
|
||||
static void failedHandler(String msg);
|
||||
static void configurationUpdateSchedule(void);
|
||||
static void configUpdateHandle(void);
|
||||
static void configUpdateHandle(void);
|
||||
static void updateDisplayAndLedBar(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
@@ -157,13 +151,12 @@ 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 restartIfCeClientIssueOverTwoHours();
|
||||
static void networkSignalCheck();
|
||||
static void networkingTask(void *args);
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
||||
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL, configurationUpdateSchedule);
|
||||
AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer);
|
||||
AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
@@ -180,7 +173,7 @@ void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(100); /** For bester show log */
|
||||
|
||||
// Enable cullular module power board
|
||||
// Enable cullular module power board
|
||||
pinMode(GPIO_EXPANSION_CARD_POWER, OUTPUT);
|
||||
digitalWrite(GPIO_EXPANSION_CARD_POWER, HIGH);
|
||||
|
||||
@@ -226,17 +219,15 @@ void setup() {
|
||||
/** Show message confirm offline mode, should me perform if LED bar button
|
||||
* test pressed */
|
||||
if (ledBarButtonTest == false) {
|
||||
oledDisplay.setText(
|
||||
"Press now for",
|
||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
||||
oledDisplay.setText("Press now for",
|
||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
||||
uint32_t startTime = millis();
|
||||
while (true) {
|
||||
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
|
||||
configuration.setOfflineMode(!configuration.isOfflineMode());
|
||||
|
||||
oledDisplay.setText(
|
||||
"Offline Mode",
|
||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
||||
oledDisplay.setText("Offline Mode",
|
||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
||||
delay(1000);
|
||||
break;
|
||||
}
|
||||
@@ -256,12 +247,7 @@ void setup() {
|
||||
if (connectToNetwork) {
|
||||
oledDisplay.setText("Initialize", "network...", "");
|
||||
initializeNetwork();
|
||||
}
|
||||
|
||||
/** Set offline mode without saving, cause wifi is not configured */
|
||||
if (wifiConnector.hasConfigurated() == false && networkOption == UseWifi) {
|
||||
Serial.println("Set offline mode cause wifi is not configurated");
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
wifiConnector.stopBLE();
|
||||
}
|
||||
|
||||
/** Show display Warning up */
|
||||
@@ -274,7 +260,6 @@ void setup() {
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
// If using cellular re-set scheduler interval
|
||||
configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL);
|
||||
@@ -284,14 +269,14 @@ void setup() {
|
||||
// Queue now only applied for cellular
|
||||
// Allocate queue memory to avoid always reallocation
|
||||
measurementCycleQueue.reserve(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||
// Initialize mutex to access mesurementCycleQueue
|
||||
// Initialize mutex to access mesurementCycleQueue
|
||||
mutexMeasurementCycleQueue = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
// Only run network task if monitor is not in offline mode
|
||||
if (configuration.isOfflineMode() == false) {
|
||||
BaseType_t xReturned =
|
||||
xTaskCreate(networkingTask, "NetworkingTask", 4096, null, 5, &handleNetworkTask);
|
||||
xTaskCreate(networkingTask, "NetworkingTask", 4096, null, 5, &handleNetworkTask);
|
||||
if (xReturned == pdPASS) {
|
||||
Serial.println("Success create networking task");
|
||||
} else {
|
||||
@@ -302,17 +287,15 @@ void setup() {
|
||||
// Log monitor mode for debugging purpose
|
||||
if (configuration.isOfflineMode()) {
|
||||
Serial.println("Running monitor in offline mode");
|
||||
}
|
||||
else if (configuration.isCloudConnectionDisabled()) {
|
||||
} else if (configuration.isCloudConnectionDisabled()) {
|
||||
Serial.println("Running monitor without connection to AirGradient server");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (networkOption == UseCellular) {
|
||||
// Check if cellular client not ready until certain time
|
||||
// Redundant check in both task to make sure its executed
|
||||
// Redundant check in both task to make sure its executed
|
||||
restartIfCeClientIssueOverTwoHours();
|
||||
}
|
||||
|
||||
@@ -353,7 +336,7 @@ void loop() {
|
||||
static bool pmsConnected = false;
|
||||
if (pmsConnected != ag->pms5003.connected()) {
|
||||
pmsConnected = ag->pms5003.connected();
|
||||
Serial.printf("PMS sensor %s \n", pmsConnected?"connected":"removed");
|
||||
Serial.printf("PMS sensor %s \n", pmsConnected ? "connected" : "removed");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -392,9 +375,7 @@ static void co2Update(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void printMeasurements() {
|
||||
measurements.printCurrentAverage();
|
||||
}
|
||||
void printMeasurements() { measurements.printCurrentAverage(); }
|
||||
|
||||
static void mdnsInit(void) {
|
||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||
@@ -403,8 +384,7 @@ static void mdnsInit(void) {
|
||||
}
|
||||
|
||||
MDNS.addService("_airgradient", "_tcp", 80);
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model",
|
||||
AgFirmwareModeName(fwMode));
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model", AgFirmwareModeName(fwMode));
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion());
|
||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||
@@ -428,8 +408,7 @@ static void createMqttTask(void) {
|
||||
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())) {
|
||||
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||
Serial.println("MQTT sync success");
|
||||
} else {
|
||||
Serial.println("MQTT sync failure");
|
||||
@@ -447,8 +426,7 @@ static void createMqttTask(void) {
|
||||
static void initMqtt(void) {
|
||||
String mqttUri = configuration.getMqttBrokerUri();
|
||||
if (mqttUri.isEmpty()) {
|
||||
Serial.println(
|
||||
"MQTT is not configured, skipping initialization of MQTT client");
|
||||
Serial.println("MQTT is not configured, skipping initialization of MQTT client");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -509,7 +487,7 @@ static void factoryConfigReset(void) {
|
||||
Serial.println("Factory reset successful");
|
||||
}
|
||||
delay(3000);
|
||||
oledDisplay.setText("","","");
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
@@ -547,7 +525,7 @@ static void ledBarEnabledUpdate(void) {
|
||||
ag->ledBar.setBrightness(brightness);
|
||||
ag->ledBar.setEnable(configuration.getLedBarMode() != LedBarModeOff);
|
||||
}
|
||||
ag->ledBar.show();
|
||||
ag->ledBar.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,7 +537,7 @@ static bool sgp41Init(void) {
|
||||
configuration.hasSensorSGP = true;
|
||||
return true;
|
||||
} else {
|
||||
Serial.println("Init SGP41 failuire");
|
||||
Serial.println("Init SGP41 failure");
|
||||
configuration.hasSensorSGP = false;
|
||||
}
|
||||
return false;
|
||||
@@ -618,11 +596,11 @@ void otaHandlerCallback(AirgradientOTA::OtaResult result, const char *msg) {
|
||||
displayExecuteOta(result, "", std::stoi(msg));
|
||||
break;
|
||||
case AirgradientOTA::Failed:
|
||||
displayExecuteOta(result, "", 0);
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
ag->sgp41.resume();
|
||||
}
|
||||
break;
|
||||
displayExecuteOta(result, "", 0);
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
ag->sgp41.resume();
|
||||
}
|
||||
break;
|
||||
case AirgradientOTA::Skipped:
|
||||
case AirgradientOTA::AlreadyUpToDate:
|
||||
displayExecuteOta(result, "", 0);
|
||||
@@ -638,7 +616,7 @@ void otaHandlerCallback(AirgradientOTA::OtaResult result, const char *msg) {
|
||||
|
||||
static void displayExecuteOta(AirgradientOTA::OtaResult result, String msg, int processing) {
|
||||
switch (result) {
|
||||
case AirgradientOTA::Starting:
|
||||
case AirgradientOTA::Starting:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateVersion(msg);
|
||||
} else {
|
||||
@@ -707,6 +685,7 @@ static void sendDataToAg() {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||
}
|
||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting);
|
||||
wifiConnector.bleNotifyStatus(PROV_CONNECTING_TO_SERVER);
|
||||
|
||||
/** Task handle led connecting animation */
|
||||
xTaskCreate(
|
||||
@@ -714,8 +693,7 @@ static void sendDataToAg() {
|
||||
for (;;) {
|
||||
// ledSmHandler();
|
||||
stateMachine.handleLeds();
|
||||
if (stateMachine.getLedState() !=
|
||||
AgStateMachineWiFiOkServerConnecting) {
|
||||
if (stateMachine.getLedState() != AgStateMachineWiFiOkServerConnecting) {
|
||||
break;
|
||||
}
|
||||
delay(LED_BAR_ANIMATION_PERIOD);
|
||||
@@ -725,7 +703,7 @@ static void sendDataToAg() {
|
||||
"task_led", 2048, NULL, 5, NULL);
|
||||
|
||||
delay(1500);
|
||||
|
||||
|
||||
// Build payload to check connection to airgradient server
|
||||
JSONVar root;
|
||||
root["wifi"] = wifiConnector.RSSI();
|
||||
@@ -736,11 +714,13 @@ static void sendDataToAg() {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
}
|
||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected);
|
||||
wifiConnector.bleNotifyStatus(PROV_SERVER_REACHABLE);
|
||||
} else {
|
||||
if (ag->isOne()) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||
}
|
||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed);
|
||||
wifiConnector.bleNotifyStatus(PROV_ERR_SERVER_UNREACHABLE);
|
||||
}
|
||||
|
||||
stateMachine.handleLeds(AgStateMachineNormal);
|
||||
@@ -761,8 +741,7 @@ static void oneIndoorInit(void) {
|
||||
/** Show boot display */
|
||||
Serial.println("Firmware Version: " + ag->getVersion());
|
||||
|
||||
oledDisplay.setText("AirGradient ONE",
|
||||
"FW Version: ", ag->getVersion().c_str());
|
||||
oledDisplay.setText("AirGradient ONE", "FW Version: ", ag->getVersion().c_str());
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
|
||||
ag->ledBar.begin();
|
||||
@@ -793,9 +772,9 @@ static void oneIndoorInit(void) {
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
oledDisplay.setText("Configure WiFi", "connect to", "\'airgradient\'");
|
||||
delay(2500);
|
||||
oledDisplay.setText("Rebooting...", "","");
|
||||
oledDisplay.setText("Rebooting...", "", "");
|
||||
delay(2500);
|
||||
oledDisplay.setText("","","");
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
@@ -921,8 +900,7 @@ static void openAirInit(void) {
|
||||
}
|
||||
|
||||
if (fwMode == FW_MODE_O_1PP) {
|
||||
int count = (configuration.hasSensorPMS1 ? 1 : 0) +
|
||||
(configuration.hasSensorPMS2 ? 1 : 0);
|
||||
int count = (configuration.hasSensorPMS1 ? 1 : 0) + (configuration.hasSensorPMS2 ? 1 : 0);
|
||||
if (count == 1) {
|
||||
fwMode = FW_MODE_O_1P;
|
||||
}
|
||||
@@ -970,7 +948,7 @@ void initializeNetwork() {
|
||||
agSerial->init(GPIO_IIC_RESET);
|
||||
if (agSerial->open()) {
|
||||
Serial.println("Cellular module found");
|
||||
// Initialize cellular module and use cellular as agClient
|
||||
// Initialize cellular module and use cellular as agClient
|
||||
cellularCard = new CellularModuleA7672XX(agSerial, GPIO_POWER_MODULE_PIN);
|
||||
agClient = new AirgradientCellularClient(cellularCard);
|
||||
networkOption = UseCellular;
|
||||
@@ -984,7 +962,7 @@ void initializeNetwork() {
|
||||
}
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
// Enable serial stream debugging to check the AT command when doing registration
|
||||
// Enable serial stream debugging to check the AT command when doing registration
|
||||
agSerial->setDebug(true);
|
||||
}
|
||||
|
||||
@@ -1005,7 +983,7 @@ void initializeNetwork() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// Provide openmetrics to have access to last transmission result
|
||||
// Provide openmetrics to have access to last transmission result
|
||||
openMetrics.setAirgradientClient(agClient);
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
@@ -1014,30 +992,26 @@ void initializeNetwork() {
|
||||
}
|
||||
|
||||
if (networkOption == UseWifi) {
|
||||
if (!wifiConnector.connect()) {
|
||||
String modelName = AgFirmwareModeName(fwMode);
|
||||
if (!wifiConnector.connect(modelName)) {
|
||||
Serial.println("Cannot initiate wifi connection");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!wifiConnector.isConnected()) {
|
||||
Serial.println("Failed connect to WiFi");
|
||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
// Directly return because the rest of the function applied if wifi is connect only
|
||||
return;
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
|
||||
// Initiate local network configuration
|
||||
mdnsInit();
|
||||
localServer.begin();
|
||||
// Apply mqtt connection if configured
|
||||
initMqtt();
|
||||
|
||||
|
||||
// Ignore the rest if cloud connection to AirGradient is disabled
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
return;
|
||||
@@ -1064,21 +1038,22 @@ void initializeNetwork() {
|
||||
if (agClient->isRegisteredOnAgServer() == false) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
wifiConnector.bleNotifyStatus(PROV_ERR_MONITOR_NOT_REGISTERED);
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
wifiConnector.bleNotifyStatus(PROV_ERR_GET_MONITOR_CONFIG_FAILED);
|
||||
}
|
||||
}
|
||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ledBarEnabledUpdate();
|
||||
wifiConnector.bleNotifyStatus(PROV_MONITOR_CONFIGURED);
|
||||
}
|
||||
}
|
||||
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (configuration.getConfigurationControl() ==
|
||||
ConfigurationControl::ConfigurationControlLocal) {
|
||||
if (configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||
Serial.println("Ignore fetch server configuration, configurationControl set to local");
|
||||
agClient->resetFetchConfigurationStatus();
|
||||
return;
|
||||
@@ -1112,8 +1087,7 @@ static void configUpdateHandle() {
|
||||
}
|
||||
|
||||
if (configuration.hasSensorSGP) {
|
||||
if (configuration.noxLearnOffsetChanged() ||
|
||||
configuration.tvocLearnOffsetChanged()) {
|
||||
if (configuration.noxLearnOffsetChanged() || configuration.tvocLearnOffsetChanged()) {
|
||||
ag->sgp41.end();
|
||||
|
||||
int oldTvocOffset = ag->sgp41.getTvocLearningOffset();
|
||||
@@ -1124,14 +1098,12 @@ static void configUpdateHandle() {
|
||||
resultStr = "failure";
|
||||
}
|
||||
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
|
||||
oldTvocOffset, configuration.getTvocLearningOffset(),
|
||||
resultStr);
|
||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", oldTvocOffset,
|
||||
configuration.getTvocLearningOffset(), resultStr);
|
||||
}
|
||||
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
|
||||
oldNoxOffset, configuration.getNoxLearningOffset(),
|
||||
resultStr);
|
||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", oldNoxOffset,
|
||||
configuration.getNoxLearningOffset(), resultStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1153,7 +1125,7 @@ static void configUpdateHandle() {
|
||||
if (configuration.getLedBarBrightness() == 0) {
|
||||
ag->ledBar.setEnable(false);
|
||||
} else {
|
||||
if(configuration.getLedBarMode() == LedBarMode::LedBarModeOff) {
|
||||
if (configuration.getLedBarMode() == LedBarMode::LedBarModeOff) {
|
||||
ag->ledBar.setEnable(false);
|
||||
} else {
|
||||
ag->ledBar.setEnable(true);
|
||||
@@ -1191,9 +1163,8 @@ static void updateDisplayAndLedBar(void) {
|
||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
} else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
// Same action as wifi
|
||||
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||
@@ -1202,7 +1173,7 @@ static void updateDisplayAndLedBar(void) {
|
||||
}
|
||||
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
// Ignore API related check since cloud is disabled
|
||||
// Ignore API related check since cloud is disabled
|
||||
stateMachine.displayHandle(AgStateMachineNormal);
|
||||
stateMachine.handleLeds(AgStateMachineNormal);
|
||||
return;
|
||||
@@ -1390,12 +1361,12 @@ void postUsingWifi() {
|
||||
}
|
||||
|
||||
/**
|
||||
* forcePost to force post without checking transmit cycle
|
||||
*/
|
||||
* forcePost to force post without checking transmit cycle
|
||||
*/
|
||||
void postUsingCellular(bool forcePost) {
|
||||
// Aquire queue mutex to get queue size
|
||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||
|
||||
|
||||
// Make sure measurement cycle available
|
||||
int queueSize = measurementCycleQueue.size();
|
||||
if (queueSize == 0) {
|
||||
@@ -1405,7 +1376,7 @@ void postUsingCellular(bool forcePost) {
|
||||
}
|
||||
|
||||
// Check queue size if its ready to transmit
|
||||
// It is ready if size is divisible by 3
|
||||
// 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);
|
||||
@@ -1426,7 +1397,7 @@ void postUsingCellular(bool forcePost) {
|
||||
|
||||
// Attempt to send
|
||||
if (agClient->httpPostMeasures(payload) == false) {
|
||||
// Consider network has a problem, retry in next schedule
|
||||
// Consider network has a problem, retry in next schedule
|
||||
Serial.println("Post measures failed, retry in next schedule");
|
||||
return;
|
||||
}
|
||||
@@ -1527,11 +1498,10 @@ void setMeasurementMaxPeriod() {
|
||||
|
||||
int calculateMaxPeriod(int updateInterval) {
|
||||
// 0.8 is 80% reduced interval for max period
|
||||
// NOTE: Both network option use the same measurement interval
|
||||
// NOTE: Both network option use the same measurement interval
|
||||
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
}
|
||||
|
||||
|
||||
void networkSignalCheck() {
|
||||
if (networkOption == UseWifi) {
|
||||
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
|
||||
@@ -1544,7 +1514,7 @@ void networkSignalCheck() {
|
||||
}
|
||||
|
||||
// Save last signal quality
|
||||
lastCellSignalQuality = result.data;
|
||||
lastCellSignalQuality = result.data;
|
||||
|
||||
if (result.data == 99) {
|
||||
// 99 indicate cellular not attached to network
|
||||
@@ -1557,12 +1527,11 @@ void networkSignalCheck() {
|
||||
}
|
||||
|
||||
/**
|
||||
* If in 2 hours cellular client still not ready, then restart system
|
||||
*/
|
||||
* 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) {
|
||||
(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;
|
||||
@@ -1613,8 +1582,7 @@ void networkingTask(void *args) {
|
||||
delay(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (networkOption == UseCellular) {
|
||||
} else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
// Start time if value still default
|
||||
if (agCeClientProblemDetectedTime == 0) {
|
||||
@@ -1622,10 +1590,10 @@ void networkingTask(void *args) {
|
||||
}
|
||||
|
||||
// Enable at command debug
|
||||
agSerial->setDebug(true);
|
||||
agSerial->setDebug(true);
|
||||
|
||||
// Check if cellular client not ready until certain time
|
||||
// Redundant check in both task to make sure its executed
|
||||
// Redundant check in both task to make sure its executed
|
||||
restartIfCeClientIssueOverTwoHours();
|
||||
|
||||
// Power cycling cellular module due to network issues for more than 1 hour
|
||||
@@ -1652,7 +1620,7 @@ void networkingTask(void *args) {
|
||||
|
||||
// Client is ready
|
||||
agCeClientProblemDetectedTime = 0; // reset to default
|
||||
agSerial->setDebug(false); // disable at command debug
|
||||
agSerial->setDebug(false); // disable at command debug
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1676,14 +1644,14 @@ void networkingTask(void *args) {
|
||||
|
||||
void newMeasurementCycle() {
|
||||
if (xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY) == pdTRUE) {
|
||||
// Make sure queue not overflow
|
||||
// Make sure queue not overflow
|
||||
if (measurementCycleQueue.size() >= MAXIMUM_MEASUREMENT_CYCLE_QUEUE) {
|
||||
// Remove the oldest data from queue if queue reach max
|
||||
measurementCycleQueue.erase(measurementCycleQueue.begin());
|
||||
}
|
||||
|
||||
// Get current measures
|
||||
auto mc = measurements.getMeasures();
|
||||
auto mc = measurements.getMeasures();
|
||||
mc.signal = cellularCard->csqToDbm(lastCellSignalQuality); // convert to RSSI
|
||||
|
||||
measurementCycleQueue.push_back(mc);
|
||||
@@ -1694,4 +1662,3 @@ void newMeasurementCycle() {
|
||||
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) {
|
||||
this->ag = ag;
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) {
|
||||
this->ag = ag;
|
||||
}
|
||||
|
||||
void OpenMetrics::setAirgradientClient(AirgradientClient *client) {
|
||||
|
||||
@@ -15,7 +15,7 @@ framework = arduino
|
||||
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 =
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
WebServer
|
||||
@@ -26,13 +26,14 @@ lib_deps =
|
||||
WiFiClientSecure
|
||||
Update
|
||||
DNSServer
|
||||
h2zero/NimBLE-Arduino@^2.1.0
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266
|
||||
board = d1_mini
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
lib_deps =
|
||||
aglib=symlink://../arduino
|
||||
EEPROM
|
||||
ESP8266HTTPClient
|
||||
|
||||
@@ -47,7 +47,7 @@ bool AgApiClient::fetchServerConfiguration(void) {
|
||||
}
|
||||
#else
|
||||
HTTPClient client;
|
||||
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
|
||||
if (apiRootChanged) {
|
||||
// If apiRoot is changed, assume not using https
|
||||
@@ -114,7 +114,7 @@ bool AgApiClient::postToServer(String data) {
|
||||
}
|
||||
#else
|
||||
HTTPClient client;
|
||||
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||
client.setConnectTimeout(timeoutMs); // Set timeout when establishing connection to server
|
||||
client.setTimeout(timeoutMs); // Timeout when waiting for response from AG server
|
||||
if (apiRootChanged) {
|
||||
// If apiRoot is changed, assume not using https
|
||||
@@ -185,13 +185,13 @@ void AgApiClient::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
|
||||
/**
|
||||
* @brief Send the package to check the connection with cloud
|
||||
*
|
||||
*
|
||||
* @param rssi WiFi RSSI
|
||||
* @param bootCount Boot count
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool AgApiClient::sendPing(int rssi, int bootCount) {
|
||||
bool AgApiClient::sendPing(int rssi, int bootCount) {
|
||||
JSONVar root;
|
||||
root["wifi"] = rssi;
|
||||
root["boot"] = bootCount;
|
||||
|
||||
@@ -114,7 +114,7 @@ PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
|
||||
|
||||
const size_t enumSize = COR_ALGO_PM_SLR_CUSTOM + 1; // Get the actual size of the enum
|
||||
PMCorrectionAlgorithm result = COR_ALGO_PM_UNKNOWN;;
|
||||
|
||||
|
||||
// Loop through enum values
|
||||
for (size_t enumVal = 0; enumVal < enumSize; enumVal++) {
|
||||
if (algorithm == PM_CORRECTION_ALGORITHM_NAMES[enumVal]) {
|
||||
@@ -122,7 +122,7 @@ PMCorrectionAlgorithm Configuration::matchPmAlgorithm(String algorithm) {
|
||||
}
|
||||
}
|
||||
|
||||
// If string not match from enum, check if correctionAlgorithm is one of the PM batch corrections
|
||||
// If string not match from enum, check if correctionAlgorithm is one of the PM batch corrections
|
||||
if (result == COR_ALGO_PM_UNKNOWN) {
|
||||
// Check the substring "slr_PMS5003_xxxxxxxx"
|
||||
if (algorithm.substring(0, 11) == "slr_PMS5003") {
|
||||
@@ -749,7 +749,7 @@ bool Configuration::parse(String data, bool isLocal) {
|
||||
}
|
||||
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)
|
||||
// then set its value to default (empty)
|
||||
jconfig[jprop_mqttBrokerUrl] = jprop_mqttBrokerUrl_default;
|
||||
}
|
||||
else {
|
||||
@@ -1073,7 +1073,7 @@ String Configuration::getMqttBrokerUri(void) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get HTTP domain for post measures and get configuration
|
||||
* @brief Get HTTP domain for post measures and get configuration
|
||||
*
|
||||
* @return String http domain, might be empty string
|
||||
*/
|
||||
@@ -1562,13 +1562,13 @@ void Configuration::setDisableCloudConnection(bool disable) {
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
bool Configuration::isLedBarModeChanged(void) {
|
||||
bool Configuration::isLedBarModeChanged(void) {
|
||||
bool changed = _ledBarModeChanged;
|
||||
_ledBarModeChanged = false;
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool Configuration::isMonitorDisplayCompensatedValues(void) {
|
||||
bool Configuration::isMonitorDisplayCompensatedValues(void) {
|
||||
return jconfig[jprop_monitorDisplayCompensatedValues];
|
||||
}
|
||||
|
||||
@@ -1591,8 +1591,8 @@ bool Configuration::isPMCorrectionChanged(void) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if PM correction is enabled
|
||||
*
|
||||
* @brief Check if PM correction is enabled
|
||||
*
|
||||
* @return true if PM correction algorithm is not None, otherwise false
|
||||
*/
|
||||
bool Configuration::isPMCorrectionEnabled(void) {
|
||||
|
||||
@@ -19,10 +19,7 @@ static unsigned char OFFLINE_BITS[] = {
|
||||
0xE6, 0x00, 0xFE, 0x1F, 0xFE, 0x1F, 0xE6, 0x00, 0x62, 0x00,
|
||||
0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
// {
|
||||
// 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x60, 0x00, 0x62, 0x00, 0xE2, 0x00,
|
||||
// 0xFE, 0x1F, 0xFE, 0x1F, 0xE2, 0x00, 0x62, 0x00, 0x60, 0x00, 0x30, 0x00,
|
||||
// 0x00, 0x00, 0x00, 0x00, };
|
||||
|
||||
/**
|
||||
* @brief Show dashboard temperature and humdity
|
||||
*
|
||||
@@ -270,6 +267,37 @@ void OledDisplay::setText(const char *line1, const char *line2,
|
||||
}
|
||||
}
|
||||
|
||||
void OledDisplay::showWiFiProvisioning(bool firstRun, int countdown) {
|
||||
if (firstRun) {
|
||||
DISP()->clearBuffer();
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
DISP()->drawStr(1, 25, "to WiFi hotspot:");
|
||||
DISP()->drawStr(1, 40, "\"airgradient-");
|
||||
DISP()->drawStr(1, 55, (ag->deviceId() + "\"").c_str());
|
||||
}
|
||||
|
||||
// Now just update countdown area
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%ds to connect", countdown);
|
||||
DISP()->setDrawColor(0); // erase previous text
|
||||
DISP()->drawBox(0, 0, 128, 14); // clear top region
|
||||
DISP()->setDrawColor(1); // draw new text in white
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
DISP()->drawStr(1, 10, buf);
|
||||
|
||||
// Blink the BLE mark section
|
||||
if (countdown % 2 == 0) {
|
||||
DISP()->setFont(u8g2_font_t0_12b_tf);
|
||||
DISP()->drawStr(108, 60, "BLE");
|
||||
} else {
|
||||
DISP()->setDrawColor(0);
|
||||
DISP()->drawBox(108, 48, 20, 16);
|
||||
DISP()->setDrawColor(1);
|
||||
}
|
||||
|
||||
DISP()->sendBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update dashboard content
|
||||
*
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
void setText(String &line1, String &line2, String &line3, String &line4);
|
||||
void setText(const char *line1, const char *line2, const char *line3,
|
||||
const char *line4);
|
||||
void showWiFiProvisioning(bool firstRun, int countdown);
|
||||
void showDashboard(void);
|
||||
void showDashboard(DashboardStatus status);
|
||||
void setBrightness(int percent);
|
||||
|
||||
@@ -494,13 +494,10 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
if (ag->isBasic()) {
|
||||
String ssid = "\"airgradient-" + ag->deviceId() + "\" " +
|
||||
String(wifiConnectCountDown) + String("s");
|
||||
disp.setText("Connect tohotspot:", ssid.c_str(), "");
|
||||
disp.setText("Connect to hotspot:", ssid.c_str(), "");
|
||||
} else {
|
||||
String line1 = String(wifiConnectCountDown) + "s to connect";
|
||||
String line2 = "to WiFi hotspot:";
|
||||
String line3 = "\"airgradient-";
|
||||
String line4 = ag->deviceId() + "\"";
|
||||
disp.setText(line1, line2, line3, line4);
|
||||
// NOTE: This bool is hardcoded!
|
||||
disp.showWiFiProvisioning((wifiConnectCountDown == 180), wifiConnectCountDown);
|
||||
}
|
||||
wifiConnectCountDown--;
|
||||
}
|
||||
@@ -648,7 +645,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
||||
ag->ledBar.clear();
|
||||
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
||||
} else {
|
||||
ag->statusLed.setToggle();
|
||||
ag->statusLed.setStep();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -568,7 +568,7 @@ float Measurements::getAverage(MeasurementType type, int ch) {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
return measurementAverage;
|
||||
return measurementAverage;
|
||||
}
|
||||
|
||||
String Measurements::pms5003FirmwareVersion(int fwCode) {
|
||||
@@ -895,7 +895,7 @@ std::string Measurements::buildMeasuresPayload(Measures &mc) {
|
||||
|
||||
oss << ",";
|
||||
|
||||
// Temperature
|
||||
// Temperature
|
||||
if (utils::isValidTemperature(mc.temperature[0]) && utils::isValidTemperature(mc.temperature[1])) {
|
||||
float temp = (mc.temperature[0] + mc.temperature[1]) / 2.0f;
|
||||
oss << std::round(temp * 10);
|
||||
|
||||
@@ -149,7 +149,7 @@ public:
|
||||
*
|
||||
* @param type measurement type that will be retrieve
|
||||
* @param ch target type value channel
|
||||
* @return moving average value of target measurements type
|
||||
* @return moving average value of target measurements type
|
||||
*/
|
||||
float getAverage(MeasurementType type, int ch = 1);
|
||||
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "Arduino.h"
|
||||
#include "Libraries/WiFiManager/WiFiManager.h"
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
#include "WiFiType.h"
|
||||
#include "esp32-hal.h"
|
||||
|
||||
#define WIFI_CONNECT_COUNTDOWN_MAX 180
|
||||
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
||||
|
||||
|
||||
#define BLE_SERVICE_UUID "acbcfea8-e541-4c40-9bfd-17820f16c95c"
|
||||
#define BLE_CRED_CHAR_UUID "703fa252-3d2a-4da9-a05c-83b0d9cacb8e"
|
||||
#define BLE_SCAN_CHAR_UUID "467a080f-e50f-42c9-b9b2-a2ab14d82725"
|
||||
|
||||
#define BLE_CRED_BIT (1 << 0)
|
||||
#define BLE_SCAN_BIT (1 << 1)
|
||||
|
||||
#define WIFI() ((WiFiManager *)(this->wifi))
|
||||
|
||||
/**
|
||||
@@ -32,7 +44,7 @@ WifiConnector::~WifiConnector() {}
|
||||
* @return true Success
|
||||
* @return false Failure
|
||||
*/
|
||||
bool WifiConnector::connect(void) {
|
||||
bool WifiConnector::connect(String modelName) {
|
||||
if (wifi == NULL) {
|
||||
wifi = new WiFiManager();
|
||||
if (wifi == NULL) {
|
||||
@@ -61,61 +73,89 @@ bool WifiConnector::connect(void) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WIFI()->setConfigPortalBlocking(false);
|
||||
WIFI()->setConnectTimeout(15);
|
||||
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
|
||||
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
|
||||
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
||||
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Connecting to", "WiFi", "...");
|
||||
if (!WiFi.isConnected()) {
|
||||
// Erase already saved default credentials
|
||||
WiFi.disconnect(false, true);
|
||||
}
|
||||
} else {
|
||||
logInfo("Connecting to WiFi...");
|
||||
Serial.printf("Attempt connect to configured ssid: %d\n", wifiSSID.c_str());
|
||||
// WiFi.begin() already called before, it will attempt connect when wifi creds already persist
|
||||
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerStaConnecting);
|
||||
sm.displayHandle(AgStateMachineWiFiManagerStaConnecting);
|
||||
|
||||
uint32_t ledPeriod = millis();
|
||||
uint32_t startTime = millis();
|
||||
while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < 15000) {
|
||||
/** LED animations */
|
||||
if ((millis() - ledPeriod) >= 100) {
|
||||
ledPeriod = millis();
|
||||
sm.handleLeds();
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
if (!WiFi.isConnected()) {
|
||||
// WiFi not connect, show indicator.
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
||||
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
||||
delay(3000);
|
||||
}
|
||||
}
|
||||
ssid = "airgradient-" + ag->deviceId();
|
||||
|
||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
if (WiFi.isConnected()) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerStaConnected);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enable provision by both BLE and WiFi portal
|
||||
WiFiManagerParameter disableCloud("chbPostToAg", "Prevent Connection to AirGradient Server", "T",
|
||||
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
||||
WIFI()->addParameter(&disableCloud);
|
||||
WiFiManagerParameter disableCloudInfo(
|
||||
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
||||
"it if you are sure you don't want to use any AirGradient cloud "
|
||||
"features. As a result you will not receive automatic firmware updates, "
|
||||
"configuration settings from cloud and the measure data will not reach the AirGradient dashboard.</p>");
|
||||
WIFI()->addParameter(&disableCloudInfo);
|
||||
|
||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
logInfo("Wait for configure portal");
|
||||
setupProvisionByPortal(&disableCloud, &disableCloudInfo);
|
||||
|
||||
#ifdef ESP32
|
||||
// Task handle WiFi connection.
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
WifiConnector *connector = (WifiConnector *)obj;
|
||||
while (connector->_wifiConfigPortalActive()) {
|
||||
connector->_wifiProcess();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, this, 10, NULL);
|
||||
// Provision by BLE only for ESP32
|
||||
setupProvisionByBLE(modelName.c_str());
|
||||
|
||||
/** Wait for WiFi connect and show LED, display status */
|
||||
// Task handling WiFi portal
|
||||
xTaskCreate(
|
||||
[](void *obj) {
|
||||
WifiConnector *connector = (WifiConnector *)obj;
|
||||
while (connector->_wifiConfigPortalActive()) {
|
||||
if (connector->isBleClientConnected()) {
|
||||
Serial.println("Stopping portal because BLE connected");
|
||||
connector->_wifiStop();
|
||||
connector->provisionMethod = ProvisionMethod::BLE;
|
||||
break;
|
||||
}
|
||||
connector->_wifiProcess();
|
||||
vTaskDelay(1);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
},
|
||||
"wifi_cfg", 4096, this, 10, NULL);
|
||||
|
||||
|
||||
// Wait for WiFi connect and show LED, display status
|
||||
uint32_t dispPeriod = millis();
|
||||
uint32_t ledPeriod = millis();
|
||||
bool clientConnectChanged = false;
|
||||
|
||||
// By default wifi portal loops run first
|
||||
// Provision method defined when either wifi or ble client connected first
|
||||
// If wifi client connect, then ble server will be stopped
|
||||
// If ble client connect, then wifi portal will be stopped (see wifi_cfg task)
|
||||
AgStateMachineState stateOld = sm.getDisplayState();
|
||||
while (WIFI()->getConfigPortalActive()) {
|
||||
/** LED animatoin and display update content */
|
||||
/** LED animation and display update content */
|
||||
if (WiFi.isConnected() == false) {
|
||||
/** Display countdown */
|
||||
uint32_t ms;
|
||||
@@ -145,6 +185,11 @@ bool WifiConnector::connect(void) {
|
||||
clientConnectChanged = clientConnected;
|
||||
if (clientConnectChanged) {
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
if (bleServerRunning) {
|
||||
Serial.println("Stopping BLE since wifi is connected");
|
||||
stopBLE();
|
||||
provisionMethod = ProvisionMethod::WiFi;
|
||||
}
|
||||
} else {
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||
@@ -157,6 +202,74 @@ bool WifiConnector::connect(void) {
|
||||
|
||||
delay(1); // avoid watchdog timer reset.
|
||||
}
|
||||
|
||||
if (provisionMethod == ProvisionMethod::BLE) {
|
||||
disp.setText("Provision by", "BLE", "");
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
|
||||
uint32_t wdMillis = 0;
|
||||
|
||||
// Loop until the BLE client disconnected or WiFi connected
|
||||
while (isBleClientConnected() && !WiFi.isConnected()) {
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
bleEventGroup,
|
||||
BLE_SCAN_BIT | BLE_CRED_BIT,
|
||||
pdTRUE,
|
||||
pdFALSE,
|
||||
10 / portTICK_PERIOD_MS
|
||||
);
|
||||
|
||||
if (bits & BLE_CRED_BIT) {
|
||||
Serial.printf("Connecting to %s...\n", ssid.c_str());
|
||||
wifiConnecting = true;
|
||||
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerStaConnecting);
|
||||
sm.displayHandle(AgStateMachineWiFiManagerStaConnecting);
|
||||
|
||||
uint32_t startTime = millis();
|
||||
while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < 15000) {
|
||||
// Led animations
|
||||
if ((millis() - ledPeriod) >= 100) {
|
||||
ledPeriod = millis();
|
||||
sm.handleLeds();
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("Failed connect to WiFi");
|
||||
// If not connect send status through BLE while also turn led and display indicator
|
||||
WiFi.disconnect();
|
||||
wifiConnecting = false;
|
||||
bleNotifyStatus(PROV_ERR_WIFI_CONNECT_FAILED);
|
||||
|
||||
// Show failed inficator then revert back to provision mode
|
||||
sm.ledAnimationInit();
|
||||
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
||||
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
||||
delay(3000);
|
||||
sm.ledAnimationInit();
|
||||
disp.setText("Provision by", "BLE", "");
|
||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||
}
|
||||
}
|
||||
else if (bits & BLE_SCAN_BIT) {
|
||||
handleBleScanRequest();
|
||||
}
|
||||
|
||||
// Ensure watchdog fed every minute
|
||||
if ((millis() - wdMillis) >= 60000) {
|
||||
wdMillis = millis();
|
||||
ag->watchdog.reset();
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
Serial.println("Exit provision by BLE");
|
||||
}
|
||||
#else
|
||||
_wifiProcess();
|
||||
#endif
|
||||
@@ -180,6 +293,7 @@ bool WifiConnector::connect(void) {
|
||||
config.setDisableCloudConnection(result == "T");
|
||||
}
|
||||
hasPortalConfig = false;
|
||||
bleNotifyStatus(PROV_WIFI_CONNECT);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -206,6 +320,11 @@ bool WifiConnector::wifiClientConnected(void) {
|
||||
return WiFi.softAPgetStationNum() ? true : false;
|
||||
}
|
||||
|
||||
|
||||
bool WifiConnector::isBleClientConnected() {
|
||||
return bleClientConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle WiFiManage softAP setup completed callback
|
||||
*
|
||||
@@ -248,6 +367,10 @@ bool WifiConnector::_wifiConfigPortalActive(void) {
|
||||
}
|
||||
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
||||
|
||||
void WifiConnector::_wifiStop() {
|
||||
WIFI()->stopConfigPortal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process WiFiManager connection
|
||||
*
|
||||
@@ -358,7 +481,7 @@ bool WifiConnector::isConnected(void) { return WiFi.isConnected(); }
|
||||
* this method
|
||||
*
|
||||
*/
|
||||
void WifiConnector::reset(void) {
|
||||
void WifiConnector::reset(void) {
|
||||
if(this->wifi == NULL) {
|
||||
this->wifi = new WiFiManager();
|
||||
if(this->wifi == NULL){
|
||||
@@ -366,7 +489,7 @@ void WifiConnector::reset(void) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
WIFI()->resetSettings();
|
||||
WIFI()->resetSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,10 +527,336 @@ bool WifiConnector::hasConfigurated(void) {
|
||||
*/
|
||||
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
||||
|
||||
|
||||
void WifiConnector::bleNotifyStatus(int status) {
|
||||
if (!bleServerRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pServer->getConnectedCount()) {
|
||||
NimBLEService* pSvc = pServer->getServiceByUUID(BLE_SERVICE_UUID);
|
||||
if (pSvc) {
|
||||
NimBLECharacteristic* pChr = pSvc->getCharacteristic(BLE_CRED_CHAR_UUID);
|
||||
if (pChr) {
|
||||
char tosend[50];
|
||||
memset(tosend, 0, 50);
|
||||
sprintf(tosend, "{\"status\":%d}", status);
|
||||
Serial.printf("BLE Notify >> %s \n", tosend);
|
||||
pChr->setValue(String(tosend));
|
||||
pChr->notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set wifi connect to default WiFi
|
||||
*
|
||||
*
|
||||
*/
|
||||
void WifiConnector::setDefault(void) {
|
||||
WiFi.begin("airgradient", "cleanair");
|
||||
}
|
||||
|
||||
int WifiConnector::scanAndFilterWiFi(WiFiNetwork networks[], int maxResults) {
|
||||
Serial.println("Scanning for Wi-Fi networks...");
|
||||
int n = WiFi.scanNetworks(false, true); // async=false, show_hidden=true
|
||||
Serial.printf("Found %d networks\n", n);
|
||||
|
||||
const int MAX_NETWORKS = 50;
|
||||
|
||||
if (n <= 0) {
|
||||
Serial.println("No networks found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
WiFiNetwork allNetworks[MAX_NETWORKS];
|
||||
int allCount = 0;
|
||||
|
||||
// Collect valid networks (filter weak or empty SSID)
|
||||
for (int i = 0; i < n && allCount < MAX_NETWORKS; ++i) {
|
||||
String ssid = WiFi.SSID(i);
|
||||
int32_t rssi = WiFi.RSSI(i);
|
||||
bool open = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN);
|
||||
|
||||
if (ssid.length() == 0 || rssi < -75) continue;
|
||||
|
||||
allNetworks[allCount++] = {ssid, rssi, open};
|
||||
}
|
||||
|
||||
// Remove duplicates (keep the strongest)
|
||||
WiFiNetwork uniqueNetworks[MAX_NETWORKS];
|
||||
int uniqueCount = 0;
|
||||
|
||||
for (int i = 0; i < allCount; i++) {
|
||||
bool exists = false;
|
||||
for (int j = 0; j < uniqueCount; j++) {
|
||||
if (uniqueNetworks[j].ssid == allNetworks[i].ssid) {
|
||||
exists = true;
|
||||
if (allNetworks[i].rssi > uniqueNetworks[j].rssi)
|
||||
uniqueNetworks[j] = allNetworks[i]; // keep stronger one
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!exists && uniqueCount < MAX_NETWORKS) {
|
||||
uniqueNetworks[uniqueCount++] = allNetworks[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by RSSI descending (simple bubble sort for small lists)
|
||||
for (int i = 0; i < uniqueCount - 1; i++) {
|
||||
for (int j = i + 1; j < uniqueCount; j++) {
|
||||
if (uniqueNetworks[j].rssi > uniqueNetworks[i].rssi) {
|
||||
WiFiNetwork temp = uniqueNetworks[i];
|
||||
uniqueNetworks[i] = uniqueNetworks[j];
|
||||
uniqueNetworks[j] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy to output array
|
||||
int resultCount = (uniqueCount > maxResults) ? maxResults : uniqueCount;
|
||||
for (int i = 0; i < resultCount; i++) {
|
||||
networks[i] = uniqueNetworks[i];
|
||||
}
|
||||
|
||||
Serial.printf("Returning %d filtered networks\n", resultCount);
|
||||
return resultCount;
|
||||
}
|
||||
|
||||
String WifiConnector::buildPaginatedWiFiJSON(WiFiNetwork networks[], int totalFound,
|
||||
int page, int batchSize, int totalPages) {
|
||||
// Calculate start and end indices for this page
|
||||
int startIdx = (page - 1) * batchSize;
|
||||
int endIdx = startIdx + batchSize;
|
||||
if (endIdx > totalFound) {
|
||||
endIdx = totalFound;
|
||||
}
|
||||
|
||||
// Build JSON object with pagination
|
||||
JSONVar jsonRoot;
|
||||
JSONVar jsonArray;
|
||||
|
||||
for (int i = startIdx; i < endIdx; i++) {
|
||||
JSONVar obj;
|
||||
obj["s"] = networks[i].ssid;
|
||||
obj["r"] = networks[i].rssi;
|
||||
obj["o"] = networks[i].open ? 1 : 0;
|
||||
jsonArray[i - startIdx] = obj;
|
||||
}
|
||||
|
||||
jsonRoot["wifi"] = jsonArray;
|
||||
jsonRoot["page"] = page;
|
||||
jsonRoot["tpage"] = totalPages;
|
||||
jsonRoot["found"] = totalFound;
|
||||
|
||||
String jsonString = JSON.stringify(jsonRoot);
|
||||
|
||||
Serial.printf("Page %d/%d JSON: %s\n", page, totalPages, jsonString.c_str());
|
||||
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
void WifiConnector::handleBleScanRequest() {
|
||||
const int BATCH_SIZE = 3;
|
||||
const int MAX_RESULTS = 30;
|
||||
WiFiNetwork networks[MAX_RESULTS];
|
||||
|
||||
// Scan and filter networks once
|
||||
int networkCount = scanAndFilterWiFi(networks, MAX_RESULTS);
|
||||
|
||||
// Calculate total pages
|
||||
int totalFound = (networkCount + BATCH_SIZE - 1) / BATCH_SIZE;
|
||||
|
||||
NimBLEService* pSvc = pServer->getServiceByUUID(BLE_SERVICE_UUID);
|
||||
if (!pSvc) {
|
||||
Serial.println("BLE service not found");
|
||||
return;
|
||||
}
|
||||
|
||||
NimBLECharacteristic* pChr = pSvc->getCharacteristic(BLE_SCAN_CHAR_UUID);
|
||||
if (!pChr) {
|
||||
Serial.println("BLE scan characteristic not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (networkCount == 0) {
|
||||
Serial.println("No networks found to send");
|
||||
String tosend = "{\"found\":0}";
|
||||
pChr->setValue(tosend);
|
||||
pChr->notify();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send results in batches
|
||||
for (int page = 1; page <= totalFound; page++) {
|
||||
String batchJson = buildPaginatedWiFiJSON(networks, networkCount,
|
||||
page, BATCH_SIZE, totalFound);
|
||||
pChr->setValue(batchJson);
|
||||
pChr->notify();
|
||||
|
||||
Serial.printf("Sent WiFi scan page %d/%d through BLE notify\n", page, totalFound);
|
||||
|
||||
// Delay between batches (except last one)
|
||||
if (page < totalFound) {
|
||||
delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("All WiFi scan pages sent successfully");
|
||||
}
|
||||
|
||||
void WifiConnector::setupProvisionByPortal(WiFiManagerParameter *disableCloudParam, WiFiManagerParameter *disableCloudInfo) {
|
||||
WIFI()->setConfigPortalBlocking(false);
|
||||
WIFI()->setConnectTimeout(15);
|
||||
WIFI()->setTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
WIFI()->setBreakAfterConfig(true);
|
||||
|
||||
WIFI()->setAPCallback([this](WiFiManager *obj) { _wifiApCallback(); });
|
||||
WIFI()->setSaveConfigCallback([this]() { _wifiSaveConfig(); });
|
||||
WIFI()->setSaveParamsCallback([this]() { _wifiSaveParamCallback(); });
|
||||
WIFI()->setConfigPortalTimeoutCallback([this]() {_wifiTimeoutCallback();});
|
||||
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
||||
disp.setText("Connecting to", "WiFi", "...");
|
||||
} else {
|
||||
logInfo("Connecting to WiFi...");
|
||||
}
|
||||
ssid = "airgradient-" + ag->deviceId();
|
||||
|
||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
||||
|
||||
WIFI()->addParameter(disableCloudParam);
|
||||
WIFI()->addParameter(disableCloudInfo);
|
||||
|
||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
||||
|
||||
logInfo("Wait for configure portal");
|
||||
}
|
||||
|
||||
void WifiConnector::setupProvisionByBLE(const char *modelName) {
|
||||
NimBLEDevice::init("AirGradient");
|
||||
NimBLEDevice::setPower(3); /** +3db */
|
||||
|
||||
/** bonding, MITM, don't need BLE secure connections as we are using passkey pairing */
|
||||
NimBLEDevice::setSecurityAuth(false, false, true);
|
||||
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT);
|
||||
|
||||
pServer = NimBLEDevice::createServer();
|
||||
pServer->setCallbacks(new ServerCallbacks(this));
|
||||
|
||||
// Service and characteristics for device information
|
||||
NimBLEService *pServDeviceInfo = pServer->createService("180A");
|
||||
NimBLECharacteristic *pModelCharacteristic = pServDeviceInfo->createCharacteristic("2A24", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
|
||||
pModelCharacteristic->setValue(modelName);
|
||||
NimBLECharacteristic *pSerialCharacteristic = pServDeviceInfo->createCharacteristic("2A25", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
|
||||
pSerialCharacteristic->setValue(ag->deviceId().c_str());
|
||||
NimBLECharacteristic *pFwCharacteristic = pServDeviceInfo->createCharacteristic("2A26", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
|
||||
pFwCharacteristic->setValue(ag->getVersion().c_str());
|
||||
NimBLECharacteristic *pManufCharacteristic = pServDeviceInfo->createCharacteristic("2A29", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC);
|
||||
pManufCharacteristic->setValue("AirGradient");
|
||||
|
||||
// Service and characteristics for wifi provisioning
|
||||
NimBLEService *pServProvisioning = pServer->createService(BLE_SERVICE_UUID);
|
||||
auto characteristicCallback = new CharacteristicCallbacks(this);
|
||||
NimBLECharacteristic *pCredentialCharacteristic =
|
||||
pServProvisioning->createCharacteristic(BLE_CRED_CHAR_UUID,
|
||||
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC |
|
||||
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY);
|
||||
pCredentialCharacteristic->setCallbacks(characteristicCallback);
|
||||
NimBLECharacteristic *pScanCharacteristic =
|
||||
pServProvisioning->createCharacteristic(BLE_SCAN_CHAR_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY);
|
||||
pScanCharacteristic->setCallbacks(characteristicCallback);
|
||||
|
||||
// Start services
|
||||
pServProvisioning->start();
|
||||
pServDeviceInfo->start();
|
||||
|
||||
// Advertise
|
||||
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
||||
// Format advertising data
|
||||
String mdata;
|
||||
mdata += (char)0xFF;
|
||||
mdata += (char)0xFF;
|
||||
mdata += modelName;
|
||||
mdata += '#';
|
||||
mdata += ag->deviceId();
|
||||
pAdvertising->setManufacturerData(mdata.c_str());
|
||||
// Start advertise
|
||||
pAdvertising->start();
|
||||
bleServerRunning = true;
|
||||
|
||||
// Create event group
|
||||
bleEventGroup = xEventGroupCreate();
|
||||
if (bleEventGroup == NULL) {
|
||||
Serial.println("Failed to create BLE event group!");
|
||||
// This case is very unlikely
|
||||
}
|
||||
|
||||
Serial.println("Provision by BLE ready");
|
||||
}
|
||||
|
||||
void WifiConnector::stopBLE() {
|
||||
if (bleServerRunning) {
|
||||
Serial.println("Stopping BLE");
|
||||
NimBLEDevice::deinit();
|
||||
}
|
||||
bleServerRunning = false;
|
||||
}
|
||||
|
||||
//
|
||||
// BLE innerclass implementation
|
||||
//
|
||||
|
||||
WifiConnector::ServerCallbacks::ServerCallbacks(WifiConnector* parent)
|
||||
: parent(parent) {}
|
||||
|
||||
void WifiConnector::ServerCallbacks::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) {
|
||||
Serial.printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
|
||||
parent->bleClientConnected = true;
|
||||
NimBLEDevice::stopAdvertising();
|
||||
}
|
||||
|
||||
void WifiConnector::ServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
|
||||
Serial.printf("Client disconnected - start advertising\n");
|
||||
NimBLEDevice::startAdvertising();
|
||||
parent->bleClientConnected = false;
|
||||
}
|
||||
|
||||
void WifiConnector::ServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo) {
|
||||
Serial.println("\n========== PAIRING COMPLETE ==========");
|
||||
Serial.printf("Peer Address: %s\n", connInfo.getAddress().toString().c_str());
|
||||
Serial.printf("Encrypted: %s\n", connInfo.isEncrypted() ? "YES" : "NO");
|
||||
Serial.printf("Authenticated: %s\n", connInfo.isAuthenticated() ? "YES" : "NO");
|
||||
Serial.printf("Key Size: %d bits\n", connInfo.getSecKeySize() * 8);
|
||||
Serial.println("======================================\n");
|
||||
}
|
||||
|
||||
WifiConnector::CharacteristicCallbacks::CharacteristicCallbacks(WifiConnector* parent)
|
||||
: parent(parent) {}
|
||||
|
||||
void WifiConnector::CharacteristicCallbacks::onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) {
|
||||
Serial.printf("%s : onRead(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
|
||||
pCharacteristic->getValue().c_str());
|
||||
}
|
||||
|
||||
void WifiConnector::CharacteristicCallbacks::onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) {
|
||||
Serial.printf("%s : onWrite(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
|
||||
pCharacteristic->getValue().c_str());
|
||||
|
||||
auto bleCred = NimBLEUUID(BLE_CRED_CHAR_UUID);
|
||||
if (pCharacteristic->getUUID().equals(bleCred)) {
|
||||
if (!parent->wifiConnecting) {
|
||||
JSONVar root = JSON.parse(pCharacteristic->getValue().c_str());
|
||||
|
||||
String ssid = root["ssid"];
|
||||
String pass = root["password"];
|
||||
|
||||
WiFi.begin(ssid.c_str(), pass.c_str());
|
||||
xEventGroupSetBits(parent->bleEventGroup, BLE_CRED_BIT);
|
||||
}
|
||||
} else {
|
||||
xEventGroupSetBits(parent->bleEventGroup, BLE_SCAN_BIT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,49 @@
|
||||
#include "AgStateMachine.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "Libraries/WiFiManager/WiFiManager.h"
|
||||
#include "Main/PrintLog.h"
|
||||
#include "NimBLECharacteristic.h"
|
||||
#include "NimBLEService.h"
|
||||
#include "esp32-hal.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <NimBLEDevice.h>
|
||||
|
||||
// Provisioning Status Codes
|
||||
#define PROV_WIFI_CONNECT 0 // WiFi Connect
|
||||
#define PROV_CONNECTING_TO_SERVER 1 // Connecting to server
|
||||
#define PROV_SERVER_REACHABLE 2 // Server reachable
|
||||
#define PROV_MONITOR_CONFIGURED 3 // Monitor configured properly on dashboard
|
||||
|
||||
// Provisioning Error Codes
|
||||
#define PROV_ERR_WIFI_CONNECT_FAILED 10 // Failed to connect to WiFi
|
||||
#define PROV_ERR_SERVER_UNREACHABLE 11 // Server unreachable
|
||||
#define PROV_ERR_GET_MONITOR_CONFIG_FAILED 12 // Failed to get monitor configuration from dashboard
|
||||
#define PROV_ERR_MONITOR_NOT_REGISTERED 13 // Monitor is not registered on dashboard
|
||||
|
||||
class WifiConnector : public PrintLog {
|
||||
public:
|
||||
enum class ProvisionMethod {
|
||||
Unknown = 0,
|
||||
WiFi,
|
||||
BLE
|
||||
};
|
||||
|
||||
struct WiFiNetwork {
|
||||
String ssid;
|
||||
int32_t rssi;
|
||||
bool open;
|
||||
};
|
||||
|
||||
private:
|
||||
AirGradient *ag;
|
||||
OledDisplay &disp;
|
||||
StateMachine &sm;
|
||||
Configuration &config;
|
||||
NimBLEServer *pServer;
|
||||
|
||||
EventGroupHandle_t bleEventGroup;
|
||||
|
||||
String ssid;
|
||||
void *wifi = NULL;
|
||||
@@ -22,16 +55,51 @@ private:
|
||||
uint32_t lastRetry;
|
||||
bool hasPortalConfig = false;
|
||||
bool connectorTimeout = false;
|
||||
bool bleServerRunning = false;
|
||||
bool bleClientConnected = false;
|
||||
bool wifiConnecting = false;
|
||||
ProvisionMethod provisionMethod = ProvisionMethod::Unknown;
|
||||
|
||||
bool wifiClientConnected(void);
|
||||
bool isBleClientConnected();
|
||||
int scanAndFilterWiFi(WiFiNetwork networks[], int maxResults);
|
||||
String buildPaginatedWiFiJSON(WiFiNetwork networks[], int totalCount,
|
||||
int page, int batchSize, int totalPages);
|
||||
void handleBleScanRequest();
|
||||
|
||||
// BLE server handler
|
||||
class ServerCallbacks : public NimBLEServerCallbacks {
|
||||
public:
|
||||
explicit ServerCallbacks(WifiConnector *parent);
|
||||
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override;
|
||||
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override;
|
||||
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override;
|
||||
|
||||
private:
|
||||
WifiConnector *parent;
|
||||
};
|
||||
|
||||
// BLE Characteristics handler
|
||||
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks {
|
||||
public:
|
||||
explicit CharacteristicCallbacks(WifiConnector *parent);
|
||||
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override;
|
||||
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override;
|
||||
private:
|
||||
WifiConnector *parent;
|
||||
};
|
||||
|
||||
|
||||
public:
|
||||
void setAirGradient(AirGradient *ag);
|
||||
|
||||
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config);
|
||||
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration &config);
|
||||
~WifiConnector();
|
||||
|
||||
bool connect(void);
|
||||
void setupProvisionByPortal(WiFiManagerParameter *disableCloudParam, WiFiManagerParameter *disableCloudInfo);
|
||||
void setupProvisionByBLE(const char *modelName);
|
||||
void stopBLE();
|
||||
bool connect(String modelName = "");
|
||||
void disconnect(void);
|
||||
void handle(void);
|
||||
void _wifiApCallback(void);
|
||||
@@ -39,6 +107,7 @@ public:
|
||||
void _wifiSaveParamCallback(void);
|
||||
bool _wifiConfigPortalActive(void);
|
||||
void _wifiTimeoutCallback(void);
|
||||
void _wifiStop();
|
||||
void _wifiProcess();
|
||||
bool isConnected(void);
|
||||
void reset(void);
|
||||
@@ -47,8 +116,11 @@ public:
|
||||
bool hasConfigurated(void);
|
||||
bool isConfigurePorttalTimeout(void);
|
||||
|
||||
const char* defaultSsid = "airgradient";
|
||||
const char* defaultPassword = "cleanair";
|
||||
|
||||
void bleNotifyStatus(int status);
|
||||
|
||||
const char *defaultSsid = "airgradient";
|
||||
const char *defaultPassword = "cleanair";
|
||||
void setDefault(void);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "AirGradient.h"
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#else
|
||||
#else
|
||||
#include "WiFi.h"
|
||||
#endif
|
||||
|
||||
@@ -57,7 +57,7 @@ String AirGradient::getBoardName(void) {
|
||||
|
||||
/**
|
||||
* @brief Board Type is ONE_INDOOR
|
||||
*
|
||||
*
|
||||
* @return true ONE_INDOOR
|
||||
* @return false Other
|
||||
*/
|
||||
@@ -65,15 +65,15 @@ bool AirGradient::isOne(void) {
|
||||
return boardType == BoardType::ONE_INDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isOpenAir(void) {
|
||||
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||
bool AirGradient::isOpenAir(void) {
|
||||
return boardType == BoardType::OPEN_AIR_OUTDOOR;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro4_2(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V4_2;
|
||||
}
|
||||
|
||||
bool AirGradient::isPro3_3(void) {
|
||||
bool AirGradient::isPro3_3(void) {
|
||||
return boardType == BoardType::DIY_PRO_INDOOR_V3_3;
|
||||
}
|
||||
|
||||
|
||||
@@ -177,9 +177,9 @@ public:
|
||||
|
||||
/**
|
||||
* @brief Check that Airgradient object is OPEN_AIR
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool isOpenAir(void);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ enum BoardType {
|
||||
|
||||
/**
|
||||
* @brief Board definitions
|
||||
*
|
||||
*
|
||||
*/
|
||||
struct BoardDef {
|
||||
/** Board Support CO2 SenseS8 */
|
||||
|
||||
@@ -72,6 +72,36 @@ void StatusLed::setToggle(void) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void StatusLed::setStep(void) {
|
||||
static uint8_t step = 0;
|
||||
|
||||
// Pattern definition
|
||||
const bool pattern[] = {
|
||||
true, // 0: ON
|
||||
false, // 1: OFF
|
||||
true, // 2: ON
|
||||
false, // 3: OFF
|
||||
false, // 4: OFF
|
||||
false, // 5: OFF
|
||||
false, // 6: OFF
|
||||
false, // 7: OFF
|
||||
false, // 8: OFF
|
||||
false // 9: OFF
|
||||
};
|
||||
|
||||
if (pattern[step]) {
|
||||
this->setOn();
|
||||
} else {
|
||||
this->setOff();
|
||||
}
|
||||
|
||||
step++;
|
||||
if (step >= sizeof(pattern)) {
|
||||
step = 0; // restart pattern
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get current LED state
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
/**
|
||||
* @brief The class define how to handle the LED
|
||||
*
|
||||
*
|
||||
*/
|
||||
class StatusLed {
|
||||
public:
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
void setOn(void);
|
||||
void setOff(void);
|
||||
void setToggle(void);
|
||||
void setStep(void);
|
||||
State getState(void);
|
||||
String toString(StatusLed::State state);
|
||||
|
||||
|
||||
@@ -316,10 +316,10 @@ int PMSBase::pm25ToAQI(int pm02) {
|
||||
|
||||
|
||||
/**
|
||||
* @brief SLR correction for PM2.5
|
||||
*
|
||||
* @brief SLR correction for PM2.5
|
||||
*
|
||||
* Reference: https://www.airgradient.com/blog/low-readings-from-pms5003/
|
||||
*
|
||||
*
|
||||
* @param pm25 PM2.5 raw value
|
||||
* @param pm003Count PM0.3 count
|
||||
* @param scalingFactor Scaling factor
|
||||
|
||||
@@ -35,7 +35,7 @@ public:
|
||||
/** For PMS5003T*/
|
||||
int16_t getTemp(void);
|
||||
uint16_t getHum(void);
|
||||
uint8_t getFirmwareVersion(void);
|
||||
uint8_t getFirmwareVersion(void);
|
||||
uint8_t getErrorCode(void);
|
||||
|
||||
int pm25ToAQI(int pm02);
|
||||
|
||||
@@ -189,21 +189,21 @@ float PMS5003::compensate(float pm25, float humidity) { return pms.compensate(pm
|
||||
|
||||
/**
|
||||
* @brief Get sensor firmware version
|
||||
*
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int PMS5003::getFirmwareVersion(void) { return _ver; }
|
||||
|
||||
/**
|
||||
* @brief Get sensor error code
|
||||
*
|
||||
* @return uint8_t
|
||||
*
|
||||
* @return uint8_t
|
||||
*/
|
||||
uint8_t PMS5003::getErrorCode(void) { return pms.getErrorCode(); }
|
||||
|
||||
/**
|
||||
* @brief Is sensor connect with device
|
||||
*
|
||||
*
|
||||
* @return true Connected
|
||||
* @return false Removed
|
||||
*/
|
||||
@@ -255,14 +255,14 @@ void PMS5003::resetFailCount(void) {
|
||||
|
||||
/**
|
||||
* @brief Get number of fail count
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int PMS5003::getFailCount(void) { return pms.getFailCount(); }
|
||||
|
||||
/**
|
||||
* @brief Get number of fail count max
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int PMS5003::getFailCountMax(void) { return pms.getFailCountMax(); }
|
||||
|
||||
@@ -218,21 +218,21 @@ float PMS5003T::compensate(float pm25, float humidity) { return pms.compensate(p
|
||||
|
||||
/**
|
||||
* @brief Get module(s) firmware version
|
||||
*
|
||||
*
|
||||
* @return int Version code
|
||||
*/
|
||||
int PMS5003T::getFirmwareVersion(void) { return _ver; }
|
||||
|
||||
/**
|
||||
* @brief Get sensor error code
|
||||
*
|
||||
* @return uint8_t
|
||||
*
|
||||
* @return uint8_t
|
||||
*/
|
||||
uint8_t PMS5003T::getErrorCode(void) { return pms.getErrorCode(); }
|
||||
|
||||
/**
|
||||
* @brief Is sensor connect to device
|
||||
*
|
||||
*
|
||||
* @return true Connected
|
||||
* @return false Removed
|
||||
*/
|
||||
@@ -281,14 +281,14 @@ void PMS5003T::resetFailCount(void) {
|
||||
|
||||
/**
|
||||
* @brief Get fail count
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int PMS5003T::getFailCount(void) { return pms.getFailCount(); }
|
||||
|
||||
/**
|
||||
* @brief Get fail count max
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int PMS5003T::getFailCountMax(void) { return pms.getFailCountMax(); }
|
||||
|
||||
@@ -6,11 +6,11 @@ PMS5003TBase::~PMS5003TBase() {}
|
||||
|
||||
/**
|
||||
* @brief Compensate the temperature
|
||||
*
|
||||
*
|
||||
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||
*
|
||||
* @param temp
|
||||
* @return * float
|
||||
*
|
||||
* @param temp
|
||||
* @return * float
|
||||
*/
|
||||
float PMS5003TBase::compensateTemp(float temp) {
|
||||
if (temp < 10.0f) {
|
||||
@@ -21,11 +21,11 @@ float PMS5003TBase::compensateTemp(float temp) {
|
||||
|
||||
/**
|
||||
* @brief Compensate the humidity
|
||||
*
|
||||
*
|
||||
* Reference formula: https://www.airgradient.com/documentation/correction-algorithms/
|
||||
*
|
||||
* @param temp
|
||||
* @return * float
|
||||
*
|
||||
* @param temp
|
||||
* @return * float
|
||||
*/
|
||||
float PMS5003TBase::compensateHum(float hum) {
|
||||
hum = hum * 1.259f + 7.34f;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
class PMS5003TBase
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
public:
|
||||
PMS5003TBase();
|
||||
~PMS5003TBase();
|
||||
|
||||
@@ -140,7 +140,7 @@ void Sgp41::pause() {
|
||||
tvoc = utils::getInvalidVOC();
|
||||
noxRaw = utils::getInvalidNOx();
|
||||
nox = utils::getInvalidNOx();
|
||||
}
|
||||
}
|
||||
|
||||
void Sgp41::resume() {
|
||||
onPause = false;
|
||||
|
||||
Reference in New Issue
Block a user