POC BLE provision

This commit is contained in:
samuelbles07
2025-10-26 23:25:20 +07:00
parent 16339acb97
commit 19df574130
3 changed files with 168 additions and 75 deletions

View File

@@ -247,6 +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 */ /** Set offline mode without saving, cause wifi is not configured */
@@ -690,6 +691,7 @@ static void sendDataToAg() {
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting); stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting);
} }
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting); stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting);
wifiConnector.bleNotifyStatus(1);
/** Task handle led connecting animation */ /** Task handle led connecting animation */
xTaskCreate( xTaskCreate(
@@ -718,11 +720,13 @@ static void sendDataToAg() {
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected); stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected);
} }
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected); stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected);
wifiConnector.bleNotifyStatus(2);
} else { } else {
if (ag->isOne()) { if (ag->isOne()) {
stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed); stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed);
} }
stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed); stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed);
wifiConnector.bleNotifyStatus(11);
} }
stateMachine.handleLeds(AgStateMachineNormal); stateMachine.handleLeds(AgStateMachineNormal);
@@ -1044,14 +1048,17 @@ void initializeNetwork() {
if (agClient->isRegisteredOnAgServer() == false) { if (agClient->isRegisteredOnAgServer() == false) {
stateMachine.displaySetAddToDashBoard(); stateMachine.displaySetAddToDashBoard();
stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed); stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed);
wifiConnector.bleNotifyStatus(13);
} else { } else {
stateMachine.displayClearAddToDashBoard(); stateMachine.displayClearAddToDashBoard();
wifiConnector.bleNotifyStatus(12);
} }
} }
stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed); stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed);
delay(DISPLAY_DELAY_SHOW_CONTENT_MS); delay(DISPLAY_DELAY_SHOW_CONTENT_MS);
} else { } else {
ledBarEnabledUpdate(); ledBarEnabledUpdate();
wifiConnector.bleNotifyStatus(3);
} }
} }

View File

