2024-04-03 11:40:46 +07:00
|
|
|
#include "AgWiFiConnector.h"
|
2025-10-24 13:11:39 +07:00
|
|
|
#include "Arduino.h"
|
2024-04-04 18:35:15 +07:00
|
|
|
#include "Libraries/WiFiManager/WiFiManager.h"
|
2025-10-26 23:25:20 +07:00
|
|
|
#include "Libraries/Arduino_JSON/src/Arduino_JSON.h"
|
2025-10-31 01:50:39 +07:00
|
|
|
#include "WiFiType.h"
|
|
|
|
|
#include "esp32-hal.h"
|
2024-04-03 11:40:46 +07:00
|
|
|
|
2024-06-19 15:17:48 +07:00
|
|
|
#define WIFI_CONNECT_COUNTDOWN_MAX 180
|
2024-04-03 21:26:04 +07:00
|
|
|
#define WIFI_HOTSPOT_PASSWORD_DEFAULT "cleanair"
|
|
|
|
|
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
#define BLE_SERVICE_UUID "acbcfea8-e541-4c40-9bfd-17820f16c95c"
|
2025-10-28 11:46:07 +07:00
|
|
|
#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)
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
#define WIFI() ((WiFiManager *)(this->wifi))
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
2024-04-07 16:39:01 +07:00
|
|
|
* @brief Set reference AirGradient instance
|
2024-04-04 18:35:15 +07:00
|
|
|
*
|
2024-04-07 16:39:01 +07:00
|
|
|
* @param ag Point to AirGradient instance
|
2024-04-04 18:35:15 +07:00
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::setAirGradient(AirGradient *ag) { this->ag = ag; }
|
2024-04-03 21:26:04 +07:00
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
2024-04-07 16:39:01 +07:00
|
|
|
* @brief Construct a new Ag Wi Fi Connector:: Ag Wi Fi Connector object
|
2024-04-04 18:35:15 +07:00
|
|
|
*
|
2024-04-07 16:39:01 +07:00
|
|
|
* @param disp OledDisplay
|
|
|
|
|
* @param log Stream
|
|
|
|
|
* @param sm StateMachine
|
2024-04-04 18:35:15 +07:00
|
|
|
*/
|
2024-04-12 06:18:48 +07:00
|
|
|
WifiConnector::WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm,
|
|
|
|
|
Configuration &config)
|
2024-04-11 06:33:56 +07:00
|
|
|
: PrintLog(log, "WifiConnector"), disp(disp), sm(sm), config(config) {}
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2024-04-07 16:39:01 +07:00
|
|
|
WifiConnector::~WifiConnector() {}
|
2024-04-03 21:26:04 +07:00
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Connection to WIFI AP process. Just call one times
|
|
|
|
|
*
|
|
|
|
|
* @return true Success
|
|
|
|
|
* @return false Failure
|
|
|
|
|
*/
|
2025-11-10 14:36:29 +07:00
|
|
|
bool WifiConnector::connect(String modelName) {
|
2024-04-03 21:26:04 +07:00
|
|
|
if (wifi == NULL) {
|
|
|
|
|
wifi = new WiFiManager();
|
|
|
|
|
if (wifi == NULL) {
|
|
|
|
|
logError("Create 'WiFiManger' failed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-26 15:47:49 +07:00
|
|
|
WiFi.begin();
|
|
|
|
|
String wifiSSID = WIFI()->getWiFiSSID(true);
|
|
|
|
|
if (wifiSSID.isEmpty()) {
|
|
|
|
|
logInfo("Connected WiFi is empty, connect to default wifi \"" +
|
|
|
|
|
String(this->defaultSsid) + String("\""));
|
|
|
|
|
|
|
|
|
|
/** Set wifi connect */
|
|
|
|
|
WiFi.begin(this->defaultSsid, this->defaultPassword);
|
|
|
|
|
|
|
|
|
|
/** Wait for wifi connect to AP */
|
|
|
|
|
int count = 0;
|
|
|
|
|
while (WiFi.status() != WL_CONNECTED) {
|
|
|
|
|
delay(1000);
|
|
|
|
|
count++;
|
|
|
|
|
if (count >= 15) {
|
|
|
|
|
logError("Try connect to default wifi \"" + String(this->defaultSsid) +
|
|
|
|
|
String("\" failed"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-03 21:26:04 +07:00
|
|
|
|
2025-10-31 13:20:07 +07:00
|
|
|
if (!WiFi.isConnected()) {
|
|
|
|
|
// Erase already saved default credentials
|
|
|
|
|
WiFi.disconnect(false, true);
|
|
|
|
|
}
|
2024-04-03 21:26:04 +07:00
|
|
|
} else {
|
2025-10-31 01:50:39 +07:00
|
|
|
Serial.printf("Attempt connect to configured ssid: %d\n", wifiSSID.c_str());
|
|
|
|
|
// WiFi.begin() already called before, it will attempt connect when wifi creds already persist
|
|
|
|
|
|
|
|
|
|
sm.ledAnimationInit();
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerStaConnecting);
|
|
|
|
|
sm.displayHandle(AgStateMachineWiFiManagerStaConnecting);
|
|
|
|
|
|
|
|
|
|
uint32_t ledPeriod = millis();
|
|
|
|
|
uint32_t startTime = millis();
|
|
|
|
|
while (WiFi.status() != WL_CONNECTED && (millis() - startTime) < 15000) {
|
|
|
|
|
/** LED animations */
|
|
|
|
|
if ((millis() - ledPeriod) >= 100) {
|
|
|
|
|
ledPeriod = millis();
|
|
|
|
|
sm.handleLeds();
|
|
|
|
|
}
|
|
|
|
|
delay(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!WiFi.isConnected()) {
|
|
|
|
|
// WiFi not connect, show indicator.
|
|
|
|
|
sm.ledAnimationInit();
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
|
|
|
|
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
|
|
|
|
delay(3000);
|
|
|
|
|
}
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
if (WiFi.isConnected()) {
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerStaConnected);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-04-11 06:33:56 +07:00
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
// Enable provision by both BLE and WiFi portal
|
2025-01-25 03:01:32 +07:00
|
|
|
WiFiManagerParameter disableCloud("chbPostToAg", "Prevent Connection to AirGradient Server", "T",
|
|
|
|
|
2, "type=\"checkbox\" ", WFM_LABEL_AFTER);
|
|
|
|
|
WiFiManagerParameter disableCloudInfo(
|
2024-04-12 12:08:02 +07:00
|
|
|
"<p>Prevent connection to the AirGradient Server. Important: Only enable "
|
|
|
|
|
"it if you are sure you don't want to use any AirGradient cloud "
|
2025-02-05 10:43:56 +07:00
|
|
|
"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>");
|
2025-10-31 01:50:39 +07:00
|
|
|
setupProvisionByPortal(&disableCloud, &disableCloudInfo);
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2024-04-07 16:39:01 +07:00
|
|
|
#ifdef ESP32
|
2025-10-31 01:50:39 +07:00
|
|
|
// Provision by BLE only for ESP32
|
2025-11-10 14:36:29 +07:00
|
|
|
setupProvisionByBLE(modelName.c_str());
|
2025-10-31 01:50:39 +07:00
|
|
|
|
|
|
|
|
// Task handling WiFi portal
|
2024-04-03 21:26:04 +07:00
|
|
|
xTaskCreate(
|
2025-10-31 01:50:39 +07:00
|
|
|
[](void *obj) {
|
|
|
|
|
WifiConnector *connector = (WifiConnector *)obj;
|
|
|
|
|
while (connector->_wifiConfigPortalActive()) {
|
|
|
|
|
if (connector->isBleClientConnected()) {
|
|
|
|
|
Serial.println("Stopping portal because BLE connected");
|
|
|
|
|
connector->_wifiStop();
|
|
|
|
|
connector->provisionMethod = ProvisionMethod::BLE;
|
|
|
|
|
break;
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
2025-10-31 01:50:39 +07:00
|
|
|
connector->_wifiProcess();
|
|
|
|
|
vTaskDelay(1);
|
|
|
|
|
}
|
|
|
|
|
vTaskDelete(NULL);
|
|
|
|
|
},
|
|
|
|
|
"wifi_cfg", 4096, this, 10, NULL);
|
2024-04-03 21:26:04 +07:00
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
|
|
|
|
|
// Wait for WiFi connect and show LED, display status
|
2024-04-03 21:26:04 +07:00
|
|
|
uint32_t dispPeriod = millis();
|
|
|
|
|
uint32_t ledPeriod = millis();
|
|
|
|
|
bool clientConnectChanged = false;
|
|
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
// 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)
|
2024-04-03 21:26:04 +07:00
|
|
|
AgStateMachineState stateOld = sm.getDisplayState();
|
|
|
|
|
while (WIFI()->getConfigPortalActive()) {
|
2025-10-24 13:11:39 +07:00
|
|
|
/** LED animation and display update content */
|
2024-04-03 21:26:04 +07:00
|
|
|
if (WiFi.isConnected() == false) {
|
|
|
|
|
/** Display countdown */
|
2024-04-04 10:36:59 +07:00
|
|
|
uint32_t ms;
|
2024-04-07 16:39:01 +07:00
|
|
|
if (ag->isOne()) {
|
2024-04-04 10:36:59 +07:00
|
|
|
ms = (uint32_t)(millis() - dispPeriod);
|
2024-04-03 21:26:04 +07:00
|
|
|
if (ms >= 1000) {
|
|
|
|
|
dispPeriod = millis();
|
|
|
|
|
sm.displayHandle();
|
|
|
|
|
} else {
|
|
|
|
|
if (stateOld != sm.getDisplayState()) {
|
|
|
|
|
stateOld = sm.getDisplayState();
|
|
|
|
|
sm.displayHandle();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** LED animations */
|
|
|
|
|
ms = (uint32_t)(millis() - ledPeriod);
|
|
|
|
|
if (ms >= 100) {
|
|
|
|
|
ledPeriod = millis();
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds();
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Check for client connect to change led color */
|
|
|
|
|
bool clientConnected = wifiClientConnected();
|
|
|
|
|
if (clientConnected != clientConnectChanged) {
|
|
|
|
|
clientConnectChanged = clientConnected;
|
|
|
|
|
if (clientConnectChanged) {
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
2025-10-26 23:25:20 +07:00
|
|
|
if (bleServerRunning) {
|
|
|
|
|
Serial.println("Stopping BLE since wifi is connected");
|
|
|
|
|
stopBLE();
|
|
|
|
|
provisionMethod = ProvisionMethod::WiFi;
|
|
|
|
|
}
|
2024-04-03 21:26:04 +07:00
|
|
|
} else {
|
|
|
|
|
sm.ledAnimationInit();
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
|
|
|
|
if (ag->isOne()) {
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.displayHandle(AgStateMachineWiFiManagerMode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-04 10:36:59 +07:00
|
|
|
|
|
|
|
|
delay(1); // avoid watchdog timer reset.
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
if (provisionMethod == ProvisionMethod::BLE) {
|
|
|
|
|
disp.setText("Provision by", "BLE", "");
|
2025-11-10 16:21:22 +07:00
|
|
|
sm.ledAnimationInit();
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-12-04 11:32:30 +07:00
|
|
|
uint32_t wdMillis = 0;
|
|
|
|
|
|
2025-10-28 11:46:07 +07:00
|
|
|
// 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,
|
2025-10-31 01:50:39 +07:00
|
|
|
10 / portTICK_PERIOD_MS
|
2025-10-28 11:46:07 +07:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (bits & BLE_CRED_BIT) {
|
|
|
|
|
Serial.printf("Connecting to %s...\n", ssid.c_str());
|
2025-10-31 01:50:39 +07:00
|
|
|
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();
|
2025-10-28 11:46:07 +07:00
|
|
|
}
|
2025-10-31 01:50:39 +07:00
|
|
|
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;
|
2025-10-31 02:08:02 +07:00
|
|
|
bleNotifyStatus(PROV_ERR_WIFI_CONNECT_FAILED);
|
2025-10-31 01:50:39 +07:00
|
|
|
|
|
|
|
|
// 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);
|
2025-10-28 11:46:07 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (bits & BLE_SCAN_BIT) {
|
2025-11-26 00:05:38 +07:00
|
|
|
handleBleScanRequest();
|
2025-10-26 23:25:20 +07:00
|
|
|
}
|
2025-10-31 01:50:39 +07:00
|
|
|
|
2025-12-04 11:32:30 +07:00
|
|
|
// Ensure watchdog fed every minute
|
|
|
|
|
if ((millis() - wdMillis) >= 60000) {
|
|
|
|
|
wdMillis = millis();
|
|
|
|
|
ag->watchdog.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
delay(1);
|
2025-10-24 13:11:39 +07:00
|
|
|
}
|
2025-10-28 11:46:07 +07:00
|
|
|
|
|
|
|
|
Serial.println("Exit provision by BLE");
|
2025-10-24 13:11:39 +07:00
|
|
|
}
|
2025-10-31 01:50:39 +07:00
|
|
|
#else
|
|
|
|
|
_wifiProcess();
|
|
|
|
|
#endif
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2024-04-03 21:26:04 +07:00
|
|
|
/** Show display wifi connect result failed */
|
|
|
|
|
if (WiFi.isConnected() == false) {
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerConnectFailed);
|
2024-06-25 16:43:50 +07:00
|
|
|
if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) {
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
|
|
|
|
|
}
|
|
|
|
|
delay(6000);
|
|
|
|
|
} else {
|
2024-04-04 10:36:59 +07:00
|
|
|
hasConfig = true;
|
2024-04-07 16:39:01 +07:00
|
|
|
logInfo("WiFi Connected: " + WiFi.SSID() + " IP: " + localIpStr());
|
2024-04-11 06:33:56 +07:00
|
|
|
|
2024-04-22 14:24:01 +07:00
|
|
|
if (hasPortalConfig) {
|
2025-01-25 03:01:32 +07:00
|
|
|
String result = String(disableCloud.getValue());
|
|
|
|
|
logInfo("Setting disableCloudConnection set from " +
|
|
|
|
|
String(config.isCloudConnectionDisabled() ? "True" : "False") + String(" to ") +
|
|
|
|
|
String(result == "T" ? "True" : "False") + String(" successful"));
|
|
|
|
|
config.setDisableCloudConnection(result == "T");
|
2024-04-22 14:24:01 +07:00
|
|
|
}
|
|
|
|
|
hasPortalConfig = false;
|
2025-10-31 02:08:02 +07:00
|
|
|
bleNotifyStatus(PROV_WIFI_CONNECT);
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2024-04-04 10:36:59 +07:00
|
|
|
return true;
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Disconnect to current connected WiFi AP
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::disconnect(void) {
|
2024-04-04 18:35:15 +07:00
|
|
|
if (WiFi.isConnected()) {
|
|
|
|
|
logInfo("Disconnect");
|
|
|
|
|
WiFi.disconnect();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Has wifi STA connected to WIFI softAP (this device)
|
|
|
|
|
*
|
|
|
|
|
* @return true Connected
|
|
|
|
|
* @return false Not connected
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
bool WifiConnector::wifiClientConnected(void) {
|
2024-04-03 21:26:04 +07:00
|
|
|
return WiFi.softAPgetStationNum() ? true : false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
|
|
|
|
|
bool WifiConnector::isBleClientConnected() {
|
|
|
|
|
return bleClientConnected;
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Handle WiFiManage softAP setup completed callback
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::_wifiApCallback(void) {
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.displayWiFiConnectCountDown(WIFI_CONNECT_COUNTDOWN_MAX);
|
|
|
|
|
sm.setDisplayState(AgStateMachineWiFiManagerMode);
|
2024-04-04 10:36:59 +07:00
|
|
|
sm.ledAnimationInit();
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Handle WiFiManager save configuration callback
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::_wifiSaveConfig(void) {
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.setDisplayState(AgStateMachineWiFiManagerStaConnected);
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerStaConnected);
|
2024-04-03 21:26:04 +07:00
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Handle WiFiManager save parameter callback
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::_wifiSaveParamCallback(void) {
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.ledAnimationInit();
|
2024-04-07 16:39:01 +07:00
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerStaConnecting);
|
2024-04-03 21:26:04 +07:00
|
|
|
sm.setDisplayState(AgStateMachineWiFiManagerStaConnecting);
|
2024-04-22 14:24:01 +07:00
|
|
|
hasPortalConfig = true;
|
2024-04-03 11:40:46 +07:00
|
|
|
}
|
2024-04-03 21:26:04 +07:00
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Check that WiFiManager Configure portal active
|
|
|
|
|
*
|
|
|
|
|
* @return true Active
|
|
|
|
|
* @return false Not-Active
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
bool WifiConnector::_wifiConfigPortalActive(void) {
|
2024-04-03 21:26:04 +07:00
|
|
|
return WIFI()->getConfigPortalActive();
|
|
|
|
|
}
|
2024-06-05 19:01:25 +07:00
|
|
|
void WifiConnector::_wifiTimeoutCallback(void) { connectorTimeout = true; }
|
2024-06-15 15:40:50 +07:00
|
|
|
|
2025-10-24 13:11:39 +07:00
|
|
|
void WifiConnector::_wifiStop() {
|
|
|
|
|
WIFI()->stopConfigPortal();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Process WiFiManager connection
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::_wifiProcess() {
|
|
|
|
|
#ifdef ESP32
|
|
|
|
|
WIFI()->process();
|
|
|
|
|
#else
|
2024-06-15 15:40:50 +07:00
|
|
|
/** Wait for WiFi connect and show LED, display status */
|
|
|
|
|
uint32_t dispPeriod = millis();
|
|
|
|
|
uint32_t ledPeriod = millis();
|
|
|
|
|
bool clientConnectChanged = false;
|
|
|
|
|
AgStateMachineState stateOld = sm.getDisplayState();
|
|
|
|
|
|
2024-04-07 16:39:01 +07:00
|
|
|
while (WIFI()->getConfigPortalActive()) {
|
|
|
|
|
WIFI()->process();
|
2024-04-07 17:01:54 +07:00
|
|
|
|
2024-06-15 15:40:50 +07:00
|
|
|
if (WiFi.isConnected() == false) {
|
|
|
|
|
/** Display countdown */
|
|
|
|
|
uint32_t ms;
|
2024-06-25 16:43:50 +07:00
|
|
|
if (ag->isOne() || (ag->isPro4_2()) || ag->isPro3_3() || ag->isBasic()) {
|
2024-06-15 15:40:50 +07:00
|
|
|
ms = (uint32_t)(millis() - dispPeriod);
|
|
|
|
|
if (ms >= 1000) {
|
|
|
|
|
dispPeriod = millis();
|
|
|
|
|
sm.displayHandle();
|
|
|
|
|
logInfo("displayHandle state: " + String(sm.getDisplayState()));
|
|
|
|
|
} else {
|
|
|
|
|
if (stateOld != sm.getDisplayState()) {
|
|
|
|
|
stateOld = sm.getDisplayState();
|
|
|
|
|
sm.displayHandle();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-07 16:39:01 +07:00
|
|
|
|
2024-06-15 15:40:50 +07:00
|
|
|
/** LED animations */
|
|
|
|
|
ms = (uint32_t)(millis() - ledPeriod);
|
|
|
|
|
if (ms >= 100) {
|
|
|
|
|
ledPeriod = millis();
|
|
|
|
|
sm.handleLeds();
|
|
|
|
|
}
|
2024-04-07 16:39:01 +07:00
|
|
|
|
2024-06-15 15:40:50 +07:00
|
|
|
/** Check for client connect to change led color */
|
|
|
|
|
bool clientConnected = wifiClientConnected();
|
|
|
|
|
if (clientConnected != clientConnectChanged) {
|
|
|
|
|
clientConnectChanged = clientConnected;
|
|
|
|
|
if (clientConnectChanged) {
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerPortalActive);
|
|
|
|
|
} else {
|
|
|
|
|
sm.ledAnimationInit();
|
|
|
|
|
sm.handleLeds(AgStateMachineWiFiManagerMode);
|
|
|
|
|
if (ag->isOne()) {
|
|
|
|
|
sm.displayHandle(AgStateMachineWiFiManagerMode);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-07 16:39:01 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-15 15:40:50 +07:00
|
|
|
delay(1);
|
2024-04-07 16:39:01 +07:00
|
|
|
}
|
2024-06-15 15:40:50 +07:00
|
|
|
|
|
|
|
|
// TODO This is for basic
|
2024-06-18 19:59:29 +07:00
|
|
|
if (ag->getBoardType() == DIY_BASIC) {
|
|
|
|
|
if (!WiFi.isConnected()) {
|
|
|
|
|
// disp.setText("Booting", "offline", "mode");
|
|
|
|
|
Serial.println("failed to connect and hit timeout");
|
|
|
|
|
delay(2500);
|
|
|
|
|
} else {
|
|
|
|
|
hasConfig = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-07 16:39:01 +07:00
|
|
|
#endif
|
|
|
|
|
}
|
2024-04-04 10:36:59 +07:00
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Handle and reconnect WiFi
|
|
|
|
|
*
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
void WifiConnector::handle(void) {
|
2024-04-04 10:36:59 +07:00
|
|
|
// Ignore if WiFi is not configured
|
|
|
|
|
if (hasConfig == false) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (WiFi.isConnected()) {
|
|
|
|
|
lastRetry = millis();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Retry connect WiFi each 10sec */
|
|
|
|
|
uint32_t ms = (uint32_t)(millis() - lastRetry);
|
|
|
|
|
if (ms >= 10000) {
|
|
|
|
|
lastRetry = millis();
|
|
|
|
|
WiFi.reconnect();
|
|
|
|
|
logInfo("Re-Connect WiFi");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-04 18:35:15 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Is WiFi connected
|
|
|
|
|
*
|
|
|
|
|
* @return true Connected
|
|
|
|
|
* @return false Disconnected
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
bool WifiConnector::isConnected(void) { return WiFi.isConnected(); }
|
2024-04-04 18:35:15 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Reset WiFi configuretion and connection, disconnect wifi before call
|
|
|
|
|
* this method
|
|
|
|
|
*
|
|
|
|
|
*/
|
2025-09-17 20:06:50 -04:00
|
|
|
void WifiConnector::reset(void) {
|
2024-06-25 16:08:57 +07:00
|
|
|
if(this->wifi == NULL) {
|
|
|
|
|
this->wifi = new WiFiManager();
|
|
|
|
|
if(this->wifi == NULL){
|
|
|
|
|
logInfo("reset failed");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-17 20:06:50 -04:00
|
|
|
WIFI()->resetSettings();
|
2024-06-25 16:08:57 +07:00
|
|
|
}
|
2024-04-04 18:35:15 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get wifi RSSI
|
|
|
|
|
*
|
|
|
|
|
* @return int
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
int WifiConnector::RSSI(void) { return WiFi.RSSI(); }
|
2024-04-04 18:35:15 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get WIFI IP as string format ex: 192.168.1.1
|
|
|
|
|
*
|
|
|
|
|
* @return String
|
|
|
|
|
*/
|
2024-04-07 16:39:01 +07:00
|
|
|
String WifiConnector::localIpStr(void) { return WiFi.localIP().toString(); }
|
2024-05-13 15:07:10 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get status that wifi has configurated
|
2024-06-15 15:40:50 +07:00
|
|
|
*
|
2024-05-13 15:07:10 +07:00
|
|
|
* @return true Configurated
|
|
|
|
|
* @return false Not Configurated
|
|
|
|
|
*/
|
|
|
|
|
bool WifiConnector::hasConfigurated(void) {
|
|
|
|
|
if (WiFi.SSID().isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-06-05 19:01:25 +07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get WiFi connection porttal timeout.
|
2024-06-15 15:40:50 +07:00
|
|
|
*
|
|
|
|
|
* @return true
|
|
|
|
|
* @return false
|
2024-06-05 19:01:25 +07:00
|
|
|
*/
|
|
|
|
|
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
|
2024-08-26 15:47:49 +07:00
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
|
|
|
|
|
void WifiConnector::bleNotifyStatus(int status) {
|
2025-10-28 11:46:07 +07:00
|
|
|
if (!bleServerRunning) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
if (pServer->getConnectedCount()) {
|
|
|
|
|
NimBLEService* pSvc = pServer->getServiceByUUID(BLE_SERVICE_UUID);
|
|
|
|
|
if (pSvc) {
|
2025-10-28 11:46:07 +07:00
|
|
|
NimBLECharacteristic* pChr = pSvc->getCharacteristic(BLE_CRED_CHAR_UUID);
|
2025-10-26 23:25:20 +07:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-26 15:47:49 +07:00
|
|
|
/**
|
|
|
|
|
* @brief Set wifi connect to default WiFi
|
2025-09-17 20:06:50 -04:00
|
|
|
*
|
2024-08-26 15:47:49 +07:00
|
|
|
*/
|
|
|
|
|
void WifiConnector::setDefault(void) {
|
|
|
|
|
WiFi.begin("airgradient", "cleanair");
|
|
|
|
|
}
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
int WifiConnector::scanAndFilterWiFi(WiFiNetwork networks[], int maxResults) {
|
2025-10-28 11:46:07 +07:00
|
|
|
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");
|
2025-11-26 00:05:38 +07:00
|
|
|
return 0;
|
2025-10-28 11:46:07 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-11-26 00:07:06 +07:00
|
|
|
if (ssid.length() == 0 || rssi < -75) continue;
|
2025-10-28 11:46:07 +07:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
// 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;
|
|
|
|
|
}
|
2025-10-28 11:46:07 +07:00
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
// Build JSON object with pagination
|
|
|
|
|
JSONVar jsonRoot;
|
2025-10-28 11:46:07 +07:00
|
|
|
JSONVar jsonArray;
|
2025-11-26 00:05:38 +07:00
|
|
|
|
|
|
|
|
for (int i = startIdx; i < endIdx; i++) {
|
2025-10-28 11:46:07 +07:00
|
|
|
JSONVar obj;
|
2025-11-26 00:34:57 +07:00
|
|
|
obj["s"] = networks[i].ssid;
|
|
|
|
|
obj["r"] = networks[i].rssi;
|
|
|
|
|
obj["o"] = networks[i].open ? 1 : 0;
|
2025-11-26 00:05:38 +07:00
|
|
|
jsonArray[i - startIdx] = obj;
|
2025-10-28 11:46:07 +07:00
|
|
|
}
|
|
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
jsonRoot["wifi"] = jsonArray;
|
|
|
|
|
jsonRoot["page"] = page;
|
2025-11-26 00:34:57 +07:00
|
|
|
jsonRoot["tpage"] = totalPages;
|
2025-11-26 00:05:38 +07:00
|
|
|
jsonRoot["found"] = totalFound;
|
|
|
|
|
|
|
|
|
|
String jsonString = JSON.stringify(jsonRoot);
|
2025-10-28 11:46:07 +07:00
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
Serial.printf("Page %d/%d JSON: %s\n", page, totalPages, jsonString.c_str());
|
2025-10-28 11:46:07 +07:00
|
|
|
|
|
|
|
|
return jsonString;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 00:05:38 +07:00
|
|
|
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");
|
|
|
|
|
}
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-10-31 01:50:39 +07:00
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 14:36:29 +07:00
|
|
|
void WifiConnector::setupProvisionByBLE(const char *modelName) {
|
|
|
|
|
NimBLEDevice::init("AirGradient");
|
2025-10-24 13:11:39 +07:00
|
|
|
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);
|
|
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
pServer = NimBLEDevice::createServer();
|
|
|
|
|
pServer->setCallbacks(new ServerCallbacks(this));
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-11-10 14:36:29 +07:00
|
|
|
// 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");
|
|
|
|
|
|
2025-12-19 14:57:31 +07:00
|
|
|
// Service and characteristics for wifi provisioning
|
2025-11-10 14:36:29 +07:00
|
|
|
NimBLEService *pServProvisioning = pServer->createService(BLE_SERVICE_UUID);
|
2025-10-28 11:46:07 +07:00
|
|
|
auto characteristicCallback = new CharacteristicCallbacks(this);
|
|
|
|
|
NimBLECharacteristic *pCredentialCharacteristic =
|
2025-11-10 14:36:29 +07:00
|
|
|
pServProvisioning->createCharacteristic(BLE_CRED_CHAR_UUID,
|
2025-10-24 13:11:39 +07:00
|
|
|
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC |
|
2025-10-26 23:25:20 +07:00
|
|
|
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY);
|
2025-10-28 11:46:07 +07:00
|
|
|
pCredentialCharacteristic->setCallbacks(characteristicCallback);
|
|
|
|
|
NimBLECharacteristic *pScanCharacteristic =
|
2025-11-10 14:36:29 +07:00
|
|
|
pServProvisioning->createCharacteristic(BLE_SCAN_CHAR_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY);
|
2025-10-28 11:46:07 +07:00
|
|
|
pScanCharacteristic->setCallbacks(characteristicCallback);
|
|
|
|
|
|
2025-11-10 14:36:29 +07:00
|
|
|
// Start services
|
|
|
|
|
pServProvisioning->start();
|
|
|
|
|
pServDeviceInfo->start();
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-11-10 14:36:29 +07:00
|
|
|
// Advertise
|
2025-10-24 13:11:39 +07:00
|
|
|
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
|
2025-11-29 22:29:07 +07:00
|
|
|
// Format advertising data
|
|
|
|
|
String mdata;
|
|
|
|
|
mdata += (char)0xFF;
|
|
|
|
|
mdata += (char)0xFF;
|
|
|
|
|
mdata += modelName;
|
|
|
|
|
mdata += '#';
|
|
|
|
|
mdata += ag->deviceId();
|
|
|
|
|
pAdvertising->setManufacturerData(mdata.c_str());
|
|
|
|
|
// Start advertise
|
2025-10-24 13:11:39 +07:00
|
|
|
pAdvertising->start();
|
2025-10-26 23:25:20 +07:00
|
|
|
bleServerRunning = true;
|
2025-10-28 11:46:07 +07:00
|
|
|
|
|
|
|
|
// Create event group
|
|
|
|
|
bleEventGroup = xEventGroupCreate();
|
|
|
|
|
if (bleEventGroup == NULL) {
|
|
|
|
|
Serial.println("Failed to create BLE event group!");
|
|
|
|
|
// This case is very unlikely
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
Serial.println("Provision by BLE ready");
|
2025-10-24 13:11:39 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WifiConnector::stopBLE() {
|
2025-10-26 23:25:20 +07:00
|
|
|
if (bleServerRunning) {
|
|
|
|
|
Serial.println("Stopping BLE");
|
|
|
|
|
NimBLEDevice::deinit();
|
2025-10-31 02:08:02 +07:00
|
|
|
}
|
2025-10-26 23:25:20 +07:00
|
|
|
bleServerRunning = false;
|
|
|
|
|
}
|
2025-10-24 13:11:39 +07:00
|
|
|
|
2025-10-26 23:25:20 +07:00
|
|
|
//
|
|
|
|
|
// 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();
|
2025-10-24 13:11:39 +07:00
|
|
|
}
|
2025-10-26 23:25:20 +07:00
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
2025-10-28 11:46:07 +07:00
|
|
|
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"];
|
2025-10-26 23:25:20 +07:00
|
|
|
|
2025-10-28 11:46:07 +07:00
|
|
|
WiFi.begin(ssid.c_str(), pass.c_str());
|
|
|
|
|
xEventGroupSetBits(parent->bleEventGroup, BLE_CRED_BIT);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
xEventGroupSetBits(parent->bleEventGroup, BLE_SCAN_BIT);
|
|
|
|
|
}
|
2025-10-26 23:25:20 +07:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|