mirror of
https://github.com/airgradienthq/arduino.git
synced 2025-12-17 19:18:27 +01:00
Compare commits
73 Commits
3.3.6
...
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 | ||
|
|
5802cf17f6 | ||
|
|
4a4ce89f00 | ||
|
|
8985e08a00 | ||
|
|
e984aced18 | ||
|
|
73edf56c97 | ||
|
|
e54c62a2ef | ||
|
|
5c95f011e4 | ||
|
|
bed448e7d6 | ||
|
|
eb8378adfa | ||
|
|
f94ed5c5f5 | ||
|
|
9471f3e323 | ||
|
|
e6d90372c2 | ||
|
|
fabf0550fc | ||
|
|
ce0af5bf60 | ||
|
|
93bdbd85d5 | ||
|
|
29f583f20d | ||
|
|
0f6a2fe908 | ||
|
|
e3ce2c41be | ||
|
|
92b3c69b98 | ||
|
|
f4d518aa87 | ||
|
|
831c844c24 | ||
|
|
060a7f6815 | ||
|
|
d8eb6b3c1a | ||
|
|
77859bea22 | ||
|
|
969858b5cb | ||
|
|
09b5805686 | ||
|
|
b09b753339 | ||
|
|
ddb3dba131 | ||
|
|
e780b0ace6 | ||
|
|
e82da5401e | ||
|
|
50a98acde4 | ||
|
|
7049d21a41 | ||
|
|
d5cdeaa9f3 | ||
|
|
09207c6923 | ||
|
|
0a64424196 | ||
|
|
5b38ca222b | ||
|
|
9ee35341a5 | ||
|
|
cec0514444 | ||
|
|
21b9ddb2ed | ||
|
|
1ee05da5d1 | ||
|
|
626a2240fa | ||
|
|
174ec6568f | ||
|
|
6b55719399 | ||
|
|
e2084f0738 | ||
|
|
5e07923690 | ||
|
|
04049439b1 | ||
|
|
c148d256d7 | ||
|
|
02849a1938 | ||
|
|
074337a96d | ||
|
|
4daa817a0b | ||
|
|
81a4502952 |
31
.github/workflows/check.yml
vendored
31
.github/workflows/check.yml
vendored
@@ -1,5 +1,36 @@
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
jobs:
|
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:
|
compile:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|||||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
|||||||
[submodule "src/Libraries/airgradient-client"]
|
[submodule "src/Libraries/airgradient-client"]
|
||||||
path = src/Libraries/airgradient-client
|
path = src/Libraries/airgradient-client
|
||||||
url = git@github.com:airgradienthq/airgradient-client.git
|
url = ../../airgradienthq/airgradient-client.git
|
||||||
[submodule "src/Libraries/airgradient-ota"]
|
[submodule "src/Libraries/airgradient-ota"]
|
||||||
path = src/Libraries/airgradient-ota
|
path = src/Libraries/airgradient-ota
|
||||||
url = git@github.com:airgradienthq/airgradient-ota.git
|
url = ../../airgradienthq/airgradient-ota.git
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -20,13 +20,32 @@ Make sure you have exactly the versions of libraries and boards installed as des
|
|||||||
|
|
||||||
If you have an older version of the AirGradient PCB not mentioned in the example files, please downgrade this library to version 2.4.15 to support these legacy boards.
|
If you have an older version of the AirGradient PCB not mentioned in the example files, please downgrade this library to version 2.4.15 to support these legacy boards.
|
||||||
|
|
||||||
|
### Release Process
|
||||||
|
|
||||||
|
Releases published on GitHub are **not immediately deployed to all devices in the market**. Each release first goes through internal testing, including limited deployments in select locations to verify stability and functionality.
|
||||||
|
|
||||||
|
If the tests pass, the firmware is then made available for:
|
||||||
|
- **FOTA (Firmware Over-The-Air) updates** from AirGradient dashboard
|
||||||
|
- **Manual flashing** via [Airgradient](https://www.airgradient.com/documentation/firmwares/) website
|
||||||
|
|
||||||
|
Each GitHub release note will also include the planned rollout date for wider availability.
|
||||||
|
|
||||||
## Help & Support
|
## 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/).
|
||||||
|
|
||||||
## Documentation
|
## Development
|
||||||
|
|
||||||
Local server API documentation is available in [/docs/local-server.md](/docs/local-server.md) and AirGradient server API on [https://api.airgradient.com/public/docs/api/v1/](https://api.airgradient.com/public/docs/api/v1/).
|
* See [compilation instructions](/docs/howto-compile.md) for details about how to customize AirGradient's firmware and flash it to your device.
|
||||||
|
|
||||||
|
## Over the air (OTA) updates
|
||||||
|
|
||||||
|
* See the [OTA Updates documentation](/docs/ota-updates.md) for details about how AirGradient monitors receive over the air updates.
|
||||||
|
|
||||||
|
## API documentation
|
||||||
|
|
||||||
|
* [Local server API documentation](/docs/local-server.md)
|
||||||
|
* [AirGradient Cloud server API documentation](https://api.airgradient.com/public/docs/api/v1/).
|
||||||
|
|
||||||
## The following libraries have been integrated into this library for ease of use
|
## The following libraries have been integrated into this library for ease of use
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Choose based on how python installed on your machine. But most user, using `apt`
|
|||||||
|
|
||||||
## How to contribute
|
## 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.
|
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 which you plan to contribute back to the main project, instead of installing the AirGradient library, check out the repository at Documents/Arduino/libraries (for Windows and Mac) or ~/Arduino/libraries (for 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`
|
**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`
|
||||||
|
|
||||||
@@ -100,6 +100,3 @@ There are 2 environment options to compile this project, PlatformIO and ArduinoI
|
|||||||
|
|
||||||
- For PlatformIO, it should work out of the box
|
- 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
|
- 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ Compensated values apply correction algorithms to make the sensor values more ac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Set Configuration Parameters (PUT)
|
### Set Configuration Parameters (PUT)
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ static bool sgp41Init(void) {
|
|||||||
configuration.hasSensorSGP = true;
|
configuration.hasSensorSGP = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Init SGP41 failuire");
|
Serial.println("Init SGP41 failure");
|
||||||
configuration.hasSensorSGP = false;
|
configuration.hasSensorSGP = false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ static bool sgp41Init(void) {
|
|||||||
configuration.hasSensorSGP = true;
|
configuration.hasSensorSGP = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Init SGP41 failuire");
|
Serial.println("Init SGP41 failure");
|
||||||
configuration.hasSensorSGP = false;
|
configuration.hasSensorSGP = false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ static bool sgp41Init(void) {
|
|||||||
configuration.hasSensorSGP = true;
|
configuration.hasSensorSGP = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Init SGP41 failuire");
|
Serial.println("Init SGP41 failure");
|
||||||
configuration.hasSensorSGP = false;
|
configuration.hasSensorSGP = false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -100,21 +100,15 @@ static Configuration configuration(Serial);
|
|||||||
static Measurements measurements(configuration);
|
static Measurements measurements(configuration);
|
||||||
static AirGradient *ag;
|
static AirGradient *ag;
|
||||||
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
static OledDisplay oledDisplay(configuration, measurements, Serial);
|
||||||
static StateMachine stateMachine(oledDisplay, Serial, measurements,
|
static StateMachine stateMachine(oledDisplay, Serial, measurements, configuration);
|
||||||
configuration);
|
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine, configuration);
|
||||||
static WifiConnector wifiConnector(oledDisplay, Serial, stateMachine,
|
|
||||||
configuration);
|
|
||||||
static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
|
static OpenMetrics openMetrics(measurements, configuration, wifiConnector);
|
||||||
static LocalServer localServer(Serial, openMetrics, measurements, configuration,
|
static LocalServer localServer(Serial, openMetrics, measurements, configuration, wifiConnector);
|
||||||
wifiConnector);
|
|
||||||
static AgSerial *agSerial;
|
static AgSerial *agSerial;
|
||||||
static CellularModule *cellularCard;
|
static CellularModule *cellularCard;
|
||||||
static AirgradientClient *agClient;
|
static AirgradientClient *agClient;
|
||||||
|
|
||||||
enum NetworkOption {
|
enum NetworkOption { UseWifi, UseCellular };
|
||||||
UseWifi,
|
|
||||||
UseCellular
|
|
||||||
};
|
|
||||||
NetworkOption networkOption;
|
NetworkOption networkOption;
|
||||||
TaskHandle_t handleNetworkTask = NULL;
|
TaskHandle_t handleNetworkTask = NULL;
|
||||||
static bool firmwareUpdateInProgress = false;
|
static bool firmwareUpdateInProgress = false;
|
||||||
@@ -143,6 +137,7 @@ static void updatePm(void);
|
|||||||
static void sendDataToServer(void);
|
static void sendDataToServer(void);
|
||||||
static void tempHumUpdate(void);
|
static void tempHumUpdate(void);
|
||||||
static void co2Update(void);
|
static void co2Update(void);
|
||||||
|
static void printMeasurements();
|
||||||
static void mdnsInit(void);
|
static void mdnsInit(void);
|
||||||
static void createMqttTask(void);
|
static void createMqttTask(void);
|
||||||
static void initMqtt(void);
|
static void initMqtt(void);
|
||||||
@@ -161,8 +156,7 @@ static void networkSignalCheck();
|
|||||||
static void networkingTask(void *args);
|
static void networkingTask(void *args);
|
||||||
|
|
||||||
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
AgSchedule dispLedSchedule(DISP_UPDATE_INTERVAL, updateDisplayAndLedBar);
|
||||||
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL,
|
AgSchedule configSchedule(WIFI_SERVER_CONFIG_SYNC_INTERVAL, configurationUpdateSchedule);
|
||||||
configurationUpdateSchedule);
|
|
||||||
AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer);
|
AgSchedule transmissionSchedule(WIFI_TRANSMISSION_INTERVAL, sendDataToServer);
|
||||||
AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle);
|
AgSchedule measurementSchedule(WIFI_MEASUREMENT_INTERVAL, newMeasurementCycle);
|
||||||
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
AgSchedule co2Schedule(SENSOR_CO2_UPDATE_INTERVAL, co2Update);
|
||||||
@@ -172,6 +166,7 @@ AgSchedule tvocSchedule(SENSOR_TVOC_UPDATE_INTERVAL, updateTvoc);
|
|||||||
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
AgSchedule watchdogFeedSchedule(60000, wdgFeedUpdate);
|
||||||
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmwareUpdate);
|
AgSchedule checkForUpdateSchedule(FIRMWARE_CHECK_FOR_UPDATE_MS, checkForFirmwareUpdate);
|
||||||
AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck);
|
AgSchedule networkSignalCheckSchedule(10000, networkSignalCheck);
|
||||||
|
AgSchedule printMeasurementsSchedule(6000, printMeasurements);
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
/** Serial for print debug message */
|
/** Serial for print debug message */
|
||||||
@@ -191,6 +186,7 @@ void setup() {
|
|||||||
|
|
||||||
/** Initialize local configure */
|
/** Initialize local configure */
|
||||||
configuration.begin();
|
configuration.begin();
|
||||||
|
configuration.setConfigurationUpdatedCallback(configUpdateHandle);
|
||||||
|
|
||||||
/** Init I2C */
|
/** Init I2C */
|
||||||
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
|
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
|
||||||
@@ -210,7 +206,7 @@ void setup() {
|
|||||||
oledDisplay.setAirGradient(ag);
|
oledDisplay.setAirGradient(ag);
|
||||||
stateMachine.setAirGradient(ag);
|
stateMachine.setAirGradient(ag);
|
||||||
wifiConnector.setAirGradient(ag);
|
wifiConnector.setAirGradient(ag);
|
||||||
openMetrics.setAirGradient(ag, agClient);
|
openMetrics.setAirGradient(ag);
|
||||||
localServer.setAirGraident(ag);
|
localServer.setAirGraident(ag);
|
||||||
measurements.setAirGradient(ag);
|
measurements.setAirGradient(ag);
|
||||||
|
|
||||||
@@ -218,24 +214,19 @@ void setup() {
|
|||||||
boardInit();
|
boardInit();
|
||||||
setMeasurementMaxPeriod();
|
setMeasurementMaxPeriod();
|
||||||
|
|
||||||
// Comment below line to disable debug measurement readings
|
|
||||||
measurements.setDebug(true);
|
|
||||||
|
|
||||||
bool connectToNetwork = true;
|
bool connectToNetwork = true;
|
||||||
if (ag->isOne()) { // Offline mode only available for indoor monitor
|
if (ag->isOne()) { // Offline mode only available for indoor monitor
|
||||||
/** Show message confirm offline mode, should me perform if LED bar button
|
/** Show message confirm offline mode, should me perform if LED bar button
|
||||||
* test pressed */
|
* test pressed */
|
||||||
if (ledBarButtonTest == false) {
|
if (ledBarButtonTest == false) {
|
||||||
oledDisplay.setText(
|
oledDisplay.setText("Press now for",
|
||||||
"Press now for",
|
|
||||||
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
configuration.isOfflineMode() ? "online mode" : "offline mode", "");
|
||||||
uint32_t startTime = millis();
|
uint32_t startTime = millis();
|
||||||
while (true) {
|
while (true) {
|
||||||
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
|
if (ag->button.getState() == ag->button.BUTTON_PRESSED) {
|
||||||
configuration.setOfflineMode(!configuration.isOfflineMode());
|
configuration.setOfflineMode(!configuration.isOfflineMode());
|
||||||
|
|
||||||
oledDisplay.setText(
|
oledDisplay.setText("Offline Mode",
|
||||||
"Offline Mode",
|
|
||||||
configuration.isOfflineMode() ? " = True" : " = False", "");
|
configuration.isOfflineMode() ? " = True" : " = False", "");
|
||||||
delay(1000);
|
delay(1000);
|
||||||
break;
|
break;
|
||||||
@@ -256,12 +247,7 @@ void setup() {
|
|||||||
if (connectToNetwork) {
|
if (connectToNetwork) {
|
||||||
oledDisplay.setText("Initialize", "network...", "");
|
oledDisplay.setText("Initialize", "network...", "");
|
||||||
initializeNetwork();
|
initializeNetwork();
|
||||||
}
|
wifiConnector.stopBLE();
|
||||||
|
|
||||||
/** 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Show display Warning up */
|
/** Show display Warning up */
|
||||||
@@ -271,9 +257,9 @@ void setup() {
|
|||||||
|
|
||||||
Serial.println("Display brightness: " + String(configuration.getDisplayBrightness()));
|
Serial.println("Display brightness: " + String(configuration.getDisplayBrightness()));
|
||||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||||
|
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (networkOption == UseCellular) {
|
if (networkOption == UseCellular) {
|
||||||
// If using cellular re-set scheduler interval
|
// If using cellular re-set scheduler interval
|
||||||
configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL);
|
configSchedule.setPeriod(CELLULAR_SERVER_CONFIG_SYNC_INTERVAL);
|
||||||
@@ -301,11 +287,9 @@ void setup() {
|
|||||||
// Log monitor mode for debugging purpose
|
// Log monitor mode for debugging purpose
|
||||||
if (configuration.isOfflineMode()) {
|
if (configuration.isOfflineMode()) {
|
||||||
Serial.println("Running monitor in offline mode");
|
Serial.println("Running monitor in offline mode");
|
||||||
}
|
} else if (configuration.isCloudConnectionDisabled()) {
|
||||||
else if (configuration.isCloudConnectionDisabled()) {
|
|
||||||
Serial.println("Running monitor without connection to AirGradient server");
|
Serial.println("Running monitor without connection to AirGradient server");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
@@ -352,7 +336,7 @@ void loop() {
|
|||||||
static bool pmsConnected = false;
|
static bool pmsConnected = false;
|
||||||
if (pmsConnected != ag->pms5003.connected()) {
|
if (pmsConnected != ag->pms5003.connected()) {
|
||||||
pmsConnected = ag->pms5003.connected();
|
pmsConnected = ag->pms5003.connected();
|
||||||
Serial.printf("PMS sensor %s ", pmsConnected?"connected":"removed");
|
Serial.printf("PMS sensor %s \n", pmsConnected ? "connected" : "removed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -364,11 +348,17 @@ void loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Run measurement schedule */
|
||||||
|
printMeasurementsSchedule.run();
|
||||||
|
|
||||||
/** factory reset handle */
|
/** factory reset handle */
|
||||||
factoryConfigReset();
|
factoryConfigReset();
|
||||||
|
|
||||||
/** check that local configuration changed then do some action */
|
if (configuration.isCommandRequested()) {
|
||||||
configUpdateHandle();
|
// Each state machine already has an independent request command check
|
||||||
|
stateMachine.executeCo2Calibration();
|
||||||
|
stateMachine.executeLedBarTest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void co2Update(void) {
|
static void co2Update(void) {
|
||||||
@@ -385,6 +375,8 @@ static void co2Update(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void printMeasurements() { measurements.printCurrentAverage(); }
|
||||||
|
|
||||||
static void mdnsInit(void) {
|
static void mdnsInit(void) {
|
||||||
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
if (!MDNS.begin(localServer.getHostname().c_str())) {
|
||||||
Serial.println("Init mDNS failed");
|
Serial.println("Init mDNS failed");
|
||||||
@@ -392,8 +384,7 @@ static void mdnsInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MDNS.addService("_airgradient", "_tcp", 80);
|
MDNS.addService("_airgradient", "_tcp", 80);
|
||||||
MDNS.addServiceTxt("_airgradient", "_tcp", "model",
|
MDNS.addServiceTxt("_airgradient", "_tcp", "model", AgFirmwareModeName(fwMode));
|
||||||
AgFirmwareModeName(fwMode));
|
|
||||||
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId());
|
MDNS.addServiceTxt("_airgradient", "_tcp", "serialno", ag->deviceId());
|
||||||
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion());
|
MDNS.addServiceTxt("_airgradient", "_tcp", "fw_ver", ag->getVersion());
|
||||||
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
MDNS.addServiceTxt("_airgradient", "_tcp", "vendor", "AirGradient");
|
||||||
@@ -417,8 +408,7 @@ static void createMqttTask(void) {
|
|||||||
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
|
String payload = measurements.toString(true, fwMode, wifiConnector.RSSI());
|
||||||
String topic = "airgradient/readings/" + ag->deviceId();
|
String topic = "airgradient/readings/" + ag->deviceId();
|
||||||
|
|
||||||
if (mqttClient.publish(topic.c_str(), payload.c_str(),
|
if (mqttClient.publish(topic.c_str(), payload.c_str(), payload.length())) {
|
||||||
payload.length())) {
|
|
||||||
Serial.println("MQTT sync success");
|
Serial.println("MQTT sync success");
|
||||||
} else {
|
} else {
|
||||||
Serial.println("MQTT sync failure");
|
Serial.println("MQTT sync failure");
|
||||||
@@ -436,8 +426,7 @@ static void createMqttTask(void) {
|
|||||||
static void initMqtt(void) {
|
static void initMqtt(void) {
|
||||||
String mqttUri = configuration.getMqttBrokerUri();
|
String mqttUri = configuration.getMqttBrokerUri();
|
||||||
if (mqttUri.isEmpty()) {
|
if (mqttUri.isEmpty()) {
|
||||||
Serial.println(
|
Serial.println("MQTT is not configured, skipping initialization of MQTT client");
|
||||||
"MQTT is not configured, skipping initialization of MQTT client");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,25 +537,38 @@ static bool sgp41Init(void) {
|
|||||||
configuration.hasSensorSGP = true;
|
configuration.hasSensorSGP = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Init SGP41 failuire");
|
Serial.println("Init SGP41 failure");
|
||||||
configuration.hasSensorSGP = false;
|
configuration.hasSensorSGP = false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkForFirmwareUpdate(void) {
|
void checkForFirmwareUpdate(void) {
|
||||||
|
if (configuration.isCloudConnectionDisabled()) {
|
||||||
|
Serial.println("Cloud connection is disabled, skip firmware update");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AirgradientOTA *agOta;
|
AirgradientOTA *agOta;
|
||||||
if (networkOption == UseWifi) {
|
if (networkOption == UseWifi) {
|
||||||
agOta = new AirgradientOTAWifi;
|
agOta = new AirgradientOTAWifi;
|
||||||
} else {
|
} else {
|
||||||
agOta = new AirgradientOTACellular(cellularCard);
|
agOta = new AirgradientOTACellular(cellularCard, agClient->getICCID());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicate main task that firmware update is in progress
|
// Indicate main task that firmware update is in progress
|
||||||
firmwareUpdateInProgress = true;
|
firmwareUpdateInProgress = true;
|
||||||
|
|
||||||
agOta->setHandlerCallback(otaHandlerCallback);
|
agOta->setHandlerCallback(otaHandlerCallback);
|
||||||
|
|
||||||
|
String httpDomain = configuration.getHttpDomain();
|
||||||
|
if (httpDomain != "") {
|
||||||
|
Serial.printf("httpDomain configuration available, start OTA with custom domain\n",
|
||||||
|
httpDomain.c_str());
|
||||||
|
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION, httpDomain.c_str());
|
||||||
|
} else {
|
||||||
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION);
|
agOta->updateIfAvailable(ag->deviceId().c_str(), GIT_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
// Only goes to this line if firmware update is not success
|
// Only goes to this line if firmware update is not success
|
||||||
// Handled by otaHandlerCallback
|
// Handled by otaHandlerCallback
|
||||||
@@ -683,6 +685,7 @@ static void sendDataToAg() {
|
|||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
|
||||||
}
|
}
|
||||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting);
|
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting);
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_CONNECTING_TO_SERVER);
|
||||||
|
|
||||||
/** Task handle led connecting animation */
|
/** Task handle led connecting animation */
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
@@ -690,8 +693,7 @@ static void sendDataToAg() {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
// ledSmHandler();
|
// ledSmHandler();
|
||||||
stateMachine.handleLeds();
|
stateMachine.handleLeds();
|
||||||
if (stateMachine.getLedState() !=
|
if (stateMachine.getLedState() != AgStateMachineWiFiOkServerConnecting) {
|
||||||
AgStateMachineWiFiOkServerConnecting) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delay(LED_BAR_ANIMATION_PERIOD);
|
delay(LED_BAR_ANIMATION_PERIOD);
|
||||||
@@ -712,11 +714,13 @@ static void sendDataToAg() {
|
|||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
|
||||||
}
|
}
|
||||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected);
|
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected);
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_SERVER_REACHABLE);
|
||||||
} else {
|
} else {
|
||||||
if (ag->isOne()) {
|
if (ag->isOne()) {
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
|
||||||
}
|
}
|
||||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed);
|
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed);
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_ERR_SERVER_UNREACHABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMachine.handleLeds(AgStateMachineNormal);
|
stateMachine.handleLeds(AgStateMachineNormal);
|
||||||
@@ -737,8 +741,7 @@ static void oneIndoorInit(void) {
|
|||||||
/** Show boot display */
|
/** Show boot display */
|
||||||
Serial.println("Firmware Version: " + ag->getVersion());
|
Serial.println("Firmware Version: " + ag->getVersion());
|
||||||
|
|
||||||
oledDisplay.setText("AirGradient ONE",
|
oledDisplay.setText("AirGradient ONE", "FW Version: ", ag->getVersion().c_str());
|
||||||
"FW Version: ", ag->getVersion().c_str());
|
|
||||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||||
|
|
||||||
ag->ledBar.begin();
|
ag->ledBar.begin();
|
||||||
@@ -897,8 +900,7 @@ static void openAirInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fwMode == FW_MODE_O_1PP) {
|
if (fwMode == FW_MODE_O_1PP) {
|
||||||
int count = (configuration.hasSensorPMS1 ? 1 : 0) +
|
int count = (configuration.hasSensorPMS1 ? 1 : 0) + (configuration.hasSensorPMS2 ? 1 : 0);
|
||||||
(configuration.hasSensorPMS2 ? 1 : 0);
|
|
||||||
if (count == 1) {
|
if (count == 1) {
|
||||||
fwMode = FW_MODE_O_1P;
|
fwMode = FW_MODE_O_1P;
|
||||||
}
|
}
|
||||||
@@ -981,30 +983,29 @@ void initializeNetwork() {
|
|||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Provide openmetrics to have access to last transmission result
|
||||||
|
openMetrics.setAirgradientClient(agClient);
|
||||||
|
|
||||||
if (networkOption == UseCellular) {
|
if (networkOption == UseCellular) {
|
||||||
// Disabling it again
|
// Disabling it again
|
||||||
agSerial->setDebug(false);
|
agSerial->setDebug(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (networkOption == UseWifi) {
|
if (networkOption == UseWifi) {
|
||||||
if (!wifiConnector.connect()) {
|
String modelName = AgFirmwareModeName(fwMode);
|
||||||
|
if (!wifiConnector.connect(modelName)) {
|
||||||
Serial.println("Cannot initiate wifi connection");
|
Serial.println("Cannot initiate wifi connection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wifiConnector.isConnected()) {
|
if (!wifiConnector.isConnected()) {
|
||||||
Serial.println("Failed connect to WiFi");
|
Serial.println("Failed connect to WiFi");
|
||||||
if (wifiConnector.isConfigurePorttalTimeout()) {
|
|
||||||
oledDisplay.showRebooting();
|
oledDisplay.showRebooting();
|
||||||
delay(2500);
|
delay(2500);
|
||||||
oledDisplay.setText("", "", "");
|
oledDisplay.setText("", "", "");
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Directly return because the rest of the function applied if wifi is connect only
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate local network configuration
|
// Initiate local network configuration
|
||||||
mdnsInit();
|
mdnsInit();
|
||||||
localServer.begin();
|
localServer.begin();
|
||||||
@@ -1016,10 +1017,17 @@ void initializeNetwork() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send data for the first time to AG server at boot
|
// Send data for the first time to AG server at boot only if postDataToAirgradient is enabled
|
||||||
|
if (configuration.isPostDataToAirGradient()) {
|
||||||
sendDataToAg();
|
sendDataToAg();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip fetch configuration if configuration control is set to "local" only
|
||||||
|
if (configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||||
|
ledBarEnabledUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string config = agClient->httpFetchConfig();
|
std::string config = agClient->httpFetchConfig();
|
||||||
configSchedule.update();
|
configSchedule.update();
|
||||||
@@ -1030,29 +1038,30 @@ void initializeNetwork() {
|
|||||||
if (agClient->isRegisteredOnAgServer() == false) {
|
if (agClient->isRegisteredOnAgServer() == false) {
|
||||||
stateMachine.displaySetAddToDashBoard();
|
stateMachine.displaySetAddToDashBoard();
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_ERR_MONITOR_NOT_REGISTERED);
|
||||||
} else {
|
} else {
|
||||||
stateMachine.displayClearAddToDashBoard();
|
stateMachine.displayClearAddToDashBoard();
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_ERR_GET_MONITOR_CONFIG_FAILED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
|
||||||
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ledBarEnabledUpdate();
|
ledBarEnabledUpdate();
|
||||||
|
wifiConnector.bleNotifyStatus(PROV_MONITOR_CONFIGURED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void configurationUpdateSchedule(void) {
|
static void configurationUpdateSchedule(void) {
|
||||||
if (configuration.getConfigurationControl() ==
|
if (configuration.getConfigurationControl() == ConfigurationControl::ConfigurationControlLocal) {
|
||||||
ConfigurationControl::ConfigurationControlLocal) {
|
|
||||||
Serial.println("Ignore fetch server configuration, configurationControl set to local");
|
Serial.println("Ignore fetch server configuration, configurationControl set to local");
|
||||||
agClient->resetFetchConfigurationStatus();
|
agClient->resetFetchConfigurationStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string config = agClient->httpFetchConfig();
|
std::string config = agClient->httpFetchConfig();
|
||||||
if (agClient->isLastFetchConfigSucceed() && configuration.parse(config.c_str(), false)) {
|
if (agClient->isLastFetchConfigSucceed()) {
|
||||||
configUpdateHandle();
|
configuration.parse(config.c_str(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,8 +1070,6 @@ static void configUpdateHandle() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMachine.executeCo2Calibration();
|
|
||||||
|
|
||||||
String mqttUri = configuration.getMqttBrokerUri();
|
String mqttUri = configuration.getMqttBrokerUri();
|
||||||
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
if (mqttClient.isCurrentUri(mqttUri) == false) {
|
||||||
mqttClient.end();
|
mqttClient.end();
|
||||||
@@ -1080,8 +1087,7 @@ static void configUpdateHandle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.hasSensorSGP) {
|
if (configuration.hasSensorSGP) {
|
||||||
if (configuration.noxLearnOffsetChanged() ||
|
if (configuration.noxLearnOffsetChanged() || configuration.tvocLearnOffsetChanged()) {
|
||||||
configuration.tvocLearnOffsetChanged()) {
|
|
||||||
ag->sgp41.end();
|
ag->sgp41.end();
|
||||||
|
|
||||||
int oldTvocOffset = ag->sgp41.getTvocLearningOffset();
|
int oldTvocOffset = ag->sgp41.getTvocLearningOffset();
|
||||||
@@ -1092,14 +1098,12 @@ static void configUpdateHandle() {
|
|||||||
resultStr = "failure";
|
resultStr = "failure";
|
||||||
}
|
}
|
||||||
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
if (oldTvocOffset != configuration.getTvocLearningOffset()) {
|
||||||
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n",
|
Serial.printf("Setting tvocLearningOffset from %d to %d hours %s\r\n", oldTvocOffset,
|
||||||
oldTvocOffset, configuration.getTvocLearningOffset(),
|
configuration.getTvocLearningOffset(), resultStr);
|
||||||
resultStr);
|
|
||||||
}
|
}
|
||||||
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
if (oldNoxOffset != configuration.getNoxLearningOffset()) {
|
||||||
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n",
|
Serial.printf("Setting noxLearningOffset from %d to %d hours %s\r\n", oldNoxOffset,
|
||||||
oldNoxOffset, configuration.getNoxLearningOffset(),
|
configuration.getNoxLearningOffset(), resultStr);
|
||||||
resultStr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1134,11 +1138,6 @@ static void configUpdateHandle() {
|
|||||||
if (configuration.isDisplayBrightnessChanged()) {
|
if (configuration.isDisplayBrightnessChanged()) {
|
||||||
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
oledDisplay.setBrightness(configuration.getDisplayBrightness());
|
||||||
}
|
}
|
||||||
|
|
||||||
stateMachine.executeLedBarTest();
|
|
||||||
}
|
|
||||||
else if(ag->isOpenAir()) {
|
|
||||||
stateMachine.executeLedBarTest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display and led bar notification based on updated configuration
|
// Update display and led bar notification based on updated configuration
|
||||||
@@ -1164,8 +1163,7 @@ static void updateDisplayAndLedBar(void) {
|
|||||||
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
stateMachine.handleLeds(AgStateMachineWiFiLost);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} else if (networkOption == UseCellular) {
|
||||||
else if (networkOption == UseCellular) {
|
|
||||||
if (agClient->isClientReady() == false) {
|
if (agClient->isClientReady() == false) {
|
||||||
// Same action as wifi
|
// Same action as wifi
|
||||||
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
stateMachine.displayHandle(AgStateMachineWiFiLost);
|
||||||
@@ -1406,13 +1404,17 @@ void postUsingCellular(bool forcePost) {
|
|||||||
|
|
||||||
// Post success, remove the data that previously sent from queue
|
// Post success, remove the data that previously sent from queue
|
||||||
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
xSemaphoreTake(mutexMeasurementCycleQueue, portMAX_DELAY);
|
||||||
measurementCycleQueue.erase(measurementCycleQueue.begin(),
|
|
||||||
measurementCycleQueue.begin() + queueSize);
|
|
||||||
|
|
||||||
if (measurementCycleQueue.capacity() > RESERVED_MEASUREMENT_CYCLE_CAPACITY) {
|
if (measurementCycleQueue.capacity() > RESERVED_MEASUREMENT_CYCLE_CAPACITY) {
|
||||||
Serial.println("measurementCycleQueue capacity more than reserved space, resizing..");
|
Serial.println("measurementCycleQueue capacity more than reserved space, resizing..");
|
||||||
measurementCycleQueue.resize(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
std::vector<Measurements::Measures> tmp;
|
||||||
|
tmp.reserve(RESERVED_MEASUREMENT_CYCLE_CAPACITY);
|
||||||
|
measurementCycleQueue.swap(tmp);
|
||||||
|
} else {
|
||||||
|
// If not more than the capacity, then just clear all the values
|
||||||
|
measurementCycleQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
xSemaphoreGive(mutexMeasurementCycleQueue);
|
xSemaphoreGive(mutexMeasurementCycleQueue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1500,7 +1502,6 @@ int calculateMaxPeriod(int updateInterval) {
|
|||||||
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
return (WIFI_MEASUREMENT_INTERVAL - (WIFI_MEASUREMENT_INTERVAL * 0.8)) / updateInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void networkSignalCheck() {
|
void networkSignalCheck() {
|
||||||
if (networkOption == UseWifi) {
|
if (networkOption == UseWifi) {
|
||||||
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
|
Serial.printf("WiFi RSSI %d\n", wifiConnector.RSSI());
|
||||||
@@ -1530,8 +1531,7 @@ void networkSignalCheck() {
|
|||||||
*/
|
*/
|
||||||
void restartIfCeClientIssueOverTwoHours() {
|
void restartIfCeClientIssueOverTwoHours() {
|
||||||
if (agCeClientProblemDetectedTime > 0 &&
|
if (agCeClientProblemDetectedTime > 0 &&
|
||||||
(MINUTES() - agCeClientProblemDetectedTime) >
|
(MINUTES() - agCeClientProblemDetectedTime) > TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY) {
|
||||||
TIMEOUT_WAIT_FOR_CELLULAR_MODULE_READY) {
|
|
||||||
// Give up wait
|
// Give up wait
|
||||||
Serial.println("Rebooting because CE client issues for 2 hours detected");
|
Serial.println("Rebooting because CE client issues for 2 hours detected");
|
||||||
int i = 3;
|
int i = 3;
|
||||||
@@ -1551,6 +1551,8 @@ void restartIfCeClientIssueOverTwoHours() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void networkingTask(void *args) {
|
void networkingTask(void *args) {
|
||||||
|
// If cloud connection enabled, run first transmission to server at boot
|
||||||
|
if (configuration.isCloudConnectionDisabled() == false) {
|
||||||
// OTA check on boot
|
// OTA check on boot
|
||||||
#ifndef ESP8266
|
#ifndef ESP8266
|
||||||
checkForFirmwareUpdate();
|
checkForFirmwareUpdate();
|
||||||
@@ -1567,10 +1569,10 @@ void networkingTask(void *args) {
|
|||||||
postUsingCellular(true);
|
postUsingCellular(true);
|
||||||
measurementSchedule.update();
|
measurementSchedule.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset scheduler
|
// Reset scheduler
|
||||||
configSchedule.update();
|
configSchedule.update();
|
||||||
transmissionSchedule.update();
|
transmissionSchedule.update();
|
||||||
|
}
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
// Handle reconnection based on mode
|
// Handle reconnection based on mode
|
||||||
@@ -1580,8 +1582,7 @@ void networkingTask(void *args) {
|
|||||||
delay(1000);
|
delay(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
} else if (networkOption == UseCellular) {
|
||||||
else if (networkOption == UseCellular) {
|
|
||||||
if (agClient->isClientReady() == false) {
|
if (agClient->isClientReady() == false) {
|
||||||
// Start time if value still default
|
// Start time if value still default
|
||||||
if (agCeClientProblemDetectedTime == 0) {
|
if (agCeClientProblemDetectedTime == 0) {
|
||||||
@@ -1626,16 +1627,16 @@ void networkingTask(void *args) {
|
|||||||
// If connection to AirGradient server disable don't run config and transmission schedule
|
// If connection to AirGradient server disable don't run config and transmission schedule
|
||||||
if (configuration.isCloudConnectionDisabled()) {
|
if (configuration.isCloudConnectionDisabled()) {
|
||||||
delay(1000);
|
delay(1000);
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run scheduler
|
// Run scheduler
|
||||||
networkSignalCheckSchedule.run();
|
networkSignalCheckSchedule.run();
|
||||||
configSchedule.run();
|
|
||||||
transmissionSchedule.run();
|
transmissionSchedule.run();
|
||||||
|
configSchedule.run();
|
||||||
checkForUpdateSchedule.run();
|
checkForUpdateSchedule.run();
|
||||||
|
|
||||||
delay(1000);
|
delay(50);
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelete(handleNetworkTask);
|
vTaskDelete(handleNetworkTask);
|
||||||
@@ -1661,4 +1662,3 @@ void newMeasurementCycle() {
|
|||||||
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
Serial.printf("Free heap: %u\n", ESP.getFreeHeap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ OpenMetrics::OpenMetrics(Measurements &measure, Configuration &config,
|
|||||||
|
|
||||||
OpenMetrics::~OpenMetrics() {}
|
OpenMetrics::~OpenMetrics() {}
|
||||||
|
|
||||||
void OpenMetrics::setAirGradient(AirGradient *ag, AirgradientClient *client) {
|
void OpenMetrics::setAirGradient(AirGradient *ag) {
|
||||||
this->ag = ag;
|
this->ag = ag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenMetrics::setAirgradientClient(AirgradientClient *client) {
|
||||||
this->agClient = client;
|
this->agClient = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,14 +202,14 @@ String OpenMetrics::getPayload(void) {
|
|||||||
}
|
}
|
||||||
if (utils::isValidNOx(nox)) {
|
if (utils::isValidNOx(nox)) {
|
||||||
add_metric("nox_index",
|
add_metric("nox_index",
|
||||||
"The processed Nitrous Oxide (NOx) index as measured by the "
|
"The processed Nitrogen Oxide (NOx) index as measured by the "
|
||||||
"AirGradient SGP sensor",
|
"AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(nox));
|
add_metric_point("", String(nox));
|
||||||
}
|
}
|
||||||
if (utils::isValidNOx(noxRaw)) {
|
if (utils::isValidNOx(noxRaw)) {
|
||||||
add_metric("nox_raw",
|
add_metric("nox_raw",
|
||||||
"The raw input value to the Nitrous Oxide (NOx) index as "
|
"The raw input value to the Nitrogen Oxide (NOx) index as "
|
||||||
"measured by the AirGradient SGP sensor",
|
"measured by the AirGradient SGP sensor",
|
||||||
"gauge");
|
"gauge");
|
||||||
add_metric_point("", String(noxRaw));
|
add_metric_point("", String(noxRaw));
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ public:
|
|||||||
OpenMetrics(Measurements &measure, Configuration &config,
|
OpenMetrics(Measurements &measure, Configuration &config,
|
||||||
WifiConnector &wifiConnector);
|
WifiConnector &wifiConnector);
|
||||||
~OpenMetrics();
|
~OpenMetrics();
|
||||||
void setAirGradient(AirGradient *ag, AirgradientClient *client);
|
void setAirGradient(AirGradient *ag);
|
||||||
|
void setAirgradientClient(AirgradientClient *client);
|
||||||
const char *getApiContentType(void);
|
const char *getApiContentType(void);
|
||||||
const char* getApi(void);
|
const char* getApi(void);
|
||||||
String getPayload(void);
|
String getPayload(void);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name=AirGradient Air Quality Sensor
|
name=AirGradient Air Quality Sensor
|
||||||
version=3.3.6
|
version=3.3.9
|
||||||
author=AirGradient <support@airgradient.com>
|
author=AirGradient <support@airgradient.com>
|
||||||
maintainer=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.
|
sentence=ESP32-C3 / ESP8266 library for air quality monitor measuring PM, CO2, Temperature, TVOC and Humidity with OLED display.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ lib_deps =
|
|||||||
WiFiClientSecure
|
WiFiClientSecure
|
||||||
Update
|
Update
|
||||||
DNSServer
|
DNSServer
|
||||||
|
h2zero/NimBLE-Arduino@^2.1.0
|
||||||
|
|
||||||
[env:esp8266]
|
[env:esp8266]
|
||||||
platform = espressif8266
|
platform = espressif8266
|
||||||
|
|||||||
@@ -456,6 +456,10 @@ bool Configuration::begin(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Configuration::setConfigurationUpdatedCallback(ConfigurationUpdatedCallback_t callback) {
|
||||||
|
_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse JSON configura string to local configure
|
* @brief Parse JSON configura string to local configure
|
||||||
*
|
*
|
||||||
@@ -951,15 +955,18 @@ bool Configuration::parse(String data, bool isLocal) {
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ledBarTestRequested || co2CalibrationRequested) {
|
||||||
|
commandRequested = true;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
updated = true;
|
updated = true;
|
||||||
saveConfig();
|
saveConfig();
|
||||||
printConfig();
|
printConfig();
|
||||||
} else {
|
_callback();
|
||||||
if (ledBarTestRequested || co2CalibrationRequested) {
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1159,6 +1166,12 @@ bool Configuration::isUpdated(void) {
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Configuration::isCommandRequested(void) {
|
||||||
|
bool oldState = this->commandRequested;
|
||||||
|
this->commandRequested = false;
|
||||||
|
return oldState;
|
||||||
|
}
|
||||||
|
|
||||||
String Configuration::jsonTypeInvalidMessage(String name, String type) {
|
String Configuration::jsonTypeInvalidMessage(String name, String type) {
|
||||||
return "'" + name + "' type is invalid, expecting '" + type + "'";
|
return "'" + name + "' type is invalid, expecting '" + type + "'";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ private:
|
|||||||
bool co2CalibrationRequested;
|
bool co2CalibrationRequested;
|
||||||
bool ledBarTestRequested;
|
bool ledBarTestRequested;
|
||||||
bool updated;
|
bool updated;
|
||||||
|
bool commandRequested = false;
|
||||||
String failedMessage;
|
String failedMessage;
|
||||||
bool _noxLearnOffsetChanged;
|
bool _noxLearnOffsetChanged;
|
||||||
bool _tvocLearningOffsetChanged;
|
bool _tvocLearningOffsetChanged;
|
||||||
@@ -70,6 +71,9 @@ public:
|
|||||||
bool hasSensorSGP = true;
|
bool hasSensorSGP = true;
|
||||||
bool hasSensorSHT = true;
|
bool hasSensorSHT = true;
|
||||||
|
|
||||||
|
typedef void (*ConfigurationUpdatedCallback_t)();
|
||||||
|
void setConfigurationUpdatedCallback(ConfigurationUpdatedCallback_t callback);
|
||||||
|
|
||||||
bool begin(void);
|
bool begin(void);
|
||||||
bool parse(String data, bool isLocal);
|
bool parse(String data, bool isLocal);
|
||||||
String toString(void);
|
String toString(void);
|
||||||
@@ -90,6 +94,7 @@ public:
|
|||||||
void reset(void);
|
void reset(void);
|
||||||
String getModel(void);
|
String getModel(void);
|
||||||
bool isUpdated(void);
|
bool isUpdated(void);
|
||||||
|
bool isCommandRequested(void);
|
||||||
String getFailedMesage(void);
|
String getFailedMesage(void);
|
||||||
void setPostToAirGradient(bool enable);
|
void setPostToAirGradient(bool enable);
|
||||||
bool noxLearnOffsetChanged(void);
|
bool noxLearnOffsetChanged(void);
|
||||||
@@ -116,6 +121,8 @@ public:
|
|||||||
PMCorrection getPMCorrection(void);
|
PMCorrection getPMCorrection(void);
|
||||||
TempHumCorrection getTempCorrection(void);
|
TempHumCorrection getTempCorrection(void);
|
||||||
TempHumCorrection getHumCorrection(void);
|
TempHumCorrection getHumCorrection(void);
|
||||||
|
private:
|
||||||
|
ConfigurationUpdatedCallback_t _callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /** _AG_CONFIG_H_ */
|
#endif /** _AG_CONFIG_H_ */
|
||||||
|
|||||||
@@ -19,10 +19,7 @@ static unsigned char OFFLINE_BITS[] = {
|
|||||||
0xE6, 0x00, 0xFE, 0x1F, 0xFE, 0x1F, 0xE6, 0x00, 0x62, 0x00,
|
0xE6, 0x00, 0xFE, 0x1F, 0xFE, 0x1F, 0xE6, 0x00, 0x62, 0x00,
|
||||||
0x30, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 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
|
* @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
|
* @brief Update dashboard content
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public:
|
|||||||
void setText(String &line1, String &line2, String &line3, String &line4);
|
void setText(String &line1, String &line2, String &line3, String &line4);
|
||||||
void setText(const char *line1, const char *line2, const char *line3,
|
void setText(const char *line1, const char *line2, const char *line3,
|
||||||
const char *line4);
|
const char *line4);
|
||||||
|
void showWiFiProvisioning(bool firstRun, int countdown);
|
||||||
void showDashboard(void);
|
void showDashboard(void);
|
||||||
void showDashboard(DashboardStatus status);
|
void showDashboard(DashboardStatus status);
|
||||||
void setBrightness(int percent);
|
void setBrightness(int percent);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ AgSchedule::~AgSchedule() {}
|
|||||||
void AgSchedule::run(void) {
|
void AgSchedule::run(void) {
|
||||||
uint32_t ms = (uint32_t)(millis() - count);
|
uint32_t ms = (uint32_t)(millis() - count);
|
||||||
if (ms >= period) {
|
if (ms >= period) {
|
||||||
handler();
|
|
||||||
count = millis();
|
count = millis();
|
||||||
|
handler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
#define RGB_COLOR_R 255, 0, 0 /** Red */
|
||||||
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
#define RGB_COLOR_G 0, 255, 0 /** Green */
|
||||||
#define RGB_COLOR_Y 255, 150, 0 /** Yellow */
|
#define RGB_COLOR_Y 255, 255, 0 /** Yellow */
|
||||||
#define RGB_COLOR_O 255, 40, 0 /** Orange */
|
#define RGB_COLOR_O 255, 128, 0 /** Orange */
|
||||||
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
#define RGB_COLOR_P 180, 0, 255 /** Purple */
|
||||||
#define RGB_COLOR_CLEAR 0, 0, 0 /** No color */
|
#define RGB_COLOR_CLEAR 0, 0, 0 /** No color */
|
||||||
|
|
||||||
@@ -496,11 +496,8 @@ void StateMachine::displayHandle(AgStateMachineState state) {
|
|||||||
String(wifiConnectCountDown) + String("s");
|
String(wifiConnectCountDown) + String("s");
|
||||||
disp.setText("Connect to hotspot:", ssid.c_str(), "");
|
disp.setText("Connect to hotspot:", ssid.c_str(), "");
|
||||||
} else {
|
} else {
|
||||||
String line1 = String(wifiConnectCountDown) + "s to connect";
|
// NOTE: This bool is hardcoded!
|
||||||
String line2 = "to WiFi hotspot:";
|
disp.showWiFiProvisioning((wifiConnectCountDown == 180), wifiConnectCountDown);
|
||||||
String line3 = "\"airgradient-";
|
|
||||||
String line4 = ag->deviceId() + "\"";
|
|
||||||
disp.setText(line1, line2, line3, line4);
|
|
||||||
}
|
}
|
||||||
wifiConnectCountDown--;
|
wifiConnectCountDown--;
|
||||||
}
|
}
|
||||||
@@ -648,7 +645,7 @@ void StateMachine::handleLeds(AgStateMachineState state) {
|
|||||||
ag->ledBar.clear();
|
ag->ledBar.clear();
|
||||||
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
ag->ledBar.setColor(0, 0, 255, ag->ledBar.getNumberOfLeds() / 2);
|
||||||
} else {
|
} else {
|
||||||
ag->statusLed.setToggle();
|
ag->statusLed.setStep();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
151
src/AgValue.cpp
151
src/AgValue.cpp
@@ -76,6 +76,86 @@ Measurements::Measurements(Configuration &config) : config(config) {
|
|||||||
|
|
||||||
void Measurements::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
void Measurements::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
||||||
|
|
||||||
|
void Measurements::printCurrentAverage() {
|
||||||
|
Serial.println();
|
||||||
|
if (config.hasSensorS8) {
|
||||||
|
if (utils::isValidCO2(_co2.update.avg)) {
|
||||||
|
Serial.printf("CO2 = %.2f ppm\n", _co2.update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("CO2 = -\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSHT) {
|
||||||
|
if (utils::isValidTemperature(_temperature[0].update.avg)) {
|
||||||
|
Serial.printf("Temperature = %.2f C\n", _temperature[0].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("Temperature = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidHumidity(_humidity[0].update.avg)) {
|
||||||
|
Serial.printf("Relative Humidity = %.2f\n", _humidity[0].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("Relative Humidity = -\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorSGP) {
|
||||||
|
if (utils::isValidVOC(_tvoc.update.avg)) {
|
||||||
|
Serial.printf("TVOC Index = %.1f\n", _tvoc.update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("TVOC Index = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidVOC(_tvoc_raw.update.avg)) {
|
||||||
|
Serial.printf("TVOC Raw = %.1f\n", _tvoc_raw.update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("TVOC Raw = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidNOx(_nox.update.avg)) {
|
||||||
|
Serial.printf("NOx Index = %.1f\n", _nox.update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("NOx Index = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidNOx(_nox_raw.update.avg)) {
|
||||||
|
Serial.printf("NOx Raw = %.1f\n", _nox_raw.update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("NOx Raw = -\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.hasSensorPMS1) {
|
||||||
|
printCurrentPMAverage(1);
|
||||||
|
if (!config.hasSensorSHT) {
|
||||||
|
if (utils::isValidTemperature(_temperature[0].update.avg)) {
|
||||||
|
Serial.printf("[1] Temperature = %.2f C\n", _temperature[0].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[1] Temperature = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidHumidity(_humidity[0].update.avg)) {
|
||||||
|
Serial.printf("[1] Relative Humidity = %.2f\n", _humidity[0].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[1] Relative Humidity = -\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.hasSensorPMS2) {
|
||||||
|
printCurrentPMAverage(2);
|
||||||
|
if (!config.hasSensorSHT) {
|
||||||
|
if (utils::isValidTemperature(_temperature[1].update.avg)) {
|
||||||
|
Serial.printf("[2] Temperature = %.2f C\n", _temperature[1].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[2] Temperature = -\n");
|
||||||
|
}
|
||||||
|
if (utils::isValidHumidity(_humidity[1].update.avg)) {
|
||||||
|
Serial.printf("[2] Relative Humidity = %.2f\n", _humidity[1].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[2] Relative Humidity = -\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
void Measurements::maxPeriod(MeasurementType type, int max) {
|
void Measurements::maxPeriod(MeasurementType type, int max) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Temperature:
|
case Temperature:
|
||||||
@@ -570,6 +650,77 @@ String Measurements::measurementTypeStr(MeasurementType type) {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Measurements::printCurrentPMAverage(int ch) {
|
||||||
|
int idx = ch - 1;
|
||||||
|
|
||||||
|
if (utils::isValidPm(_pm_01[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 1.0 = %.2f ug/m3\n", ch, _pm_01[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 1.0 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm(_pm_25[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 2.5 = %.2f ug/m3\n", ch, _pm_25[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 2.5 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm(_pm_10[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 10 = %.2f ug/m3\n", ch, _pm_10[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Atmospheric PM 10 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm(_pm_01_sp[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 1.0 = %.2f ug/m3\n", ch, _pm_01_sp[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 1.0 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm(_pm_25_sp[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 2.5 = %.2f ug/m3\n", ch, _pm_25_sp[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 2.5 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm(_pm_10_sp[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 10 = %.2f ug/m3\n", ch, _pm_10_sp[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Standard Particle PM 10 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm03Count(_pm_03_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 0.3 = %.1f\n", ch, _pm_03_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 0.3 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm03Count(_pm_05_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 0.5 = %.1f\n", ch, _pm_05_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 0.5 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm03Count(_pm_01_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 1.0 = %.1f\n", ch, _pm_01_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 1.0 = -\n", ch);
|
||||||
|
}
|
||||||
|
if (utils::isValidPm03Count(_pm_25_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 2.5 = %.1f\n", ch, _pm_25_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 2.5 = -\n", ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pm_5_pc[idx].listValues.empty() == false) {
|
||||||
|
if (utils::isValidPm03Count(_pm_5_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 5.0 = %.1f\n", ch, _pm_5_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 5.0 = -\n", ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pm_10_pc[idx].listValues.empty() == false) {
|
||||||
|
if (utils::isValidPm03Count(_pm_10_pc[idx].update.avg)) {
|
||||||
|
Serial.printf("[%d] Particle Count 10 = %.1f\n", ch, _pm_10_pc[idx].update.avg);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%d] Particle Count 10 = -\n", ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Measurements::validateChannel(int ch) {
|
void Measurements::validateChannel(int ch) {
|
||||||
if (ch != 1 && ch != 2) {
|
if (ch != 1 && ch != 2) {
|
||||||
Serial.printf("ERROR! Channel %d is undefined. Only channel 1 or 2 is the optional value!", ch);
|
Serial.printf("ERROR! Channel %d is undefined. Only channel 1 or 2 is the optional value!", ch);
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ public:
|
|||||||
PM10_PC, // Particle 10 count
|
PM10_PC, // Particle 10 count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void printCurrentAverage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set each MeasurementType maximum period length for moving average
|
* @brief Set each MeasurementType maximum period length for moving average
|
||||||
*
|
*
|
||||||
@@ -258,6 +260,8 @@ private:
|
|||||||
*/
|
*/
|
||||||
void validateChannel(int ch);
|
void validateChannel(int ch);
|
||||||
|
|
||||||
|
void printCurrentPMAverage(int ch);
|
||||||
|
|
||||||
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode);
|
JSONVar buildOutdoor(bool localServer, AgFirmwareMode fwMode);
|
||||||
JSONVar buildIndoor(bool localServer);
|
JSONVar buildIndoor(bool localServer);
|
||||||
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate);
|
JSONVar buildPMS(int ch, bool allCh, bool withTempHum, bool compensate);
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
#include "AgWiFiConnector.h"
|
#include "AgWiFiConnector.h"
|
||||||
|
#include "Arduino.h"
|
||||||
#include "Libraries/WiFiManager/WiFiManager.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_CONNECT_COUNTDOWN_MAX 180
|
||||||
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
#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))
|
#define WIFI() ((WiFiManager *)(this->wifi))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +44,7 @@ WifiConnector::~WifiConnector() {}
|
|||||||
* @return true Success
|
* @return true Success
|
||||||
* @return false Failure
|
* @return false Failure
|
||||||
*/
|
*/
|
||||||
bool WifiConnector::connect(void) {
|
bool WifiConnector::connect(String modelName) {
|
||||||
if (wifi == NULL) {
|
if (wifi == NULL) {
|
||||||
wifi = new WiFiManager();
|
wifi = new WiFiManager();
|
||||||
if (wifi == NULL) {
|
if (wifi == NULL) {
|
||||||
@@ -61,46 +73,69 @@ bool WifiConnector::connect(void) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!WiFi.isConnected()) {
|
||||||
|
// Erase already saved default credentials
|
||||||
|
WiFi.disconnect(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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", "...");
|
|
||||||
} else {
|
} 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);
|
||||||
}
|
}
|
||||||
ssid = "airgradient-" + ag->deviceId();
|
|
||||||
|
|
||||||
// ssid = "AG-" + String(ESP.getChipId(), HEX);
|
if (!WiFi.isConnected()) {
|
||||||
WIFI()->setConfigPortalTimeout(WIFI_CONNECT_COUNTDOWN_MAX);
|
// WiFi not connect, show indicator.
|
||||||
|
sm.ledAnimationInit();
|
||||||
|
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
||||||
|
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
||||||
|
delay(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
WiFiManagerParameter disableCloud("chbPostToAg", "Prevent Connection to AirGradient Server", "T",
|
||||||
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
||||||
WIFI()->addParameter(&disableCloud);
|
|
||||||
WiFiManagerParameter disableCloudInfo(
|
WiFiManagerParameter disableCloudInfo(
|
||||||
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
||||||
"it if you are sure you don't want to use any AirGradient cloud "
|
"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, "
|
"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>");
|
"configuration settings from cloud and the measure data will not reach the AirGradient dashboard.</p>");
|
||||||
WIFI()->addParameter(&disableCloudInfo);
|
setupProvisionByPortal(&disableCloud, &disableCloudInfo);
|
||||||
|
|
||||||
WIFI()->autoConnect(ssid.c_str(), WIFI_HOTSPOT_PASSWORD_DEFAULT);
|
|
||||||
|
|
||||||
logInfo("Wait for configure portal");
|
|
||||||
|
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
// Task handle WiFi connection.
|
// Provision by BLE only for ESP32
|
||||||
|
setupProvisionByBLE(modelName.c_str());
|
||||||
|
|
||||||
|
// Task handling WiFi portal
|
||||||
xTaskCreate(
|
xTaskCreate(
|
||||||
[](void *obj) {
|
[](void *obj) {
|
||||||
WifiConnector *connector = (WifiConnector *)obj;
|
WifiConnector *connector = (WifiConnector *)obj;
|
||||||
while (connector->_wifiConfigPortalActive()) {
|
while (connector->_wifiConfigPortalActive()) {
|
||||||
|
if (connector->isBleClientConnected()) {
|
||||||
|
Serial.println("Stopping portal because BLE connected");
|
||||||
|
connector->_wifiStop();
|
||||||
|
connector->provisionMethod = ProvisionMethod::BLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
connector->_wifiProcess();
|
connector->_wifiProcess();
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
@@ -108,14 +143,19 @@ bool WifiConnector::connect(void) {
|
|||||||
},
|
},
|
||||||
"wifi_cfg", 4096, this, 10, NULL);
|
"wifi_cfg", 4096, this, 10, NULL);
|
||||||
|
|
||||||
/** Wait for WiFi connect and show LED, display status */
|
|
||||||
|
// Wait for WiFi connect and show LED, display status
|
||||||
uint32_t dispPeriod = millis();
|
uint32_t dispPeriod = millis();
|
||||||
uint32_t ledPeriod = millis();
|
uint32_t ledPeriod = millis();
|
||||||
bool clientConnectChanged = false;
|
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();
|
AgStateMachineState stateOld = sm.getDisplayState();
|
||||||
while (WIFI()->getConfigPortalActive()) {
|
while (WIFI()->getConfigPortalActive()) {
|
||||||
/** LED animatoin and display update content */
|
/** LED animation and display update content */
|
||||||
if (WiFi.isConnected() == false) {
|
if (WiFi.isConnected() == false) {
|
||||||
/** Display countdown */
|
/** Display countdown */
|
||||||
uint32_t ms;
|
uint32_t ms;
|
||||||
@@ -145,6 +185,11 @@ bool WifiConnector::connect(void) {
|
|||||||
clientConnectChanged = clientConnected;
|
clientConnectChanged = clientConnected;
|
||||||
if (clientConnectChanged) {
|
if (clientConnectChanged) {
|
||||||
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
||||||
|
if (bleServerRunning) {
|
||||||
|
Serial.println("Stopping BLE since wifi is connected");
|
||||||
|
stopBLE();
|
||||||
|
provisionMethod = ProvisionMethod::WiFi;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sm.ledAnimationInit();
|
sm.ledAnimationInit();
|
||||||
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
||||||
@@ -157,6 +202,74 @@ bool WifiConnector::connect(void) {
|
|||||||
|
|
||||||
delay(1); // avoid watchdog timer reset.
|
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
|
#else
|
||||||
_wifiProcess();
|
_wifiProcess();
|
||||||
#endif
|
#endif
|
||||||
@@ -180,6 +293,7 @@ bool WifiConnector::connect(void) {
|
|||||||
config.setDisableCloudConnection(result == "T");
|
config.setDisableCloudConnection(result == "T");
|
||||||
}
|
}
|
||||||
hasPortalConfig = false;
|
hasPortalConfig = false;
|
||||||
|
bleNotifyStatus(PROV_WIFI_CONNECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -206,6 +320,11 @@ bool WifiConnector::wifiClientConnected(void) {
|
|||||||
return WiFi.softAPgetStationNum() ? true : false;
|
return WiFi.softAPgetStationNum() ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool WifiConnector::isBleClientConnected() {
|
||||||
|
return bleClientConnected;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handle WiFiManage softAP setup completed callback
|
* @brief Handle WiFiManage softAP setup completed callback
|
||||||
*
|
*
|
||||||
@@ -248,6 +367,10 @@ bool WifiConnector::_wifiConfigPortalActive(void) {
|
|||||||
}
|
}
|
||||||
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
||||||
|
|
||||||
|
void WifiConnector::_wifiStop() {
|
||||||
|
WIFI()->stopConfigPortal();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Process WiFiManager connection
|
* @brief Process WiFiManager connection
|
||||||
*
|
*
|
||||||
@@ -404,6 +527,28 @@ bool WifiConnector::hasConfigurated(void) {
|
|||||||
*/
|
*/
|
||||||
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
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
|
* @brief Set wifi connect to default WiFi
|
||||||
*
|
*
|
||||||
@@ -411,3 +556,307 @@ bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
|||||||
void WifiConnector::setDefault(void) {
|
void WifiConnector::setDefault(void) {
|
||||||
WiFi.begin("airgradient", "cleanair");
|
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 "AgStateMachine.h"
|
||||||
#include "AirGradient.h"
|
#include "AirGradient.h"
|
||||||
#include "AgConfigure.h"
|
#include "AgConfigure.h"
|
||||||
|
#include "Libraries/WiFiManager/WiFiManager.h"
|
||||||
#include "Main/PrintLog.h"
|
#include "Main/PrintLog.h"
|
||||||
|
#include "NimBLECharacteristic.h"
|
||||||
|
#include "NimBLEService.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
#include <Arduino.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 {
|
class WifiConnector : public PrintLog {
|
||||||
|
public:
|
||||||
|
enum class ProvisionMethod {
|
||||||
|
Unknown = 0,
|
||||||
|
WiFi,
|
||||||
|
BLE
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WiFiNetwork {
|
||||||
|
String ssid;
|
||||||
|
int32_t rssi;
|
||||||
|
bool open;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AirGradient *ag;
|
AirGradient *ag;
|
||||||
OledDisplay &disp;
|
OledDisplay &disp;
|
||||||
StateMachine &sm;
|
StateMachine &sm;
|
||||||
Configuration &config;
|
Configuration &config;
|
||||||
|
NimBLEServer *pServer;
|
||||||
|
|
||||||
|
EventGroupHandle_t bleEventGroup;
|
||||||
|
|
||||||
String ssid;
|
String ssid;
|
||||||
void *wifi = NULL;
|
void *wifi = NULL;
|
||||||
@@ -22,8 +55,40 @@ private:
|
|||||||
uint32_t lastRetry;
|
uint32_t lastRetry;
|
||||||
bool hasPortalConfig = false;
|
bool hasPortalConfig = false;
|
||||||
bool connectorTimeout = false;
|
bool connectorTimeout = false;
|
||||||
|
bool bleServerRunning = false;
|
||||||
|
bool bleClientConnected = false;
|
||||||
|
bool wifiConnecting = false;
|
||||||
|
ProvisionMethod provisionMethod = ProvisionMethod::Unknown;
|
||||||
|
|
||||||
bool wifiClientConnected(void);
|
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:
|
public:
|
||||||
void setAirGradient(AirGradient *ag);
|
void setAirGradient(AirGradient *ag);
|
||||||
@@ -31,7 +96,10 @@ public:
|
|||||||
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration &config);
|
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration &config);
|
||||||
~WifiConnector();
|
~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 disconnect(void);
|
||||||
void handle(void);
|
void handle(void);
|
||||||
void _wifiApCallback(void);
|
void _wifiApCallback(void);
|
||||||
@@ -39,6 +107,7 @@ public:
|
|||||||
void _wifiSaveParamCallback(void);
|
void _wifiSaveParamCallback(void);
|
||||||
bool _wifiConfigPortalActive(void);
|
bool _wifiConfigPortalActive(void);
|
||||||
void _wifiTimeoutCallback(void);
|
void _wifiTimeoutCallback(void);
|
||||||
|
void _wifiStop();
|
||||||
void _wifiProcess();
|
void _wifiProcess();
|
||||||
bool isConnected(void);
|
bool isConnected(void);
|
||||||
void reset(void);
|
void reset(void);
|
||||||
@@ -47,6 +116,9 @@ public:
|
|||||||
bool hasConfigurated(void);
|
bool hasConfigurated(void);
|
||||||
bool isConfigurePorttalTimeout(void);
|
bool isConfigurePorttalTimeout(void);
|
||||||
|
|
||||||
|
|
||||||
|
void bleNotifyStatus(int status);
|
||||||
|
|
||||||
const char *defaultSsid = "airgradient";
|
const char *defaultSsid = "airgradient";
|
||||||
const char *defaultPassword = "cleanair";
|
const char *defaultPassword = "cleanair";
|
||||||
void setDefault(void);
|
void setDefault(void);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#include "Main/utils.h"
|
#include "Main/utils.h"
|
||||||
|
|
||||||
#ifndef GIT_VERSION
|
#ifndef GIT_VERSION
|
||||||
#define GIT_VERSION "3.3.6-snap"
|
#define GIT_VERSION "3.3.9-snap"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Submodule src/Libraries/airgradient-client updated: a4ac14936e...c23bb2ceac
Submodule src/Libraries/airgradient-ota updated: 81a0189f54...7b103e9073
@@ -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
|
* @brief Get current LED state
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public:
|
|||||||
void setOn(void);
|
void setOn(void);
|
||||||
void setOff(void);
|
void setOff(void);
|
||||||
void setToggle(void);
|
void setToggle(void);
|
||||||
|
void setStep(void);
|
||||||
State getState(void);
|
State getState(void);
|
||||||
String toString(StatusLed::State state);
|
String toString(StatusLed::State state);
|
||||||
|
|
||||||
|
|||||||
@@ -260,13 +260,14 @@ bool MqttClient::connect(String id) {
|
|||||||
connected = false;
|
connected = false;
|
||||||
if (user.isEmpty()) {
|
if (user.isEmpty()) {
|
||||||
logInfo("Connect without auth");
|
logInfo("Connect without auth");
|
||||||
if(CLIENT()->connect(id.c_str())) {
|
connected = CLIENT()->connect(id.c_str());
|
||||||
connected = true;
|
} else {
|
||||||
|
logInfo("Connect with auth");
|
||||||
|
connected = CLIENT()->connect(id.c_str(), user.c_str(), password.c_str());
|
||||||
}
|
}
|
||||||
return connected;
|
return connected;
|
||||||
}
|
}
|
||||||
return CLIENT()->connect(id.c_str(), user.c_str(), password.c_str());
|
|
||||||
}
|
|
||||||
void MqttClient::handle(void) {
|
void MqttClient::handle(void) {
|
||||||
if (isBegin == false) {
|
if (isBegin == false) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user