@@ -1,52 +1,17 @@
#include "AgWiFiConnector.h" #include "AgWiFiConnector.h"
#include "Arduino.h" #include "Arduino.h"
#include "Libraries/WiFiManager/WiFiManager.h" #include "Libraries/WiFiManager/WiFiManager.h"
#include "Libraries/Arduino_JSON/src/Arduino_JSON.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_CHARACTERISTIC_UUID "703fa252-3d2a-4da9-a05c-83b0d9cacb8e"
#define WIFI() ((WiFiManager *)(this->wifi)) #define WIFI() ((WiFiManager *)(this->wifi))
static bool g_isBLEConnect = false;
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override {
Serial.printf("Client address: %s\n", connInfo.getAddress().toString().c_str());
g_isBLEConnect = true;
}
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override {
Serial.printf("Client disconnected - start advertising\n");
NimBLEDevice::startAdvertising();
g_isBLEConnect = false;
}
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override {
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");
}
};
/** Handler class for characteristic actions */
class CharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override {
Serial.printf("%s : onRead(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override {
Serial.printf("%s : onWrite(), value: %s\n", pCharacteristic->getUUID().toString().c_str(),
pCharacteristic->getValue().c_str());
}
};
/** /**
* @brief Set reference AirGradient instance * @brief Set reference AirGradient instance
* *
@@ -142,9 +107,10 @@ bool WifiConnector::connect(void) {
[](void *obj) { [](void *obj) {
WifiConnector *connector = (WifiConnector *)obj; WifiConnector *connector = (WifiConnector *)obj;
while (connector->_wifiConfigPortalActive()) { while (connector->_wifiConfigPortalActive()) {
if (g_isBLEConnect) { if (connector->isBleClientConnected()) {
Serial.println("Stopping portal because BLE connected"); Serial.println("Stopping portal because BLE connected");
connector->_wifiStop(); connector->_wifiStop();
connector->provisionMethod = ProvisionMethod::BLE;
break; break;
} }
connector->_wifiProcess(); connector->_wifiProcess();
@@ -159,7 +125,7 @@ bool WifiConnector::connect(void) {
uint32_t ledPeriod = millis(); uint32_t ledPeriod = millis();
bool clientConnectChanged = false; bool clientConnectChanged = false;
setupBLE(); setupBLE(ssid);
AgStateMachineState stateOld = sm.getDisplayState(); AgStateMachineState stateOld = sm.getDisplayState();
while (WIFI()->getConfigPortalActive()) { while (WIFI()->getConfigPortalActive()) {
@@ -193,8 +159,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"); Serial.println("Stopping BLE since wifi is connected");
stopBLE(); stopBLE();
provisionMethod = ProvisionMethod::WiFi;
}
} else { } else {
sm.ledAnimationInit(); sm.ledAnimationInit();
sm.handleLeds(AgStateMachineWiFiManagerMode); sm.handleLeds(AgStateMachineWiFiManagerMode);
@@ -211,25 +180,25 @@ bool WifiConnector::connect(void) {
_wifiProcess(); _wifiProcess();
#endif #endif
while (1) { if (provisionMethod == ProvisionMethod::BLE) {
disp.setText("Provision by", "BLE", "");
while (isBleClientConnected() && !wifiConnecting) {
Serial.println("Wait for WiFi credentials through BLE");
delay(1000); delay(1000);
} }
/** Set wifi connect */
WiFi.begin("hbonfam", "51burian");
/** Wait for wifi connect to AP */
int count = 0; int count = 0;
while (WiFi.status() != WL_CONNECTED) { while (WiFi.status() != WL_CONNECTED) {
delay(1000); delay(1000);
count++; count++;
if (count >= 15) { if (count >= 15) {
logError("Try connect to default wifi \"" + String(this->defaultSsid) + // give up
String("\" failed")); WiFi.disconnect();
break; break;
} }
} }
}
/** Show display wifi connect result failed */ /** Show display wifi connect result failed */
if (WiFi.isConnected() == false) { if (WiFi.isConnected() == false) {
@@ -237,6 +206,7 @@ bool WifiConnector::connect(void) {
if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) { if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) {
sm.displayHandle(AgStateMachineWiFiManagerConnectFailed); sm.displayHandle(AgStateMachineWiFiManagerConnectFailed);
} }
bleNotifyStatus(10);
delay(6000); delay(6000);
} else { } else {
hasConfig = true; hasConfig = true;
@@ -250,6 +220,7 @@ bool WifiConnector::connect(void) {
config.setDisableCloudConnection(result == "T"); config.setDisableCloudConnection(result == "T");
} }
hasPortalConfig = false; hasPortalConfig = false;
bleNotifyStatus(0);
} }
return true; return true;
@@ -276,6 +247,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
* *
@@ -478,6 +454,24 @@ bool WifiConnector::hasConfigurated(void) {
*/ */
bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; } bool WifiConnector::isConfigurePorttalTimeout(void) { return connectorTimeout; }
void WifiConnector::bleNotifyStatus(int status) {
if (pServer->getConnectedCount()) {
NimBLEService* pSvc = pServer->getServiceByUUID(BLE_SERVICE_UUID);
if (pSvc) {
NimBLECharacteristic* pChr = pSvc->getCharacteristic(BLE_CHARACTERISTIC_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
* *
@@ -487,32 +481,88 @@ void WifiConnector::setDefault(void) {
} }
void WifiConnector::setupBLE() { void WifiConnector::setupBLE(String bleName) {
NimBLEDevice::init("AirGradient"); NimBLEDevice::init(bleName.c_str());
NimBLEDevice::setPower(3); /** +3db */ NimBLEDevice::setPower(3); /** +3db */
/** bonding, MITM, don't need BLE secure connections as we are using passkey pairing */ /** bonding, MITM, don't need BLE secure connections as we are using passkey pairing */
NimBLEDevice::setSecurityAuth(false, false, true); NimBLEDevice::setSecurityAuth(false, false, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT);
NimBLEServer *pServer = NimBLEDevice::createServer(); pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks()); pServer->setCallbacks(new ServerCallbacks(this));
NimBLEService *pService = pServer->createService("acbcfea8-e541-4c40-9bfd-17820f16c95c"); NimBLEService *pService = pServer->createService(BLE_SERVICE_UUID);
NimBLECharacteristic *pSecureCharacteristic = NimBLECharacteristic *pSecureCharacteristic =
pService->createCharacteristic("703fa252-3d2a-4da9-a05c-83b0d9cacb8e", pService->createCharacteristic(BLE_CHARACTERISTIC_UUID,
NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC |
NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC); NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY);
pSecureCharacteristic->setCallbacks(new CharacteristicCallbacks()); pSecureCharacteristic->setCallbacks(new CharacteristicCallbacks(this));
pService->start(); pService->start();
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->addServiceUUID(pService->getUUID()); pAdvertising->addServiceUUID(pService->getUUID());
pAdvertising->start(); pAdvertising->start();
bleServerRunning = true;
Serial.println("Provision by BLE ready");
} }
void WifiConnector::stopBLE() { void WifiConnector::stopBLE() {
if (bleServerRunning) {
Serial.println("Stopping BLE");
NimBLEDevice::deinit(); 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());
JSONVar root = JSON.parse(pCharacteristic->getValue().c_str());
String ssid = root["ssid"];
String pass = root["password"];
Serial.printf("Connecting to %s...\n", ssid.c_str());
WiFi.begin(ssid.c_str(), pass.c_str());
parent->wifiConnecting = true;
}

View File

@@ -13,11 +13,19 @@
#include <NimBLEDevice.h> #include <NimBLEDevice.h>
class WifiConnector : public PrintLog { class WifiConnector : public PrintLog {
public:
enum class ProvisionMethod {
Unknown = 0,
WiFi,
BLE
};
private: private:
AirGradient *ag; AirGradient *ag;
OledDisplay &disp; OledDisplay &disp;
StateMachine &sm; StateMachine &sm;
Configuration &config; Configuration &config;
NimBLEServer *pServer;
String ssid; String ssid;
void *wifi = NULL; void *wifi = NULL;
@@ -25,20 +33,45 @@ 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();
void setupBLE(); // BLE server handler
void stopBLE(); 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;
};
NimBLEServer *pServer;
public: public:
void setAirGradient(AirGradient *ag); void setAirGradient(AirGradient *ag);
WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config); WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration &config);
~WifiConnector(); ~WifiConnector();
void setupBLE(String bleName);
void stopBLE();
bool connect(void); bool connect(void);
void disconnect(void); void disconnect(void);
void handle(void); void handle(void);
@@ -56,8 +89,11 @@ public:
bool hasConfigurated(void); bool hasConfigurated(void);
bool isConfigurePorttalTimeout(void); bool isConfigurePorttalTimeout(void);
const char* defaultSsid = "airgradient";
const char* defaultPassword = "cleanair"; void bleNotifyStatus(int status);
const char *defaultSsid = "airgradient";
const char *defaultPassword = "cleanair";
void setDefault(void); void setDefault(void);
}; };