diff --git a/examples/OneOpenAir/OneOpenAir.ino b/examples/OneOpenAir/OneOpenAir.ino index 868e381..304269d 100644 --- a/examples/OneOpenAir/OneOpenAir.ino +++ b/examples/OneOpenAir/OneOpenAir.ino @@ -247,6 +247,7 @@ void setup() { if (connectToNetwork) { oledDisplay.setText("Initialize", "network...", ""); initializeNetwork(); + wifiConnector.stopBLE(); } /** Set offline mode without saving, cause wifi is not configured */ @@ -690,6 +691,7 @@ static void sendDataToAg() { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnecting); } stateMachine.handleLeds(AgStateMachineWiFiOkServerConnecting); + wifiConnector.bleNotifyStatus(1); /** Task handle led connecting animation */ xTaskCreate( @@ -718,11 +720,13 @@ static void sendDataToAg() { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnected); } stateMachine.handleLeds(AgStateMachineWiFiOkServerConnected); + wifiConnector.bleNotifyStatus(2); } else { if (ag->isOne()) { stateMachine.displayHandle(AgStateMachineWiFiOkServerConnectFailed); } stateMachine.handleLeds(AgStateMachineWiFiOkServerConnectFailed); + wifiConnector.bleNotifyStatus(11); } stateMachine.handleLeds(AgStateMachineNormal); @@ -1044,14 +1048,17 @@ void initializeNetwork() { if (agClient->isRegisteredOnAgServer() == false) { stateMachine.displaySetAddToDashBoard(); stateMachine.displayHandle(AgStateMachineWiFiOkServerOkSensorConfigFailed); + wifiConnector.bleNotifyStatus(13); } else { stateMachine.displayClearAddToDashBoard(); + wifiConnector.bleNotifyStatus(12); } } stateMachine.handleLeds(AgStateMachineWiFiOkServerOkSensorConfigFailed); delay(DISPLAY_DELAY_SHOW_CONTENT_MS); } else { ledBarEnabledUpdate(); + wifiConnector.bleNotifyStatus(3); } } diff --git a/src/AgWiFiConnector.cpp b/src/AgWiFiConnector.cpp index c21adf8..174fbe7 100644 --- a/src/AgWiFiConnector.cpp +++ b/src/AgWiFiConnector.cpp @@ -1,52 +1,17 @@ #include "AgWiFiConnector.h" #include "Arduino.h" #include "Libraries/WiFiManager/WiFiManager.h" +#include "Libraries/Arduino_JSON/src/Arduino_JSON.h" #define WIFI_CONNECT_COUNTDOWN_MAX 180 #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)) -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 * @@ -142,9 +107,10 @@ bool WifiConnector::connect(void) { [](void *obj) { WifiConnector *connector = (WifiConnector *)obj; while (connector->_wifiConfigPortalActive()) { - if (g_isBLEConnect) { + if (connector->isBleClientConnected()) { Serial.println("Stopping portal because BLE connected"); connector->_wifiStop(); + connector->provisionMethod = ProvisionMethod::BLE; break; } connector->_wifiProcess(); @@ -159,7 +125,7 @@ bool WifiConnector::connect(void) { uint32_t ledPeriod = millis(); bool clientConnectChanged = false; - setupBLE(); + setupBLE(ssid); AgStateMachineState stateOld = sm.getDisplayState(); while (WIFI()->getConfigPortalActive()) { @@ -193,8 +159,11 @@ bool WifiConnector::connect(void) { clientConnectChanged = clientConnected; if (clientConnectChanged) { sm.handleLeds(AgStateMachineWiFiManagerPortalActive); - Serial.println("Stopping BLE since wifi is connected"); - stopBLE(); + if (bleServerRunning) { + Serial.println("Stopping BLE since wifi is connected"); + stopBLE(); + provisionMethod = ProvisionMethod::WiFi; + } } else { sm.ledAnimationInit(); sm.handleLeds(AgStateMachineWiFiManagerMode); @@ -211,23 +180,23 @@ bool WifiConnector::connect(void) { _wifiProcess(); #endif - while (1) { - delay(1000); - } + if (provisionMethod == ProvisionMethod::BLE) { + disp.setText("Provision by", "BLE", ""); + while (isBleClientConnected() && !wifiConnecting) { + Serial.println("Wait for WiFi credentials through BLE"); + delay(1000); + } - /** Set wifi connect */ - WiFi.begin("hbonfam", "51burian"); - - /** 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; + int count = 0; + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + count++; + if (count >= 15) { + // give up + WiFi.disconnect(); + break; + } } } @@ -237,6 +206,7 @@ bool WifiConnector::connect(void) { if (ag->isOne() || ag->isPro4_2() || ag->isPro3_3() || ag->isBasic()) { sm.displayHandle(AgStateMachineWiFiManagerConnectFailed); } + bleNotifyStatus(10); delay(6000); } else { hasConfig = true; @@ -250,6 +220,7 @@ bool WifiConnector::connect(void) { config.setDisableCloudConnection(result == "T"); } hasPortalConfig = false; + bleNotifyStatus(0); } return true; @@ -276,6 +247,11 @@ bool WifiConnector::wifiClientConnected(void) { return WiFi.softAPgetStationNum() ? true : false; } + +bool WifiConnector::isBleClientConnected() { + return bleClientConnected; +} + /** * @brief Handle WiFiManage softAP setup completed callback * @@ -478,6 +454,24 @@ bool WifiConnector::hasConfigurated(void) { */ 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 * @@ -487,32 +481,88 @@ void WifiConnector::setDefault(void) { } -void WifiConnector::setupBLE() { - NimBLEDevice::init("AirGradient"); +void WifiConnector::setupBLE(String bleName) { + NimBLEDevice::init(bleName.c_str()); 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); - NimBLEServer *pServer = NimBLEDevice::createServer(); - pServer->setCallbacks(new ServerCallbacks()); + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks(this)); - NimBLEService *pService = pServer->createService("acbcfea8-e541-4c40-9bfd-17820f16c95c"); + NimBLEService *pService = pServer->createService(BLE_SERVICE_UUID); NimBLECharacteristic *pSecureCharacteristic = - pService->createCharacteristic("703fa252-3d2a-4da9-a05c-83b0d9cacb8e", + pService->createCharacteristic(BLE_CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | - NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC); - pSecureCharacteristic->setCallbacks(new CharacteristicCallbacks()); + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::NOTIFY); + pSecureCharacteristic->setCallbacks(new CharacteristicCallbacks(this)); pService->start(); NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->addServiceUUID(pService->getUUID()); pAdvertising->start(); + bleServerRunning = true; + Serial.println("Provision by BLE ready"); } void WifiConnector::stopBLE() { - NimBLEDevice::deinit(); - + 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()); + + 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; +} + diff --git a/src/AgWiFiConnector.h b/src/AgWiFiConnector.h index 4778dde..56fa74f 100644 --- a/src/AgWiFiConnector.h +++ b/src/AgWiFiConnector.h @@ -13,11 +13,19 @@ #include class WifiConnector : public PrintLog { +public: + enum class ProvisionMethod { + Unknown = 0, + WiFi, + BLE + }; + private: AirGradient *ag; OledDisplay &disp; StateMachine &sm; Configuration &config; + NimBLEServer *pServer; String ssid; void *wifi = NULL; @@ -25,20 +33,45 @@ private: uint32_t lastRetry; bool hasPortalConfig = false; bool connectorTimeout = false; + bool bleServerRunning = false; + bool bleClientConnected = false; + bool wifiConnecting = false; + ProvisionMethod provisionMethod = ProvisionMethod::Unknown; bool wifiClientConnected(void); + bool isBleClientConnected(); - void setupBLE(); - void stopBLE(); + // 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; + }; - NimBLEServer *pServer; public: void setAirGradient(AirGradient *ag); - WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration& config); + WifiConnector(OledDisplay &disp, Stream &log, StateMachine &sm, Configuration &config); ~WifiConnector(); + void setupBLE(String bleName); + void stopBLE(); bool connect(void); void disconnect(void); void handle(void); @@ -56,8 +89,11 @@ public: bool hasConfigurated(void); bool isConfigurePorttalTimeout(void); - const char* defaultSsid = "airgradient"; - const char* defaultPassword = "cleanair"; + + void bleNotifyStatus(int status); + + const char *defaultSsid = "airgradient"; + const char *defaultPassword = "cleanair"; void setDefault(void); };