Compare commits
56 Commits
feat/updat
...
3.3.0
Author | SHA1 | Date | |
---|---|---|---|
baf80ce250 | |||
80100e2475 | |||
d9c3fc6ec4 | |||
67d377a514 | |||
fff982f35f | |||
86cd90b94a | |||
656509c74d | |||
01f83cb02e | |||
5c9c25c6b5 | |||
9291598209 | |||
429adb5e5e | |||
4e651afc8c | |||
859abbe177 | |||
f079bb30d2 | |||
070a103234 | |||
ef87cde9d6 | |||
ea5e23b307 | |||
c2a26e78a0 | |||
0297059e91 | |||
30622fca99 | |||
7c2aa35e4f | |||
e93009f31c | |||
26db6372cd | |||
d94ebbc570 | |||
299234ac40 | |||
76b2b3f940 | |||
bf09b746c7 | |||
b5c67cb0b1 | |||
5f40a327b3 | |||
66b0c63de5 | |||
cc3228f49a | |||
8728589ca1 | |||
4b356920c2 | |||
c94b886360 | |||
e056e44917 | |||
3b00fa69b8 | |||
e0720ac580 | |||
0861c2dcaa | |||
59fc0c409b | |||
6d63fdf643 | |||
033358e2c2 | |||
47034f62b4 | |||
71a21ce7e6 | |||
3f5e5eebbb | |||
f9be400a5d | |||
54808ac076 | |||
063bb2a227 | |||
93f79173b2 | |||
615c2389e7 | |||
f972637cca | |||
a50e1e2472 | |||
92e74feabd | |||
56809a412c | |||
6a83743e2a | |||
faaf051e39 | |||
280ea5e997 |
52
.github/workflows/check.yml
vendored
@ -17,11 +17,14 @@ jobs:
|
||||
- "esp32:esp32:esp32c3"
|
||||
include:
|
||||
- fqbn: "esp8266:esp8266:d1_mini"
|
||||
core: "esp8266:esp8266@3.1.2"
|
||||
core: "esp8266:esp8266"
|
||||
core_version: "3.1.2"
|
||||
core_url: "https://arduino.esp8266.com/stable/package_esp8266com_index.json"
|
||||
- fqbn: "esp32:esp32:esp32c3"
|
||||
core: "esp32:esp32"
|
||||
core_version: "2.0.17"
|
||||
core_url: "https://espressif.github.io/arduino-esp32/package_esp32_index.json"
|
||||
board_options: "JTAGAdapter=default,CDCOnBoot=cdc,PartitionScheme=min_spiffs,CPUFreq=160,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=verbose,EraseFlash=none"
|
||||
core: "esp32:esp32@2.0.11"
|
||||
exclude:
|
||||
- example: "BASIC"
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
@ -31,30 +34,29 @@ jobs:
|
||||
fqbn: "esp32:esp32:esp32c3"
|
||||
- example: "OneOpenAir"
|
||||
fqbn: "esp8266:esp8266:d1_mini"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run:
|
||||
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh |
|
||||
sh -s 0.35.3
|
||||
- run: bin/arduino-cli --verbose core install '${{ matrix.core }}'
|
||||
--additional-urls '${{ matrix.core_url }}'
|
||||
- run: bin/arduino-cli --verbose lib install
|
||||
WiFiManager@2.0.16-rc.2
|
||||
Arduino_JSON@0.2.0
|
||||
U8g2@2.34.22
|
||||
# In some cases, actions/checkout@v4 will check out a detached HEAD; for
|
||||
# example, this happens on pull request events, where an hypothetical
|
||||
# PR merge commit is checked out. This tends to confuse
|
||||
# `arduino-cli lib install --git-url`, making it fail with errors such as:
|
||||
# Error installing Git Library: Library install failed: object not found
|
||||
# Create and check out a dummy branch to work around this issue.
|
||||
- run: git checkout -b check
|
||||
- run: bin/arduino-cli --verbose lib install --git-url .
|
||||
env:
|
||||
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL: "true"
|
||||
- run: bin/arduino-cli --verbose compile 'examples/${{ matrix.example }}'
|
||||
--fqbn '${{ matrix.fqbn }}' --board-options '${{ matrix.board_options }}'
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: 'true'
|
||||
- uses: arduino/compile-sketches@v1.1.2
|
||||
with:
|
||||
fqbn: ${{ matrix.fqbn }}
|
||||
sketch-paths: |
|
||||
examples/${{ matrix.example }}
|
||||
libraries: |
|
||||
- source-path: ./
|
||||
cli-compile-flags: |
|
||||
- --warnings
|
||||
- none
|
||||
- --board-options
|
||||
- "${{ matrix.board_options }}"
|
||||
platforms: |
|
||||
- name: ${{ matrix.core }}
|
||||
version: ${{ matrix.core_version}}
|
||||
source-url: ${{ matrix.core_url }}
|
||||
enable-deltas-report: true
|
||||
|
||||
# TODO: at this point it would be a good idea to run some smoke tests on
|
||||
# the resulting image (e.g. that it boots successfully and sends metrics)
|
||||
# but that would either require a high fidelity device emulator, or a
|
||||
|
3
.gitignore
vendored
@ -3,3 +3,6 @@ build
|
||||
.vscode
|
||||
/.idea/
|
||||
.pio
|
||||
.cache
|
||||
logs
|
||||
compile_commands.json
|
||||
|
6
.gitmodules
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
[submodule "src/Libraries/airgradient-client"]
|
||||
path = src/Libraries/airgradient-client
|
||||
url = git@github.com:airgradienthq/airgradient-client.git
|
||||
[submodule "src/Libraries/airgradient-ota"]
|
||||
path = src/Libraries/airgradient-ota
|
||||
url = git@github.com:airgradienthq/airgradient-ota.git
|
105
docs/howto-compile.md
Normal file
@ -0,0 +1,105 @@
|
||||
# How to compile AirGradient firmware on Arduino IDE
|
||||
|
||||
## Prequisite
|
||||
|
||||
Arduino IDE version 2.x ([download](https://www.arduino.cc/en/software))
|
||||
|
||||
> For AirGradient model ONE and Open Air, the codebase **WILL NOT** work on the latest major version of arduino-esp32 which is *3.x* . This related to when installing "esp32 by Espressif Systems" in board manager. Instead use version **2.0.17**, please follow the first step carefully.
|
||||
|
||||
## Steps for ESP32C3 based board (ONE and Open Air Model)
|
||||
|
||||
1. Install "esp32 by Espressif Systems" in board manager with version **2.0.17** (Tools ➝ Board ➝ Boards Manager ➝ search for `"espressif"`)
|
||||
|
||||

|
||||
|
||||
2. Install AirGradient library
|
||||
|
||||
#### Version < 3.2.0
|
||||
|
||||
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
|
||||
|
||||
3. On tools tab, follow settings below
|
||||
|
||||
```
|
||||
Board ➝ ESP32C3 Dev Module
|
||||
USB CDC On Boot ➝ Enabled
|
||||
CPU Frequency ➝ 160MHz (WiFi)
|
||||
Core Debug Level ➝ Info
|
||||
Erase All Flash Before Sketch Upload ➝ Enabled (or choose as needed)
|
||||
Flash Frequency ➝ 80MHz
|
||||
Flash Mode ➝ QIO
|
||||
Flash Size ➝ 4MB (32Mb)
|
||||
JTAG Adapter ➝ Disabled
|
||||
Partition Scheme ➝ Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
|
||||
Upload Speed ➝ 921600
|
||||
```
|
||||
|
||||
4. Open sketch to compile (File ➝ Examples ➝ AirGradient Air Quality Sensor ➝ OneOpenAir). This sketch for AirGradient ONE and Open Air monitor model
|
||||
5. Compile
|
||||
|
||||

|
||||
|
||||
## Steps for ESP8266 based board (DIY model)
|
||||
|
||||
1. Add esp8266 board by adding http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manager URLs field (File ➝ Preferences ➝ Additional boards manager URLs)
|
||||
|
||||

|
||||
|
||||
2. Install esp8266 board on board manager with version **3.1.2** (Tools ➝ Board ➝ Boards Manager ➝ search for `"esp8266"`)
|
||||
|
||||

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

|
||||
|
||||
4. On tools tab, set board to `LOLIN(WEMOS) D1 R2 & mini`, and let other settings to default
|
||||
|
||||

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

|
||||
|
||||
## Possible Issues
|
||||
|
||||
### Linux (Debian)
|
||||
|
||||
ModuleNotFoundError: No module named ‘serial’
|
||||
|
||||

|
||||
|
||||
Make sure python pyserial module installed globally in the environment by executing:
|
||||
|
||||
`$ sudo apt install -y python3-pyserial`
|
||||
|
||||
or
|
||||
|
||||
`$ pip install pyserial`
|
||||
|
||||
Choose based on how python installed on your machine. But most user, using `apt` is better.
|
||||
|
||||
## How to contribute
|
||||
|
||||
The instructions above are the instructions for how to build an official release of the AirGradient firmware using the Arduino IDE. If you intend to make changes that will you intent to contribute back to the main project, instead of installing the AirGradient library, check out the repo at `Documents/Arduino/libraries` (for Windows and Mac), or `~/Arduino/Libraries` (Linux). If you installed the library, you can remove it from the library manager in the Arduino IDE, or just delete the directory.
|
||||
|
||||
**NOTE:** When cloning the repository, for version >= 3.3.0 it has submodule, please use `--recursive` flag like this: `git clone --recursive https://github.com/airgradienthq/arduino.git AirGradient_Air_Quality_Sensor`
|
||||
|
||||
Please follow github [contributing to a project](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) tutorial to contribute to this project.
|
||||
|
||||
There are 2 environment options to compile this project, PlatformIO and ArduinoIDE.
|
||||
|
||||
- For PlatformIO, it should work out of the box
|
||||
- For arduino, files in `src` folder and also from `Examples` can be modified at `Documents/Arduino/libraries` for Windows and Mac, and `~/Arduino/Libraries` for Linux
|
||||
|
||||
|
||||
|
BIN
docs/images/additional-board.png
Normal file
After Width: | Height: | Size: 50 KiB |
BIN
docs/images/ag-lib.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
docs/images/compiled-esp8266.png
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
docs/images/compiled.png
Normal file
After Width: | Height: | Size: 131 KiB |
BIN
docs/images/esp32-board.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/images/esp8266-board.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
docs/images/linux-failed.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/images/settings-esp8266.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
docs/images/settings.png
Normal file
After Width: | Height: | Size: 106 KiB |
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
Compile Instructions:
|
||||
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
Compile Instructions:
|
||||
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
@ -12,10 +12,8 @@ Outdoor Monitor: https://www.airgradient.com/outdoor/
|
||||
Build Instructions:
|
||||
https://www.airgradient.com/documentation/diy-v4/
|
||||
|
||||
Please make sure you have esp8266 board manager installed. Tested with
|
||||
version 3.1.2.
|
||||
|
||||
Set board to "LOLIN(WEMOS) D1 R2 & mini"
|
||||
Compile Instructions:
|
||||
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
|
@ -14,17 +14,8 @@ https://www.airgradient.com/documentation/one-v9/ Build Instructions:
|
||||
AirGradient Open Air:
|
||||
https://www.airgradient.com/documentation/open-air-pst-kit-1-3/
|
||||
|
||||
Please make sure you have esp32 board manager installed. Tested with
|
||||
version 2.0.11.
|
||||
|
||||
Important flashing settings:
|
||||
- Set board to "ESP32C3 Dev Module"
|
||||
- Enable "USB CDC On Boot"
|
||||
- Flash frequency "80Mhz"
|
||||
- Flash mode "QIO"
|
||||
- Flash size "4MB"
|
||||
- Partition scheme "Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)"
|
||||
- JTAG adapter "Disabled"
|
||||
Compile Instructions:
|
||||
https://github.com/airgradienthq/arduino/blob/master/docs/howto-compile.md
|
||||
|
||||
Configuration parameters, e.g. Celsius / Fahrenheit or PM unit (US AQI vs ug/m3)
|
||||
can be set through the AirGradient dashboard.
|
||||
@ -36,46 +27,70 @@ CC BY-SA 4.0 Attribution-ShareAlike 4.0 International License
|
||||
|
||||
*/
|
||||
|
||||
#include "AgApiClient.h"
|
||||
#include "AgConfigure.h"
|
||||
#include "AgSchedule.h"
|
||||
#include "AgStateMachine.h"
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "App/AppDef.h"
|
||||
#include "Arduino.h"
|
||||
#include "EEPROM.h"
|
||||
#include "ESPmDNS.h"
|
||||
#include "LocalServer.h"
|
||||
#include "MqttClient.h"
|
||||
#include "OpenMetrics.h"
|
||||
#include "OtaHandler.h"
|
||||
#include "WebServer.h"
|
||||
#include "esp32c3/rom/rtc.h"
|
||||
#include <HardwareSerial.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
#include <string>
|
||||
|
||||
#define LED_BAR_ANIMATION_PERIOD 100 /** ms */
|
||||
#define DISP_UPDATE_INTERVAL 2500 /** ms */
|
||||
#define SERVER_CONFIG_SYNC_INTERVAL 60000 /** ms */
|
||||
#define SERVER_SYNC_INTERVAL 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 */
|
||||
#include "Libraries/airgradient-client/src/agSerial.h"
|
||||
#include "Libraries/airgradient-client/src/cellularModule.h"
|
||||
#include "Libraries/airgradient-client/src/cellularModuleA7672xx.h"
|
||||
#include "Libraries/airgradient-client/src/airgradientCellularClient.h"
|
||||
#include "Libraries/airgradient-client/src/airgradientWifiClient.h"
|
||||
#include "Libraries/airgradient-ota/src/airgradientOta.h"
|
||||
#include "Libraries/airgradient-ota/src/airgradientOtaWifi.h"
|
||||
#include "Libraries/airgradient-ota/src/airgradientOtaCellular.h"
|
||||
#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 9 * 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 MAXIMUM_MEASUREMENT_CYCLE_QUEUE 80
|
||||
#define RESERVED_MEASUREMENT_CYCLE_CAPACITY 10
|
||||
|
||||
/** I2C define */
|
||||
#define I2C_SDA_PIN 7
|
||||
#define I2C_SCL_PIN 6
|
||||
#define OLED_I2C_ADDR 0x3C
|
||||
|
||||
/** Power pin */
|
||||
#define GPIO_POWER_MODULE_PIN 5
|
||||
#define GPIO_EXPANSION_CARD_POWER 4
|
||||
#define GPIO_IIC_RESET 3
|
||||
|
||||
static MqttClient mqttClient(Serial);
|
||||
static TaskHandle_t mqttTask = NULL;
|
||||
static Configuration configuration(Serial);
|
||||
static AgApiClient apiClient(Serial, configuration);
|
||||
static Measurements measurements(configuration);
|
||||
static AirGradient *ag;
|
||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||
@ -83,22 +98,34 @@ static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
||||
configuration);
|
||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
||||
configuration);
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector,
|
||||
apiClient);
|
||||
static OtaHandler otaHandler;
|
||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
|
||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
||||
wifiConnector);
|
||||
static AgSerial *agSerial;
|
||||
static CellularModule *cell;
|
||||
static AirgradientClient *agClient;
|
||||
|
||||
enum NetworkOption {
|
||||
UseWifi,
|
||||
UseCellular
|
||||
};
|
||||
NetworkOption networkOption;
|
||||
TaskHandle_t handleNetworkTask = NULL;
|
||||
static bool otaInProgress = false;
|
||||
|
||||
static uint32_t factoryBtnPressTime = 0;
|
||||
static AgFirmwareMode fwMode = FW_MODE_I_9PSL;
|
||||
|
||||
static bool ledBarButtonTest = false;
|
||||
static String fwNewVersion;
|
||||
|
||||
SemaphoreHandle_t mutexMeasurementCycleQueue;
|
||||
static std::vector<Measurements::Measures> measurementCycleQueue;
|
||||
|
||||
static void boardInit(void);
|
||||
static void initializeNetwork(void);
|
||||
static void initializeNetwork();
|
||||
static void failedHandler(String msg);
|
||||
static void configurationUpdateSchedule(void);
|
||||
static void configUpdateHandle(void);
|
||||
static void updateDisplayAndLedBar(void);
|
||||
static void updateTvoc(void);
|
||||
static void updatePm(void);
|
||||
@ -113,27 +140,36 @@ static void wdgFeedUpdate(void);
|
||||
static void ledBarEnabledUpdate(void);
|
||||
static bool sgp41Init(void);
|
||||
static void checkForFirmwareUpdate(void);
|
||||
static void otaHandlerCallback(OtaHandler::OtaState state, String mesasge);
|
||||
static void displayExecuteOta(OtaHandler::OtaState state, String msg, int processing);
|
||||
static void otaHandlerCallback(AirgradientOTA::OtaResult result, const char *msg);
|
||||
static void displayExecuteOta(AirgradientOTA::OtaResult result, String msg, int processing);
|
||||
static int calculateMaxPeriod(int updateInterval);
|
||||
static void setMeasurementMaxPeriod();
|
||||
static void newMeasurementCycle();
|
||||
static void networkSignalCheck();
|
||||
static void networkingTask(void *args);
|
||||
|
||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
||||
AgSchedule configSchedule(SERVER_CONFIG_SYNC_INTERVAL,
|
||||
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL,
|
||||
configurationUpdateSchedule);
|
||||
AgSchedule agApiPostSchedule(SERVER_SYNC_INTERVAL, sendDataToServer);
|
||||
AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer);
|
||||
AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle);
|
||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||
AgSchedule pmsSchedule(SENSOR_PM_UPDATE_INTERVAL, updatePm);
|
||||
AgSchedule tempHumSchedule(SENSOR_TEMP_HUM_UPDATE_INTERVAL, tempHumUpdate);
|
||||
AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmwareUpdate);
|
||||
AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck);
|
||||
|
||||
void setup() {
|
||||
/** Serial for print debug message */
|
||||
Serial.begin(115200);
|
||||
delay(100); /** For bester show log */
|
||||
|
||||
// Enable cullular module power board
|
||||
pinMode(GPIO_EXPANSION_CARD_POWER, OUTPUT);
|
||||
digitalWrite(GPIO_EXPANSION_CARD_POWER, HIGH);
|
||||
|
||||
/** Print device ID into log */
|
||||
Serial.println("Serial nr: " + ag->deviceId());
|
||||
|
||||
@ -162,14 +198,10 @@ void setup() {
|
||||
oledDisplay.setAirGradient(ag);
|
||||
stateMachine.setAirGradient(ag);
|
||||
wifiConnector.setAirGradient(ag);
|
||||
apiClient.setAirGradient(ag);
|
||||
openMetrics.setAirGradient(ag);
|
||||
openMetrics.setAirGradient(ag, agClient);
|
||||
localServer.setAirGraident(ag);
|
||||
measurements.setAirGradient(ag);
|
||||
|
||||
/** Example set custom API root URL */
|
||||
// apiClient.setApiRoot("https://example.custom.api");
|
||||
|
||||
/** Init sensor */
|
||||
boardInit();
|
||||
setMeasurementMaxPeriod();
|
||||
@ -177,9 +209,8 @@ void setup() {
|
||||
// Comment below line to disable debug measurement readings
|
||||
measurements.setDebug(true);
|
||||
|
||||
/** Connecting wifi */
|
||||
bool connectToWifi = false;
|
||||
if (ag->isOne()) {
|
||||
bool connectToNetwork = true;
|
||||
if (ag->isOne()) { // Offline mode only available for indoor monitor
|
||||
/** Show message confirm offline mode, should me perform if LED bar button
|
||||
* test pressed */
|
||||
if (ledBarButtonTest == false) {
|
||||
@ -202,21 +233,21 @@ void setup() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
connectToWifi = !configuration.isOfflineMode();
|
||||
connectToNetwork = !configuration.isOfflineMode();
|
||||
} else {
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
connectToNetwork = false;
|
||||
}
|
||||
} else {
|
||||
connectToWifi = true;
|
||||
}
|
||||
|
||||
// Initialize networking configuration
|
||||
if (connectToWifi) {
|
||||
if (connectToNetwork) {
|
||||
oledDisplay.setText("Initialize", "network...", "");
|
||||
initializeNetwork();
|
||||
}
|
||||
|
||||
/** Set offline mode without saving, cause wifi is not configured */
|
||||
if (wifiConnector.hasConfigurated() == false) {
|
||||
if (wifiConnector.hasConfigurated() == false && networkOption == UseWifi) {
|
||||
Serial.println("Set offline mode cause wifi is not configurated");
|
||||
configuration.setOfflineModeWithoutSave(true);
|
||||
}
|
||||
@ -230,17 +261,59 @@ void setup() {
|
||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||
}
|
||||
|
||||
// Reset post schedulers to make sure measurements value already available
|
||||
agApiPostSchedule.update();
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
// If using cellular re-set scheduler interval
|
||||
configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL);
|
||||
transmissionSchedule.setPeriod(CELLULAR_TRANSMISSION_INTERVAL);
|
||||
measurementSchedule.setPeriod(CELLULAR_MEASUREMENT_INTERVAL);
|
||||
measurementSchedule.update();
|
||||
// Queue now only applied for cellular
|
||||
// Allocate queue memory to avoid always reallocation
|
||||
measurementCycleQueue.reserve(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||
// 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);
|
||||
if (xReturned == pdPASS) {
|
||||
Serial.println("Success create networking task");
|
||||
} else {
|
||||
assert("Failed to create networking task");
|
||||
}
|
||||
}
|
||||
|
||||
// Log monitor mode for debugging purpose
|
||||
if (configuration.isOfflineMode()) {
|
||||
Serial.println("Running monitor in offline mode");
|
||||
}
|
||||
else if (configuration.isCloudConnectionDisabled()) {
|
||||
Serial.println("Running monitor without connection to AirGradient server");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/** Run schedulers */
|
||||
dispLedSchedule.run();
|
||||
configSchedule.run();
|
||||
agApiPostSchedule.run();
|
||||
// Schedule to feed external watchdog
|
||||
watchdogFeedSchedule.run();
|
||||
|
||||
if (otaInProgress) {
|
||||
// OTA currently in progress, temporarily disable running sensor schedules
|
||||
delay(10000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule to update display and led
|
||||
dispLedSchedule.run();
|
||||
|
||||
if (networkOption == UseCellular) {
|
||||
// Queue now only applied for cellular
|
||||
measurementSchedule.run();
|
||||
}
|
||||
|
||||
if (configuration.hasSensorS8) {
|
||||
co2Schedule.run();
|
||||
}
|
||||
@ -273,17 +346,11 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Check for handle WiFi reconnect */
|
||||
wifiConnector.handle();
|
||||
|
||||
/** factory reset handle */
|
||||
factoryConfigReset();
|
||||
|
||||
/** check that local configuration changed then do some action */
|
||||
configUpdateHandle();
|
||||
|
||||
/** Firmware check for update handle */
|
||||
checkForUpdateSchedule.run();
|
||||
}
|
||||
|
||||
static void co2Update(void) {
|
||||
@ -464,47 +531,67 @@ static bool sgp41Init(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static void checkForFirmwareUpdate(void) {
|
||||
Serial.println();
|
||||
Serial.print("checkForFirmwareUpdate: ");
|
||||
|
||||
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled()) {
|
||||
Serial.println("mode is offline or cloud connection disabled, ignored");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wifiConnector.isConnected()) {
|
||||
Serial.println("wifi not connected, ignored");
|
||||
return;
|
||||
void checkForFirmwareUpdate(void) {
|
||||
AirgradientOTA *agOta;
|
||||
if (networkOption == UseWifi) {
|
||||
agOta = new AirgradientOTAWifi;
|
||||
} else {
|
||||
agOta = new AirgradientOTACellular(cell);
|
||||
}
|
||||
|
||||
Serial.println("perform");
|
||||
otaHandler.setHandlerCallback(otaHandlerCallback);
|
||||
otaHandler.updateFirmwareIfOutdated(ag->deviceId());
|
||||
// Indicate main task that ota is performing
|
||||
Serial.println("Check for firmware update, disabling main task");
|
||||
otaInProgress = true;
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
// Only for cellular because it can disturb i2c line
|
||||
Serial.println("Disable SGP41 task for cellular OTA");
|
||||
ag->sgp41.end();
|
||||
}
|
||||
|
||||
agOta->setHandlerCallback(otaHandlerCallback);
|
||||
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION);
|
||||
|
||||
// Only goes to this line if OTA is not success
|
||||
// Handled by otaHandlerCallback
|
||||
|
||||
otaInProgress = false;
|
||||
if (configuration.hasSensorSGP && networkOption == UseCellular) {
|
||||
// Re-start SGP41 task
|
||||
if (!sgp41Init()) {
|
||||
Serial.println("Failed re-start SGP41 task");
|
||||
}
|
||||
}
|
||||
|
||||
delete agOta;
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static void otaHandlerCallback(OtaHandler::OtaState state, String message) {
|
||||
Serial.println("OTA message: " + message);
|
||||
switch (state) {
|
||||
case OtaHandler::OTA_STATE_BEGIN:
|
||||
displayExecuteOta(state, fwNewVersion, 0);
|
||||
void otaHandlerCallback(AirgradientOTA::OtaResult result, const char *msg) {
|
||||
switch (result) {
|
||||
case AirgradientOTA::Starting:
|
||||
displayExecuteOta(result, fwNewVersion, 0);
|
||||
break;
|
||||
case OtaHandler::OTA_STATE_FAIL:
|
||||
displayExecuteOta(state, "", 0);
|
||||
case AirgradientOTA::InProgress:
|
||||
Serial.printf("OTA progress: %s\n", msg);
|
||||
displayExecuteOta(result, "", std::stoi(msg));
|
||||
break;
|
||||
case OtaHandler::OTA_STATE_PROCESSING:
|
||||
case OtaHandler::OTA_STATE_SUCCESS:
|
||||
displayExecuteOta(state, "", message.toInt());
|
||||
case AirgradientOTA::Failed:
|
||||
case AirgradientOTA::Skipped:
|
||||
case AirgradientOTA::AlreadyUpToDate:
|
||||
displayExecuteOta(result, "", 0);
|
||||
break;
|
||||
case AirgradientOTA::Success:
|
||||
displayExecuteOta(result, "", 0);
|
||||
esp_restart();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void displayExecuteOta(OtaHandler::OtaState state, String msg, int processing) {
|
||||
switch (state) {
|
||||
case OtaHandler::OTA_STATE_BEGIN: {
|
||||
static void displayExecuteOta(AirgradientOTA::OtaResult result, String msg, int processing) {
|
||||
switch (result) {
|
||||
case AirgradientOTA::Starting:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateVersion(msg);
|
||||
} else {
|
||||
@ -512,65 +599,50 @@ static void displayExecuteOta(OtaHandler::OtaState state, String msg, int proces
|
||||
}
|
||||
delay(2500);
|
||||
break;
|
||||
}
|
||||
case OtaHandler::OTA_STATE_FAIL: {
|
||||
case AirgradientOTA::Failed:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateFailed();
|
||||
} else {
|
||||
Serial.println("Error: Firmware update: failed");
|
||||
}
|
||||
|
||||
delay(2500);
|
||||
break;
|
||||
}
|
||||
case OtaHandler::OTA_STATE_SKIP: {
|
||||
case AirgradientOTA::Skipped:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateSkipped();
|
||||
} else {
|
||||
Serial.println("Firmware update: Skipped");
|
||||
}
|
||||
|
||||
delay(2500);
|
||||
break;
|
||||
}
|
||||
case OtaHandler::OTA_STATE_UP_TO_DATE: {
|
||||
case AirgradientOTA::AlreadyUpToDate:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateUpToDate();
|
||||
} else {
|
||||
Serial.println("Firmware update: up to date");
|
||||
}
|
||||
|
||||
delay(2500);
|
||||
break;
|
||||
}
|
||||
case OtaHandler::OTA_STATE_PROCESSING: {
|
||||
case AirgradientOTA::InProgress:
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateProgress(processing);
|
||||
} else {
|
||||
Serial.println("Firmware update: " + String(processing) + String("%"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OtaHandler::OTA_STATE_SUCCESS: {
|
||||
int i = 6;
|
||||
while(i != 0) {
|
||||
case AirgradientOTA::Success: {
|
||||
Serial.println("OTA update performed, restarting ...");
|
||||
int i = 3;
|
||||
while (i != 0) {
|
||||
i = i - 1;
|
||||
Serial.println("OTA update performed, restarting ...");
|
||||
int i = 6;
|
||||
while (i != 0) {
|
||||
i = i - 1;
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateSuccess(i);
|
||||
} else {
|
||||
Serial.println("Rebooting... " + String(i));
|
||||
}
|
||||
|
||||
delay(1000);
|
||||
if (ag->isOne()) {
|
||||
oledDisplay.showFirmwareUpdateSuccess(i);
|
||||
} else {
|
||||
Serial.println("Rebooting... " + String(i));
|
||||
}
|
||||
oledDisplay.setBrightness(0);
|
||||
esp_restart();
|
||||
delay(1000);
|
||||
}
|
||||
oledDisplay.setAirGradient(0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -602,7 +674,13 @@ static void sendDataToAg() {
|
||||
"task_led", 2048, NULL, 5, NULL);
|
||||
|
||||
delay(1500);
|
||||
if (apiClient.sendPing(wifiConnector.RSSI(), measurements.bootCount())) {
|
||||
|
||||
// Build payload to check connection to airgradient server
|
||||
JSONVar root;
|
||||
root["wifi"] = wifiConnector.RSSI();
|
||||
root["boot"] = measurements.bootCount();
|
||||
std::string payload = JSON.stringify(root).c_str();
|
||||
if (agClient->httpPostMeasures(payload)) {
|
||||
if (ag->isOne()) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||
}
|
||||
@ -838,54 +916,75 @@ static void failedHandler(String msg) {
|
||||
}
|
||||
|
||||
void initializeNetwork() {
|
||||
if (!wifiConnector.connect()) {
|
||||
Serial.println("Cannot initiate wifi connection");
|
||||
return;
|
||||
// Check if cellular module available
|
||||
agSerial = new AgSerial(Wire);
|
||||
agSerial->init(GPIO_IIC_RESET);
|
||||
if (agSerial->open()) {
|
||||
Serial.println("Cellular module found");
|
||||
// Initialize cellular module and use cellular as agClient
|
||||
cell = new CellularModuleA7672XX(agSerial, GPIO_POWER_MODULE_PIN);
|
||||
agClient = new AirgradientCellularClient(cell);
|
||||
networkOption = UseCellular;
|
||||
} else {
|
||||
Serial.println("Cellular module not available, using wifi");
|
||||
delete agSerial;
|
||||
agSerial = nullptr;
|
||||
// Use wifi as agClient
|
||||
agClient = new AirgradientWifiClient;
|
||||
networkOption = UseWifi;
|
||||
}
|
||||
|
||||
if (!wifiConnector.isConnected()) {
|
||||
Serial.println("Failed connect to WiFi");
|
||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
if (!agClient->begin(ag->deviceId().c_str())) {
|
||||
oledDisplay.setText("Client", "initialization", "failed");
|
||||
delay(5000);
|
||||
oledDisplay.showRebooting();
|
||||
delay(2500);
|
||||
oledDisplay.setText("", "", "");
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
if (networkOption == UseWifi) {
|
||||
if (!wifiConnector.connect()) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Directly return because the rest of the function applied if wifi is connect only
|
||||
return;
|
||||
// Send data for the first time to AG server at boot
|
||||
sendDataToAg();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Initialize api client
|
||||
apiClient.begin();
|
||||
|
||||
// Send data for the first time to AG server at boot
|
||||
sendDataToAg();
|
||||
|
||||
// OTA check
|
||||
#ifdef ESP8266
|
||||
// ota not supported
|
||||
#else
|
||||
checkForFirmwareUpdate();
|
||||
checkForUpdateSchedule.update();
|
||||
#endif
|
||||
|
||||
apiClient.fetchServerConfiguration();
|
||||
std::string config = agClient->httpFetchConfig();
|
||||
configSchedule.update();
|
||||
if (apiClient.isFetchConfigurationFailed()) {
|
||||
// Check if fetch configuration failed or fetch succes but parsing failed
|
||||
if (agClient->isLastFetchConfigSucceed() == false ||
|
||||
configuration.parse(config.c_str(), false) == false) {
|
||||
if (ag->isOne()) {
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
if (agClient->isRegisteredOnAgServer() == false) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
} else {
|
||||
@ -894,26 +993,22 @@ void initializeNetwork() {
|
||||
}
|
||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
ledBarEnabledUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
static void configurationUpdateSchedule(void) {
|
||||
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled() ||
|
||||
configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||
Serial.println("Ignore fetch server configuration. Either mode is offline or cloud connection "
|
||||
"disabled or configurationControl set to local");
|
||||
apiClient.resetFetchConfigurationStatus();
|
||||
if (configuration.getConfigurationControl() ==
|
||||
ConfigurationControl::ConfigurationControlLocal) {
|
||||
Serial.println("Ignore fetch server configuration, configurationControl set to local");
|
||||
agClient->resetFetchConfigurationStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
Serial.println(" WiFi not connected, skipping fetch configuration from AG server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiClient.fetchServerConfiguration()) {
|
||||
std::string config = agClient->httpFetchConfig();
|
||||
if (agClient->isLastFetchConfigSucceed() && configuration.parse(config.c_str(), false)) {
|
||||
configUpdateHandle();
|
||||
}
|
||||
}
|
||||
@ -1010,10 +1105,20 @@ static void updateDisplayAndLedBar(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||
return;
|
||||
if (networkOption == UseWifi) {
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
// Same action as wifi
|
||||
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
@ -1024,14 +1129,15 @@ static void updateDisplayAndLedBar(void) {
|
||||
}
|
||||
|
||||
AgStateMachineState state = AgStateMachineNormal;
|
||||
if (apiClient.isFetchConfigurationFailed()) {
|
||||
if (agClient->isLastFetchConfigSucceed() == false) {
|
||||
state = AgStateMachineSensorConfigFailed;
|
||||
if (apiClient.isNotAvailableOnDashboard()) {
|
||||
if (agClient->isRegisteredOnAgServer() == false) {
|
||||
stateMachine.displaySetAddToDashBoard();
|
||||
} else {
|
||||
stateMachine.displayClearAddToDashBoard();
|
||||
}
|
||||
} else if (apiClient.isPostToServerFailed() && configuration.isPostDataToAirGradient()) {
|
||||
} else if (agClient->isLastPostMeasureSucceed() == false &&
|
||||
configuration.isPostDataToAirGradient()) {
|
||||
state = AgStateMachineServerLost;
|
||||
}
|
||||
|
||||
@ -1187,34 +1293,79 @@ static void updatePm(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void sendDataToServer(void) {
|
||||
/** Increment bootcount when send measurements data is scheduled */
|
||||
void postUsingWifi() {
|
||||
// Increment bootcount when send measurements data is scheduled
|
||||
int bootCount = measurements.bootCount() + 1;
|
||||
measurements.setBootCount(bootCount);
|
||||
|
||||
if (configuration.isOfflineMode() || configuration.isCloudConnectionDisabled() ||
|
||||
!configuration.isPostDataToAirGradient()) {
|
||||
Serial.println("Skipping transmission of data to AG server. Either mode is offline or cloud connection is "
|
||||
"disabled or post data to server disabled");
|
||||
return;
|
||||
}
|
||||
|
||||
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)) {
|
||||
String payload = measurements.toString(false, fwMode, wifiConnector.RSSI());
|
||||
if (agClient->httpPostMeasures(payload.c_str()) == false) {
|
||||
Serial.println();
|
||||
Serial.println("Online mode and isPostToAirGradient = true");
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
/** Log current free heap size */
|
||||
// Log current free heap size
|
||||
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void postUsingCellular() {
|
||||
// Aquire queue mutex to get queue size
|
||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||
|
||||
// Make sure measurement cycle available
|
||||
int queueSize = measurementCycleQueue.size();
|
||||
if (queueSize == 0) {
|
||||
Serial.println("Skipping transmission, measurementCycle empty");
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build payload include all measurements from queue
|
||||
std::string payload;
|
||||
payload += std::to_string(CELLULAR_MEASUREMENT_INTERVAL / 1000); // Convert to seconds
|
||||
for (int i = 0; i < queueSize; i++) {
|
||||
auto mc = measurementCycleQueue.at(i);
|
||||
payload += ",";
|
||||
payload += measurements.buildMeasuresPayload(mc);
|
||||
}
|
||||
|
||||
// Release before actually post measures that might takes too long
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
|
||||
// Attempt to send
|
||||
if (agClient->httpPostMeasures(payload) == false) {
|
||||
// Consider network has a problem, retry in next schedule
|
||||
Serial.println("Post measures failed, retry in next schedule");
|
||||
return;
|
||||
}
|
||||
|
||||
// Post success, remove the data that previously sent from queue
|
||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||
measurementCycleQueue.erase(measurementCycleQueue.begin(),
|
||||
measurementCycleQueue.begin() + queueSize);
|
||||
|
||||
if (measurementCycleQueue.capacity() > RESERVED_MEASUREMENT_CYCLE_CAPACITY) {
|
||||
Serial.println("measurementCycleQueue capacity more than reserved space, resizing..");
|
||||
measurementCycleQueue.resize(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||
}
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
}
|
||||
|
||||
void sendDataToServer(void) {
|
||||
if (configuration.isPostDataToAirGradient() == false) {
|
||||
Serial.println("Skipping transmission of data to AG server, post data to server disabled");
|
||||
agClient->resetPostMeasuresStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (networkOption == UseWifi) {
|
||||
postUsingWifi();
|
||||
} else if (networkOption == UseCellular) {
|
||||
postUsingCellular();
|
||||
}
|
||||
}
|
||||
|
||||
static void tempHumUpdate(void) {
|
||||
delay(100);
|
||||
if (ag->sht.measure()) {
|
||||
@ -1281,5 +1432,110 @@ void setMeasurementMaxPeriod() {
|
||||
|
||||
int calculateMaxPeriod(int updateInterval) {
|
||||
// 0.8 is 80% reduced interval for max period
|
||||
return (SERVER_SYNC_INTERVAL - (SERVER_SYNC_INTERVAL * 0.8)) / updateInterval;
|
||||
if (networkOption == UseWifi) {
|
||||
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
} else {
|
||||
// Cellular
|
||||
return (CELLULAR_MEASUREMENT_INTERVAL - (CELLULAR_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void networkSignalCheck() {
|
||||
if (networkOption == UseWifi) {
|
||||
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
|
||||
} else if (networkOption == UseCellular) {
|
||||
auto result = cell->retrieveSignal();
|
||||
if (result.status != CellReturnStatus::Ok) {
|
||||
agClient->setClientReady(false);
|
||||
return;
|
||||
}
|
||||
if (result.data == 99) {
|
||||
// 99 indicate cellular not attached to network
|
||||
agClient->setClientReady(false);
|
||||
return;
|
||||
}
|
||||
Serial.printf("Cellular signal strength %d\n", result.data);
|
||||
}
|
||||
}
|
||||
|
||||
void networkingTask(void *args) {
|
||||
// OTA check on boot
|
||||
#ifdef ESP8266
|
||||
// ota not supported
|
||||
#else
|
||||
// because cellular it takes too long, watchdog triggered
|
||||
checkForFirmwareUpdate();
|
||||
checkForUpdateSchedule.update();
|
||||
#endif
|
||||
|
||||
// Because cellular interval is longer, needs to send first measures cycle on
|
||||
// boot to indicate that its online
|
||||
if (networkOption == UseCellular) {
|
||||
Serial.println("Prepare first measures cycle to send on boot for 20s");
|
||||
delay(20000);
|
||||
newMeasurementCycle();
|
||||
sendDataToServer();
|
||||
measurementSchedule.update();
|
||||
}
|
||||
|
||||
// Reset scheduler
|
||||
configSchedule.update();
|
||||
transmissionSchedule.update();
|
||||
|
||||
while (1) {
|
||||
// Handle reconnection based on mode
|
||||
if (networkOption == UseWifi) {
|
||||
wifiConnector.handle();
|
||||
if (wifiConnector.isConnected() == false) {
|
||||
delay(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (networkOption == UseCellular) {
|
||||
if (agClient->isClientReady() == false) {
|
||||
Serial.println("Cellular client not ready, ensuring connection...");
|
||||
if (agClient->ensureClientConnection() == false) {
|
||||
Serial.println("Cellular client connection not ready, retry in 5s...");
|
||||
delay(5000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If connection to AirGradient server disable don't run config and transmission schedule
|
||||
if (configuration.isCloudConnectionDisabled()) {
|
||||
delay(1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run scheduler
|
||||
networkSignalCheckSchedule.run();
|
||||
configSchedule.run();
|
||||
transmissionSchedule.run();
|
||||
checkForUpdateSchedule.run();
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
vTaskDelete(handleNetworkTask);
|
||||
}
|
||||
|
||||
void newMeasurementCycle() {
|
||||
if (xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY) == pdTRUE) {
|
||||
// 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());
|
||||
}
|
||||
|
||||
auto mc = measurements.getMeasures();
|
||||
measurementCycleQueue.push_back(mc);
|
||||
Serial.println("New measurement cycle added to queue");
|
||||
// Release mutex
|
||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||
// Log current free heap size
|
||||
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
#include "OpenMetrics.h"
|
||||
|
||||
OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector, AgApiClient &apiClient)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector),
|
||||
apiClient(apiClient) {}
|
||||
WifiConnector &wifiConnector)
|
||||
: measure(measure), config(config), wifiConnector(wifiConnector) {}
|
||||
|
||||
OpenMetrics::~OpenMetrics() {}
|
||||
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||
void OpenMetrics::setAirGradient(AirGradient *ag, AirgradientClient *client) {
|
||||
this->ag = ag;
|
||||
this->agClient = client;
|
||||
}
|
||||
|
||||
const char *OpenMetrics::getApiContentType(void) {
|
||||
return "application/openmetrics-text; version=1.0.0; charset=utf-8";
|
||||
@ -43,13 +45,13 @@ String OpenMetrics::getPayload(void) {
|
||||
"1 if the AirGradient device was able to successfully fetch its "
|
||||
"configuration from the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isFetchConfigurationFailed() ? "0" : "1");
|
||||
add_metric_point("", agClient->isLastFetchConfigSucceed() ? "1" : "0");
|
||||
|
||||
add_metric(
|
||||
"post_ok",
|
||||
"1 if the AirGradient device was able to successfully send to the server",
|
||||
"gauge");
|
||||
add_metric_point("", apiClient.isPostToServerFailed() ? "0" : "1");
|
||||
add_metric_point("", agClient->isLastPostMeasureSucceed() ? "1" : "0");
|
||||
|
||||
add_metric(
|
||||
"wifi_rssi",
|
||||
|
@ -5,21 +5,21 @@
|
||||
#include "AgValue.h"
|
||||
#include "AgWiFiConnector.h"
|
||||
#include "AirGradient.h"
|
||||
#include "AgApiClient.h"
|
||||
#include "Libraries/airgradient-client/src/airgradientClient.h"
|
||||
|
||||
class OpenMetrics {
|
||||
private:
|
||||
AirGradient *ag;
|
||||
AirgradientClient *agClient;
|
||||
Measurements &measure;
|
||||
Configuration &config;
|
||||
WifiConnector &wifiConnector;
|
||||
AgApiClient &apiClient;
|
||||
|
||||
public:
|
||||
OpenMetrics(Measurements &measure, Configuration &conig,
|
||||
WifiConnector &wifiConnector, AgApiClient& apiClient);
|
||||
OpenMetrics(Measurements &measure, Configuration &config,
|
||||
WifiConnector &wifiConnector);
|
||||
~OpenMetrics();
|
||||
void setAirGradient(AirGradient *ag);
|
||||
void setAirGradient(AirGradient *ag, AirgradientClient *client);
|
||||
const char *getApiContentType(void);
|
||||
const char* getApi(void);
|
||||
String getPayload(void);
|
||||
|
@ -1,5 +1,5 @@
|
||||
name=AirGradient Air Quality Sensor
|
||||
version=3.1.21
|
||||
version=3.3.0
|
||||
author=AirGradient <support@airgradient.com>
|
||||
maintainer=AirGradient <support@airgradient.com>
|
||||
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||
|
@ -12,7 +12,7 @@
|
||||
platform = espressif32
|
||||
board = esp32-c3-devkitm-1
|
||||
framework = arduino
|
||||
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
||||
build_flags = !echo '-D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 -D CORE_DEBUG_LEVEL=3 -D GIT_VERSION=\\"'$(git describe --tags --always --dirty)'\\"'
|
||||
board_build.partitions = partitions.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps =
|
||||
|
@ -5,12 +5,31 @@
|
||||
/** Cast U8G2 */
|
||||
#define DISP() ((U8G2_SH1106_128X64_NONAME_F_HW_I2C *)(this->u8g2))
|
||||
|
||||
static const unsigned char WIFI_ISSUE_BITS[] = {
|
||||
0xd8, 0xc6, 0xde, 0xde, 0xc7, 0xf8, 0xd1, 0xe2, 0xdc, 0xce, 0xcc,
|
||||
0xcc, 0xc0, 0xc0, 0xd0, 0xc2, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0};
|
||||
|
||||
static const unsigned char CLOUD_ISSUE_BITS[] = {
|
||||
0x70, 0xc0, 0x88, 0xc0, 0x04, 0xc1, 0x04, 0xcf, 0x02, 0xd0, 0x01,
|
||||
0xe0, 0x01, 0xe0, 0x01, 0xe0, 0xa2, 0xd0, 0x4c, 0xce, 0xa0, 0xc0};
|
||||
|
||||
// Offline mode icon
|
||||
static unsigned char OFFLINE_BITS[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x62, 0x00,
|
||||
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
|
||||
*
|
||||
* @param hasStatus
|
||||
*/
|
||||
void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
||||
void OledDisplay::showTempHum(bool hasStatus) {
|
||||
char buf[10];
|
||||
/** Temperature */
|
||||
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
||||
if (utils::isValidTemperature(temp)) {
|
||||
@ -23,22 +42,22 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
||||
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
if (hasStatus) {
|
||||
snprintf(buf, buf_size, "%0.1f", t);
|
||||
snprintf(buf, sizeof(buf), "%0.1f", t);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%0.1f°F", t);
|
||||
snprintf(buf, sizeof(buf), "%0.1f°F", t);
|
||||
}
|
||||
} else {
|
||||
if (hasStatus) {
|
||||
snprintf(buf, buf_size, "%.1f", t);
|
||||
snprintf(buf, sizeof(buf), "%.1f", t);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "%.1f°C", t);
|
||||
snprintf(buf, sizeof(buf), "%.1f°C", t);
|
||||
}
|
||||
}
|
||||
} else { /** Show invalid value */
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
snprintf(buf, buf_size, "-°F");
|
||||
snprintf(buf, sizeof(buf), "-°F");
|
||||
} else {
|
||||
snprintf(buf, buf_size, "-°C");
|
||||
snprintf(buf, sizeof(buf), "-°C");
|
||||
}
|
||||
}
|
||||
DISP()->drawUTF8(1, 10, buf);
|
||||
@ -46,9 +65,9 @@ void OledDisplay::showTempHum(bool hasStatus, char *buf, int buf_size) {
|
||||
/** Show humidity */
|
||||
int rhum = round(value.getCorrectedTempHum(Measurements::Humidity, 1));
|
||||
if (utils::isValidHumidity(rhum)) {
|
||||
snprintf(buf, buf_size, "%d%%", rhum);
|
||||
snprintf(buf, sizeof(buf), "%d%%", rhum);
|
||||
} else {
|
||||
snprintf(buf, buf_size, "-%%");
|
||||
snprintf(buf, sizeof(buf), "-%%");
|
||||
}
|
||||
|
||||
if (rhum > 99.0) {
|
||||
@ -67,6 +86,9 @@ void OledDisplay::setCentralText(int y, const char *text) {
|
||||
DISP()->drawStr(x, y, text);
|
||||
}
|
||||
|
||||
void OledDisplay::showIcon(int x, int y, xbm_icon_t *icon) {
|
||||
DISP()->drawXBM(x, y, icon->width, icon->height, icon->icon);
|
||||
}
|
||||
/**
|
||||
* @brief Construct a new Ag Oled Display:: Ag Oled Display object
|
||||
*
|
||||
@ -252,36 +274,60 @@ void OledDisplay::setText(const char *line1, const char *line2,
|
||||
* @brief Update dashboard content
|
||||
*
|
||||
*/
|
||||
void OledDisplay::showDashboard(void) { showDashboard(NULL); }
|
||||
void OledDisplay::showDashboard(void) { showDashboard(DashBoardStatusNone); }
|
||||
|
||||
/**
|
||||
* @brief Update dashboard content and error status
|
||||
*
|
||||
*/
|
||||
void OledDisplay::showDashboard(const char *status) {
|
||||
void OledDisplay::showDashboard(DashboardStatus status) {
|
||||
if (isDisplayOff) {
|
||||
return;
|
||||
}
|
||||
|
||||
char strBuf[16];
|
||||
const int icon_pos_x = 64;
|
||||
xbm_icon_t xbm_icon = {
|
||||
.width = 0,
|
||||
.height = 0,
|
||||
.icon = nullptr,
|
||||
};
|
||||
|
||||
if (ag->isOne() || ag->isPro3_3() || ag->isPro4_2()) {
|
||||
DISP()->firstPage();
|
||||
do {
|
||||
DISP()->setFont(u8g2_font_t0_16_tf);
|
||||
if ((status == NULL) || (strlen(status) == 0)) {
|
||||
showTempHum(false, strBuf, sizeof(strBuf));
|
||||
} else {
|
||||
String strStatus = "Show status: " + String(status);
|
||||
logInfo(strStatus);
|
||||
|
||||
int strWidth = DISP()->getStrWidth(status);
|
||||
DISP()->drawStr((DISP()->getWidth() - strWidth) / 2, 10, status);
|
||||
|
||||
/** Show WiFi NA*/
|
||||
if (strcmp(status, "WiFi N/A") == 0) {
|
||||
DISP()->setFont(u8g2_font_t0_12_tf);
|
||||
showTempHum(true, strBuf, sizeof(strBuf));
|
||||
}
|
||||
switch (status) {
|
||||
case DashBoardStatusNone: {
|
||||
// Maybe show signal strength?
|
||||
showTempHum(false);
|
||||
break;
|
||||
}
|
||||
case DashBoardStatusWiFiIssue: {
|
||||
DISP()->drawXBM(icon_pos_x, 0, 14, 11, WIFI_ISSUE_BITS);
|
||||
showTempHum(false);
|
||||
break;
|
||||
}
|
||||
case DashBoardStatusServerIssue: {
|
||||
DISP()->drawXBM(icon_pos_x, 0, 14, 11, CLOUD_ISSUE_BITS);
|
||||
showTempHum(false);
|
||||
break;
|
||||
}
|
||||
case DashBoardStatusAddToDashboard: {
|
||||
setCentralText(10, "Add To Dashboard");
|
||||
break;
|
||||
}
|
||||
case DashBoardStatusDeviceId: {
|
||||
setCentralText(10, ag->deviceId().c_str());
|
||||
break;
|
||||
}
|
||||
case DashBoardStatusOfflineMode: {
|
||||
DISP()->drawXBM(icon_pos_x, 0, 14, 14, OFFLINE_BITS);
|
||||
showTempHum(false); // First true
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/** Draw horizonal line */
|
||||
@ -392,7 +438,8 @@ void OledDisplay::showDashboard(const char *status) {
|
||||
float temp = value.getCorrectedTempHum(Measurements::Temperature, 1);
|
||||
if (utils::isValidTemperature(temp)) {
|
||||
if (config.isTemperatureUnitInF()) {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F", utils::degreeC_To_F(temp));
|
||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f F",
|
||||
utils::degreeC_To_F(temp));
|
||||
} else {
|
||||
snprintf(strBuf, sizeof(strBuf), "T:%0.1f C", temp);
|
||||
}
|
||||
@ -442,8 +489,7 @@ void OledDisplay::setBrightness(int percent) {
|
||||
// Clear display.
|
||||
ag->display.clear();
|
||||
ag->display.show();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
isDisplayOff = false;
|
||||
ag->display.setContrast((255 * percent) / 100);
|
||||
}
|
||||
|
@ -16,17 +16,32 @@ private:
|
||||
Measurements &value;
|
||||
bool isDisplayOff = false;
|
||||
|
||||
void showTempHum(bool hasStatus, char* buf, int buf_size);
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
unsigned char *icon;
|
||||
} xbm_icon_t;
|
||||
|
||||
void showTempHum(bool hasStatus);
|
||||
void setCentralText(int y, String text);
|
||||
void setCentralText(int y, const char *text);
|
||||
void showIcon(int x, int y, xbm_icon_t *icon);
|
||||
|
||||
public:
|
||||
OledDisplay(Configuration &config, Measurements &value,
|
||||
Stream &log);
|
||||
OledDisplay(Configuration &config, Measurements &value, Stream &log);
|
||||
~OledDisplay();
|
||||
|
||||
enum DashboardStatus {
|
||||
DashBoardStatusNone,
|
||||
DashBoardStatusWiFiIssue,
|
||||
DashBoardStatusServerIssue,
|
||||
DashBoardStatusAddToDashboard,
|
||||
DashBoardStatusDeviceId,
|
||||
DashBoardStatusOfflineMode,
|
||||
};
|
||||
|
||||
void setAirGradient(AirGradient *ag);
|
||||
bool begin(void);
|
||||
bool begin(void);
|
||||
void end(void);
|
||||
void setText(String &line1, String &line2, String &line3);
|
||||
void setText(const char *line1, const char *line2, const char *line3);
|
||||
@ -34,7 +49,7 @@ public:
|
||||
void setText(const char *line1, const char *line2, const char *line3,
|
||||
const char *line4);
|
||||
void showDashboard(void);
|
||||
void showDashboard(const char *status);
|
||||
void showDashboard(DashboardStatus status);
|
||||
void setBrightness(int percent);
|
||||
#ifdef ESP32
|
||||
void showFirmwareUpdateVersion(String version);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "AgStateMachine.h"
|
||||
#include "AgOledDisplay.h"
|
||||
|
||||
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||
#define LED_TEST_BLINK_DELAY 50 /** ms */
|
||||
#define LED_FAST_BLINK_DELAY 250 /** ms */
|
||||
#define LED_SLOW_BLINK_DELAY 1000 /** ms */
|
||||
#define LED_SHORT_BLINK_DELAY 500 /** ms */
|
||||
@ -8,9 +9,9 @@
|
||||
|
||||
#define SENSOR_CO2_CALIB_COUNTDOWN_MAX 5 /** sec */
|
||||
|
||||
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
||||
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
||||
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
||||
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
||||
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
||||
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
||||
#define RGB_COLOR_CLEAR 0, 0, 0 /** No color */
|
||||
@ -50,7 +51,7 @@ void StateMachine::ledStatusBlinkDelay(uint32_t ms) {
|
||||
/**
|
||||
* @brief Led bar show PM or CO2 led color status
|
||||
*
|
||||
* @return true if all led bar are used, false othwerwise
|
||||
* @return true if all led bar are used, false othwerwise
|
||||
*/
|
||||
bool StateMachine::sensorhandleLeds(void) {
|
||||
int totalLedUsed = 0;
|
||||
@ -82,7 +83,7 @@ bool StateMachine::sensorhandleLeds(void) {
|
||||
/**
|
||||
* @brief Show CO2 LED status
|
||||
*
|
||||
* @return return total number of led that are used on the monitor
|
||||
* @return return total number of led that are used on the monitor
|
||||
*/
|
||||
int StateMachine::co2handleLeds(void) {
|
||||
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||
@ -166,8 +167,8 @@ int StateMachine::co2handleLeds(void) {
|
||||
|
||||
/**
|
||||
* @brief Show PM2.5 LED status
|
||||
*
|
||||
* @return return total number of led that are used on the monitor
|
||||
*
|
||||
* @return return total number of led that are used on the monitor
|
||||
*/
|
||||
int StateMachine::pm25handleLeds(void) {
|
||||
int totalUsed = ag->ledBar.getNumberOfLeds();
|
||||
@ -369,18 +370,17 @@ void StateMachine::ledBarTest(void) {
|
||||
} else {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
else if(ag->isOpenAir()) {
|
||||
} else if (ag->isOpenAir()) {
|
||||
ledBarRunTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateMachine::ledBarPowerUpTest(void) {
|
||||
void StateMachine::ledBarPowerUpTest(void) {
|
||||
if (ag->isOne()) {
|
||||
ag->ledBar.clear();
|
||||
}
|
||||
ledBarRunTest();
|
||||
ledBarRunTest();
|
||||
}
|
||||
|
||||
void StateMachine::ledBarRunTest(void) {
|
||||
@ -544,11 +544,11 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
break;
|
||||
}
|
||||
case AgStateMachineWiFiLost: {
|
||||
disp.showDashboard("WiFi N/A");
|
||||
disp.showDashboard(OledDisplay::DashBoardStatusWiFiIssue);
|
||||
break;
|
||||
}
|
||||
case AgStateMachineServerLost: {
|
||||
disp.showDashboard("AG Server N/A");
|
||||
disp.showDashboard(OledDisplay::DashBoardStatusServerIssue);
|
||||
break;
|
||||
}
|
||||
case AgStateMachineSensorConfigFailed: {
|
||||
@ -557,19 +557,24 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
||||
if (ms >= 5000) {
|
||||
addToDashboardTime = millis();
|
||||
if (addToDashBoardToggle) {
|
||||
disp.showDashboard("Add to AG Dashb.");
|
||||
disp.showDashboard(OledDisplay::DashBoardStatusAddToDashboard);
|
||||
} else {
|
||||
disp.showDashboard(ag->deviceId().c_str());
|
||||
disp.showDashboard(OledDisplay::DashBoardStatusDeviceId);
|
||||
}
|
||||
addToDashBoardToggle = !addToDashBoardToggle;
|
||||
}
|
||||
} else {
|
||||
disp.showDashboard("");
|
||||
disp.showDashboard();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineNormal: {
|
||||
disp.showDashboard();
|
||||
if (config.isOfflineMode()) {
|
||||
disp.showDashboard(
|
||||
OledDisplay::DashBoardStatusOfflineMode);
|
||||
} else {
|
||||
disp.showDashboard();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AgStateMachineCo2Calibration:
|
||||
|
151
src/AgValue.cpp
@ -2,6 +2,8 @@
|
||||
#include "AgConfigure.h"
|
||||
#include "AirGradient.h"
|
||||
#include "App/AppDef.h"
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
|
||||
#define json_prop_pmFirmware "firmware"
|
||||
#define json_prop_pm01Ae "pm01"
|
||||
@ -686,6 +688,153 @@ float Measurements::getCorrectedPM25(bool useAvg, int ch, bool forceCorrection)
|
||||
return corrected;
|
||||
}
|
||||
|
||||
Measurements::Measures Measurements::getMeasures() {
|
||||
Measures mc;
|
||||
mc.bootCount = _bootCount;
|
||||
mc.freeHeap = ESP.getFreeHeap();
|
||||
// co2, tvoc, nox
|
||||
mc.co2 = _co2.update.avg;
|
||||
mc.tvoc = _tvoc.update.avg;
|
||||
mc.tvoc_raw = _tvoc_raw.update.avg;
|
||||
mc.nox = _nox.update.avg;
|
||||
mc.nox_raw = _nox_raw.update.avg;
|
||||
// Temperature & Humidity
|
||||
mc.temperature[0] = _temperature[0].update.avg;
|
||||
mc.humidity[0] = _humidity[0].update.avg;
|
||||
mc.temperature[1] = _temperature[1].update.avg;
|
||||
mc.humidity[1] = _humidity[1].update.avg;
|
||||
// PM atmospheric
|
||||
mc.pm_01[0] = _pm_01[0].update.avg;
|
||||
mc.pm_25[0] = _pm_25[0].update.avg;
|
||||
mc.pm_10[0] = _pm_10[0].update.avg;
|
||||
mc.pm_01[1] = _pm_01[1].update.avg;
|
||||
mc.pm_25[1] = _pm_25[1].update.avg;
|
||||
mc.pm_10[1] = _pm_10[1].update.avg;
|
||||
// PM standard particle
|
||||
mc.pm_01_sp[0] = _pm_01_sp[0].update.avg;
|
||||
mc.pm_25_sp[0] = _pm_25_sp[0].update.avg;
|
||||
mc.pm_10_sp[0] = _pm_10_sp[0].update.avg;
|
||||
mc.pm_01_sp[1] = _pm_01_sp[1].update.avg;
|
||||
mc.pm_25_sp[1] = _pm_25_sp[1].update.avg;
|
||||
mc.pm_10_sp[1] = _pm_10_sp[1].update.avg;
|
||||
// Particle Count
|
||||
mc.pm_03_pc[0] = _pm_03_pc[0].update.avg;
|
||||
mc.pm_05_pc[0] = _pm_05_pc[0].update.avg;
|
||||
mc.pm_01_pc[0] = _pm_01_pc[0].update.avg;
|
||||
mc.pm_25_pc[0] = _pm_25_pc[0].update.avg;
|
||||
mc.pm_5_pc[0] = _pm_5_pc[0].update.avg;
|
||||
mc.pm_10_pc[0] = _pm_10_pc[0].update.avg;
|
||||
mc.pm_03_pc[1] = _pm_03_pc[1].update.avg;
|
||||
mc.pm_05_pc[1] = _pm_05_pc[1].update.avg;
|
||||
mc.pm_01_pc[1] = _pm_01_pc[1].update.avg;
|
||||
mc.pm_25_pc[1] = _pm_25_pc[1].update.avg;
|
||||
mc.pm_5_pc[1] = _pm_5_pc[1].update.avg;
|
||||
mc.pm_10_pc[1] = _pm_10_pc[1].update.avg;
|
||||
|
||||
return mc;
|
||||
}
|
||||
|
||||
std::string Measurements::buildMeasuresPayload(Measures &mc) {
|
||||
std::ostringstream oss;
|
||||
|
||||
// CO2
|
||||
if (utils::isValidCO2(mc.co2)) {
|
||||
oss << std::round(mc.co2);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
// 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);
|
||||
} else if (utils::isValidTemperature(mc.temperature[0])) {
|
||||
oss << std::round(mc.temperature[0] * 10);
|
||||
} else if (utils::isValidTemperature(mc.temperature[1])) {
|
||||
oss << std::round(mc.temperature[1] * 10);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
// Humidity
|
||||
if (utils::isValidHumidity(mc.humidity[0]) && utils::isValidHumidity(mc.humidity[1])) {
|
||||
float hum = (mc.humidity[0] + mc.humidity[1]) / 2.0f;
|
||||
oss << std::round(hum * 10);
|
||||
} else if (utils::isValidHumidity(mc.humidity[0])) {
|
||||
oss << std::round(mc.humidity[0] * 10);
|
||||
} else if (utils::isValidHumidity(mc.humidity[1])) {
|
||||
oss << std::round(mc.humidity[1] * 10);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
/// PM1.0 atmospheric environment
|
||||
if (utils::isValidPm(mc.pm_01[0]) && utils::isValidPm(mc.pm_01[1])) {
|
||||
float pm01 = (mc.pm_01[0] + mc.pm_01[1]) / 2.0f;
|
||||
oss << std::round(pm01 * 10);
|
||||
} else if (utils::isValidPm(mc.pm_01[0])) {
|
||||
oss << std::round(mc.pm_01[0] * 10);
|
||||
} else if (utils::isValidPm(mc.pm_01[1])) {
|
||||
oss << std::round(mc.pm_01[1] * 10);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
/// PM2.5 atmospheric environment
|
||||
if (utils::isValidPm(mc.pm_25[0]) && utils::isValidPm(mc.pm_25[1])) {
|
||||
float pm25 = (mc.pm_25[0] + mc.pm_25[1]) / 2.0f;
|
||||
oss << std::round(pm25 * 10);
|
||||
} else if (utils::isValidPm(mc.pm_25[0])) {
|
||||
oss << std::round(mc.pm_25[0] * 10);
|
||||
} else if (utils::isValidPm(mc.pm_25[1])) {
|
||||
oss << std::round(mc.pm_25[1] * 10);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
/// PM10 atmospheric environment
|
||||
if (utils::isValidPm(mc.pm_10[0]) && utils::isValidPm(mc.pm_10[1])) {
|
||||
float pm10 = (mc.pm_10[0] + mc.pm_10[1]) / 2.0f;
|
||||
oss << std::round(pm10 * 10);
|
||||
} else if (utils::isValidPm(mc.pm_10[0])) {
|
||||
oss << std::round(mc.pm_10[0] * 10);
|
||||
} else if (utils::isValidPm(mc.pm_10[1])) {
|
||||
oss << std::round(mc.pm_10[1] * 10);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
// NOx
|
||||
if (utils::isValidNOx(mc.nox)) {
|
||||
oss << std::round(mc.nox);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
// TVOC
|
||||
if (utils::isValidVOC(mc.tvoc)) {
|
||||
oss << std::round(mc.tvoc);
|
||||
}
|
||||
|
||||
oss << ",";
|
||||
|
||||
/// PM 0.3 particle count
|
||||
if (utils::isValidPm03Count(mc.pm_03_pc[0]) && utils::isValidPm03Count(mc.pm_03_pc[1])) {
|
||||
oss << std::round((mc.pm_03_pc[0] + mc.pm_03_pc[1]) / 2.0f);
|
||||
} else if (utils::isValidPm03Count(mc.pm_03_pc[0])) {
|
||||
oss << std::round(mc.pm_03_pc[0]);
|
||||
} else if (utils::isValidPm03Count(mc.pm_03_pc[1])) {
|
||||
oss << std::round(mc.pm_03_pc[1]);
|
||||
}
|
||||
|
||||
// char datapoint[128] = {0};
|
||||
// snprintf(datapoint, 128, "%d,%.0f,%.0f,%.0f,%.0f,%.0f,%d,%d,%d", co2, temp * 10,
|
||||
// hum * 10, pm01 * 10, pm25 * 10, pm10 * 10, tvoc, nox, pm003Count);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
String Measurements::toString(bool localServer, AgFirmwareMode fwMode, int rssi) {
|
||||
JSONVar root;
|
||||
|
||||
@ -1219,4 +1368,4 @@ void Measurements::setResetReason(esp_reset_reason_t reason) {
|
||||
|
||||
_resetReason = (int)reason;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
||||
#include "Main/utils.h"
|
||||
#include <Arduino.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class Measurements {
|
||||
@ -37,6 +38,31 @@ public:
|
||||
Measurements(Configuration &config);
|
||||
~Measurements() {}
|
||||
|
||||
struct Measures {
|
||||
float temperature[2];
|
||||
float humidity[2];
|
||||
float co2;
|
||||
float tvoc; // Index value
|
||||
float tvoc_raw;
|
||||
float nox; // Index value
|
||||
float nox_raw;
|
||||
float pm_01[2]; // pm 1.0 atmospheric environment
|
||||
float pm_25[2]; // pm 2.5 atmospheric environment
|
||||
float pm_10[2]; // pm 10 atmospheric environment
|
||||
float pm_01_sp[2]; // pm 1.0 standard particle
|
||||
float pm_25_sp[2]; // pm 2.5 standard particle
|
||||
float pm_10_sp[2]; // pm 10 standard particle
|
||||
float pm_03_pc[2]; // particle count 0.3
|
||||
float pm_05_pc[2]; // particle count 0.5
|
||||
float pm_01_pc[2]; // particle count 1.0
|
||||
float pm_25_pc[2]; // particle count 2.5
|
||||
float pm_5_pc[2]; // particle count 5.0
|
||||
float pm_10_pc[2]; // particle count 10
|
||||
int bootCount;
|
||||
int signal;
|
||||
uint32_t freeHeap;
|
||||
};
|
||||
|
||||
void setAirGradient(AirGradient *ag);
|
||||
|
||||
// Enumeration for every AG measurements
|
||||
@ -154,6 +180,10 @@ public:
|
||||
*/
|
||||
String toString(bool localServer, AgFirmwareMode fwMode, int rssi);
|
||||
|
||||
Measures getMeasures();
|
||||
|
||||
std::string buildMeasuresPayload(Measures &measures);
|
||||
|
||||
/**
|
||||
* Set to true if want to debug every update value
|
||||
*/
|
||||
|
@ -15,9 +15,10 @@
|
||||
#include "Main/utils.h"
|
||||
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "3.1.21-snap"
|
||||
#define GIT_VERSION "3.3.0-snap"
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef ESP8266
|
||||
// Airgradient server root ca certificate
|
||||
const char *const AG_SERVER_ROOT_CA =
|
||||
|
1
src/Libraries/airgradient-client
Submodule
1
src/Libraries/airgradient-ota
Submodule
@ -1,171 +0,0 @@
|
||||
#include "OtaHandler.h"
|
||||
|
||||
#ifndef ESP8266 // Only for esp32 based mcu
|
||||
|
||||
#include "AirGradient.h"
|
||||
|
||||
void OtaHandler::setHandlerCallback(OtaHandlerCallback_t callback) { _callback = callback; }
|
||||
|
||||
void OtaHandler::updateFirmwareIfOutdated(String deviceId) {
|
||||
String url =
|
||||
"https://hw.airgradient.com/sensors/airgradient:" + deviceId + "/generic/os/firmware.bin";
|
||||
url += "?current_firmware=";
|
||||
url += GIT_VERSION;
|
||||
char urlAsChar[URL_BUF_SIZE];
|
||||
url.toCharArray(urlAsChar, URL_BUF_SIZE);
|
||||
Serial.printf("checking for new OTA update @ %s\n", urlAsChar);
|
||||
|
||||
esp_http_client_config_t config = {};
|
||||
config.url = urlAsChar;
|
||||
config.cert_pem = AG_SERVER_ROOT_CA;
|
||||
OtaUpdateOutcome ret = attemptToPerformOta(&config);
|
||||
Serial.println(ret);
|
||||
if (_callback) {
|
||||
switch (ret) {
|
||||
case OtaUpdateOutcome::UPDATE_PERFORMED:
|
||||
_callback(OtaState::OTA_STATE_SUCCESS, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::UPDATE_SKIPPED:
|
||||
_callback(OtaState::OTA_STATE_SKIP, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::ALREADY_UP_TO_DATE:
|
||||
_callback(OtaState::OTA_STATE_UP_TO_DATE, "");
|
||||
break;
|
||||
case OtaUpdateOutcome::UPDATE_FAILED:
|
||||
_callback(OtaState::OTA_STATE_FAIL, "");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OtaHandler::OtaUpdateOutcome
|
||||
OtaHandler::attemptToPerformOta(const esp_http_client_config_t *config) {
|
||||
esp_http_client_handle_t client = esp_http_client_init(config);
|
||||
if (client == NULL) {
|
||||
Serial.println("Failed to initialize HTTP connection");
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_http_client_open(client, 0);
|
||||
if (err != ESP_OK) {
|
||||
esp_http_client_cleanup(client);
|
||||
Serial.printf("Failed to open HTTP connection: %s\n", esp_err_to_name(err));
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
esp_http_client_fetch_headers(client);
|
||||
|
||||
int httpStatusCode = esp_http_client_get_status_code(client);
|
||||
if (httpStatusCode == 304) {
|
||||
Serial.println("Firmware is already up to date");
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::ALREADY_UP_TO_DATE;
|
||||
} else if (httpStatusCode != 200) {
|
||||
Serial.printf("Firmware update skipped, the server returned %d\n", httpStatusCode);
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::UPDATE_SKIPPED;
|
||||
}
|
||||
|
||||
esp_ota_handle_t update_handle = 0;
|
||||
const esp_partition_t *update_partition = NULL;
|
||||
Serial.println("Starting OTA update ...");
|
||||
update_partition = esp_ota_get_next_update_partition(NULL);
|
||||
if (update_partition == NULL) {
|
||||
Serial.println("Passive OTA partition not found");
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
Serial.printf("Writing to partition subtype %d at offset 0x%x\n", update_partition->subtype,
|
||||
update_partition->address);
|
||||
|
||||
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("esp_ota_begin failed, error=%d\n", err);
|
||||
cleanupHttp(client);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
esp_err_t ota_write_err = ESP_OK;
|
||||
char *upgrade_data_buf = (char *)malloc(OTA_BUF_SIZE);
|
||||
if (!upgrade_data_buf) {
|
||||
Serial.println("Couldn't allocate memory for data buffer");
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
int binary_file_len = 0;
|
||||
int totalSize = esp_http_client_get_content_length(client);
|
||||
Serial.println("File size: " + String(totalSize) + String(" bytes"));
|
||||
|
||||
// Show display start update new firmware.
|
||||
if (_callback) {
|
||||
_callback(OtaState::OTA_STATE_BEGIN, "");
|
||||
}
|
||||
|
||||
// Download file and write new firmware to OTA partition
|
||||
uint32_t lastUpdate = millis();
|
||||
while (1) {
|
||||
int data_read = esp_http_client_read(client, upgrade_data_buf, OTA_BUF_SIZE);
|
||||
if (data_read == 0) {
|
||||
if (_callback) {
|
||||
_callback(OtaState::OTA_STATE_PROCESSING, String(100));
|
||||
}
|
||||
Serial.println("Connection closed, all data received");
|
||||
break;
|
||||
}
|
||||
if (data_read < 0) {
|
||||
Serial.println("Data read error");
|
||||
if (_callback) {
|
||||
_callback(OtaState::OTA_STATE_FAIL, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (data_read > 0) {
|
||||
ota_write_err = esp_ota_write(update_handle, (const void *)upgrade_data_buf, data_read);
|
||||
if (ota_write_err != ESP_OK) {
|
||||
if (_callback) {
|
||||
_callback(OtaState::OTA_STATE_FAIL, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
binary_file_len += data_read;
|
||||
|
||||
int percent = (binary_file_len * 100) / totalSize;
|
||||
uint32_t ms = (uint32_t)(millis() - lastUpdate);
|
||||
if (ms >= 250) {
|
||||
// sm.executeOTA(StateMachine::OtaState::OTA_STATE_PROCESSING, "",
|
||||
// percent);
|
||||
if (_callback) {
|
||||
_callback(OtaState::OTA_STATE_PROCESSING, String(percent));
|
||||
}
|
||||
lastUpdate = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
free(upgrade_data_buf);
|
||||
cleanupHttp(client);
|
||||
Serial.printf("# of bytes written: %d\n", binary_file_len);
|
||||
|
||||
esp_err_t ota_end_err = esp_ota_end(update_handle);
|
||||
if (ota_write_err != ESP_OK) {
|
||||
Serial.printf("Error: esp_ota_write failed! err=0x%d\n", err);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
} else if (ota_end_err != ESP_OK) {
|
||||
Serial.printf("Error: esp_ota_end failed! err=0x%d. Image is invalid", ota_end_err);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
err = esp_ota_set_boot_partition(update_partition);
|
||||
if (err != ESP_OK) {
|
||||
Serial.printf("esp_ota_set_boot_partition failed! err=0x%d\n", err);
|
||||
return OtaUpdateOutcome::UPDATE_FAILED;
|
||||
}
|
||||
return OtaUpdateOutcome::UPDATE_PERFORMED;
|
||||
}
|
||||
|
||||
void OtaHandler::cleanupHttp(esp_http_client_handle_t client) {
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,43 +0,0 @@
|
||||
#ifndef OTA_HANDLER_H
|
||||
#define OTA_HANDLER_H
|
||||
#ifndef ESP8266 // Only for esp32 based mcu
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_http_client.h>
|
||||
#include <esp_ota_ops.h>
|
||||
|
||||
#define OTA_BUF_SIZE 1024
|
||||
#define URL_BUF_SIZE 256
|
||||
|
||||
class OtaHandler {
|
||||
public:
|
||||
enum OtaState {
|
||||
OTA_STATE_BEGIN,
|
||||
OTA_STATE_FAIL,
|
||||
OTA_STATE_SKIP,
|
||||
OTA_STATE_UP_TO_DATE,
|
||||
OTA_STATE_PROCESSING,
|
||||
OTA_STATE_SUCCESS
|
||||
};
|
||||
|
||||
typedef void (*OtaHandlerCallback_t)(OtaState state, String message);
|
||||
void setHandlerCallback(OtaHandlerCallback_t callback);
|
||||
void updateFirmwareIfOutdated(String deviceId);
|
||||
|
||||
private:
|
||||
OtaHandlerCallback_t _callback;
|
||||
|
||||
enum OtaUpdateOutcome {
|
||||
UPDATE_PERFORMED = 0,
|
||||
ALREADY_UP_TO_DATE,
|
||||
UPDATE_FAILED,
|
||||
UPDATE_SKIPPED
|
||||
}; // Internal use
|
||||
|
||||
OtaUpdateOutcome attemptToPerformOta(const esp_http_client_config_t *config);
|
||||
void cleanupHttp(esp_http_client_handle_t client);
|
||||
};
|
||||
|
||||
#endif // ESP8266
|
||||
#endif // OTA_HANDLER_H
|