diff --git a/Documentation/SSID List Communication Flow.png b/Documentation/SSID List Communication Flow.png new file mode 100644 index 0000000..e64802d Binary files /dev/null and b/Documentation/SSID List Communication Flow.png differ diff --git a/include/BLE.cpp b/include/BLE.cpp index 9bd5c40..a6e48aa 100644 --- a/include/BLE.cpp +++ b/include/BLE.cpp @@ -3,9 +3,14 @@ #include "WiFi.hpp" #include "nvs_flash.h" #include "defines.h" +#include +#include "bmHTTP.hpp" std::atomic flag_scan_requested{false}; std::atomic credsGiven{false}; +std::atomic tokenGiven{false}; +std::atomic isBLEClientConnected{false}; +std::atomic scanBlock{false}; std::mutex dataMutex; wifi_auth_mode_t auth; @@ -17,6 +22,10 @@ static std::string UNAME = ""; // Global pointers to characteristics for notification support NimBLECharacteristic* ssidListChar = nullptr; NimBLECharacteristic* connectConfirmChar = nullptr; +NimBLECharacteristic* authConfirmChar = nullptr; +NimBLECharacteristic* credsChar = nullptr; +NimBLECharacteristic* tokenChar = nullptr; +NimBLECharacteristic* ssidRefreshChar = nullptr; NimBLEAdvertising* initBLE() { NimBLEDevice::init("BlindMaster-C6"); @@ -40,22 +49,36 @@ NimBLEAdvertising* initBLE() { // 0x0000 - SSID List (READ + NOTIFY) ssidListChar = pService->createCharacteristic( "0000", - NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY + NIMBLE_PROPERTY::READ ); ssidListChar->createDescriptor("2902"); // Add BLE2902 descriptor for notifications - // 0x0001 - Credentials JSON (WRITE) - Replaces separate SSID/Password/Token - // Expected JSON format: {"ssid":"network","password":"pass","token":"optional"} - NimBLECharacteristic *credsChar = pService->createCharacteristic( + // 0x0001 - Credentials JSON (WRITE) - Replaces separate SSID/Password/Uname + // Expected JSON format: {"ssid":"network","password":"pass"} + credsChar = pService->createCharacteristic( "0001", NIMBLE_PROPERTY::WRITE ); credsChar->setCallbacks(charCallbacks); + + // 0x0002 - Token (WRITE) + tokenChar = pService->createCharacteristic( + "0002", + NIMBLE_PROPERTY::WRITE + ); + tokenChar->setCallbacks(charCallbacks); + + // 0x0003 - Auth Confirmation (READ + NOTIFY) + authConfirmChar = pService->createCharacteristic( + "0003", + NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY + ); + authConfirmChar->createDescriptor("2902"); // Add BLE2902 descriptor for notifications // 0x0004 - SSID Refresh (WRITE) - NimBLECharacteristic *ssidRefreshChar = pService->createCharacteristic( + ssidRefreshChar = pService->createCharacteristic( "0004", - NIMBLE_PROPERTY::WRITE + NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY ); ssidRefreshChar->setCallbacks(charCallbacks); @@ -82,14 +105,28 @@ NimBLEAdvertising* initBLE() { return pAdvertising; } +void notifyConnectionStatus(bool success) { + connectConfirmChar->setValue(success ? "Connected" : "Error"); + connectConfirmChar->notify(); + connectConfirmChar->setValue(""); // Clear value after notify +} +void notifyAuthStatus(bool success) { + authConfirmChar->setValue(success ? "Authenticated" : "Error"); + authConfirmChar->notify(); + authConfirmChar->setValue(""); // Clear value after notify +} + bool BLEtick(NimBLEAdvertising* pAdvertising) { if(flag_scan_requested) { flag_scan_requested = false; - printf("Scanning WiFi...\n"); - scanAndUpdateSSIDList(); + if (!scanBlock) { + scanBlock = true; + printf("Scanning WiFi...\n"); + scanAndUpdateSSIDList(); + } + else printf("Duplicate scan request\n"); } else if (credsGiven) { - credsGiven = false; std::string tmpSSID; std::string tmpUNAME; std::string tmpPASS; @@ -100,18 +137,25 @@ bool BLEtick(NimBLEAdvertising* pAdvertising) { tmpUNAME = UNAME; tmpPASS = PASS; tmpAUTH = auth; + credsGiven = false; } bool wifiConnect; if (tmpAUTH == WIFI_AUTH_WPA2_ENTERPRISE || tmpAUTH == WIFI_AUTH_WPA3_ENTERPRISE) wifiConnect = bmWiFi.attemptConnect(tmpSSID.c_str(), tmpUNAME.c_str(), tmpPASS.c_str(), tmpAUTH); else wifiConnect = bmWiFi.attemptConnect(tmpSSID.c_str(), tmpPASS.c_str(), tmpAUTH); - if (!wifiConnect) return false; + if (!wifiConnect) { + // notify errored + notifyConnectionStatus(false); + return false; + } nvs_handle_t WiFiHandle; esp_err_t err = nvs_open(nvsWiFi, NVS_READWRITE, &WiFiHandle); if (err != ESP_OK) { printf("ERROR Saving Credentials\n"); + // notify errored + notifyConnectionStatus(false); return false; } else { @@ -122,12 +166,85 @@ bool BLEtick(NimBLEAdvertising* pAdvertising) { if (err == ESP_OK) nvs_commit(WiFiHandle); nvs_close(WiFiHandle); } + if (err == ESP_OK) { + // notify connected + notifyConnectionStatus(true); + } + else { + // notify connected + notifyConnectionStatus(false); + } + } + else if (tokenGiven) { + tokenGiven = false; + if (!bmWiFi.isConnected()) { + printf("ERROR: token given without WiFi connection\n"); + notifyAuthStatus(false); + return false; + } - // Authenticate with server here + // HTTP request to verify device with token + std::string tmpTOKEN; + { + std::lock_guard lock(dataMutex); + tmpTOKEN = TOKEN; + } + + cJSON *responseRoot; + bool success = httpGET("verify_device", tmpTOKEN, responseRoot); + if (!success) return false; + success = false; + + if (responseRoot != NULL) { + cJSON *tokenItem = cJSON_GetObjectItem(responseRoot, "token"); + if (cJSON_IsString(tokenItem) && tokenItem->valuestring != NULL) { + printf("New token received: %s\n", tokenItem->valuestring); + + // Save token to NVS + nvs_handle_t AuthHandle; + esp_err_t nvs_err = nvs_open(nvsAuth, NVS_READWRITE, &AuthHandle); + if (nvs_err == ESP_OK) { + nvs_err = nvs_set_str(AuthHandle, tokenTag, tokenItem->valuestring); + if (nvs_err == ESP_OK) { + nvs_commit(AuthHandle); + success = true; + webToken = tokenItem->valuestring; + } + else printf("ERROR: could not save webToken to NVS\n"); + nvs_close(AuthHandle); + } + else printf("ERROR: Couldn't open NVS for auth token\n"); + } + cJSON_Delete(responseRoot); + } + else printf("Failed to parse JSON response\n"); + + notifyAuthStatus(success); + return success; } return false; } +void reset() { + esp_wifi_scan_stop(); + scanBlock = false; + flag_scan_requested = false; + credsGiven = false; + tokenGiven = false; +} + +void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) { + isBLEClientConnected = true; + printf("Client connected\n"); + reset(); +}; + +void MyServerCallbacks::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) { + isBLEClientConnected = false; + printf("Client disconnected - reason: %d\n", reason); + reset(); +} + void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo) { std::string val = pChar->getValue(); std::string uuidStr = pChar->getUUID().toString(); @@ -135,7 +252,7 @@ void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connI printf("onWrite called! UUID: %s, Value length: %d\n", uuidStr.c_str(), val.length()); // Check which characteristic was written to - if (uuidStr.find("0001") != std::string::npos) { + if (pChar == credsChar) { // Credentials JSON characteristic if (val.length() > 0) { printf("Received JSON: %s\n", val.c_str()); @@ -145,7 +262,6 @@ void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connI if (root != NULL) { cJSON *ssid = cJSON_GetObjectItem(root, "ssid"); cJSON *password = cJSON_GetObjectItem(root, "password"); - cJSON *token = cJSON_GetObjectItem(root, "token"); cJSON *authType = cJSON_GetObjectItem(root, "auth"); cJSON *uname = cJSON_GetObjectItem(root, "uname"); @@ -169,8 +285,7 @@ void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connI bool ssidPresent = cJSON_IsString(ssid) && ssid->valuestring != NULL; bool passPresent = cJSON_IsString(password) && password->valuestring != NULL; bool unamePresent = cJSON_IsString(uname) && uname->valuestring != NULL; - bool tokenPresent = cJSON_IsString(token) && token->valuestring != NULL; - bool tempCredsGiven = tokenPresent && ssidPresent && (passPresent || open) && + bool tempCredsGiven = ssidPresent && (passPresent || open) && (unamePresent || !enterprise); if (tempCredsGiven) { @@ -179,7 +294,6 @@ void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connI auth = (wifi_auth_mode_t)(authType->valueint); SSID = ssid->valuestring; - TOKEN = token->valuestring; PASS = passPresent ? password->valuestring : ""; UNAME = unamePresent ? uname->valuestring : ""; credsGiven = tempCredsGiven; // update the global flag. @@ -192,10 +306,24 @@ void MyCharCallbacks::onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connI } } } - else if (uuidStr.find("0004") != std::string::npos) { - // Refresh characteristic - printf("Refresh Requested\n"); - flag_scan_requested = true; + else if (pChar == tokenChar) { + if (val.length() > 0) { + printf("Received Token: %s\n", val.c_str()); + std::lock_guard lock(dataMutex); + TOKEN = val; + tokenGiven = true; + } + } + else if (pChar == ssidRefreshChar) { + if (val == "Start") { + // Refresh characteristic + printf("Refresh Requested\n"); + flag_scan_requested = true; + } + else if (val == "Done") { + printf("Data read complete\n"); + scanBlock = false; + } } else printf("Unknown UUID: %s\n", uuidStr.c_str()); } \ No newline at end of file diff --git a/include/BLE.hpp b/include/BLE.hpp index 57ac57d..b88bca2 100644 --- a/include/BLE.hpp +++ b/include/BLE.hpp @@ -4,26 +4,21 @@ #include "NimBLEDevice.h" #include "cJSON.h" #include -#include #include #include "esp_wifi_types.h" // Global pointers to characteristics for notification support extern NimBLECharacteristic* ssidListChar; -extern NimBLECharacteristic* connectConfirmChar; +extern NimBLECharacteristic* ssidRefreshChar; +extern std::atomic isBLEClientConnected{false}; class MyServerCallbacks : public NimBLEServerCallbacks { - void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) { - printf("Client connected\n"); - }; - - void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) { - printf("Client disconnected - reason: %d\n", reason); - // Advertising will restart automatically - } + void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo); + void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason); }; class MyCharCallbacks : public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo); void onWrite(NimBLECharacteristic* pChar, NimBLEConnInfo& connInfo); }; diff --git a/include/WiFi.cpp b/include/WiFi.cpp index e56a242..d75f893 100644 --- a/include/WiFi.cpp +++ b/include/WiFi.cpp @@ -3,7 +3,7 @@ #include "cJSON.h" // Native replacement for ArduinoJson #include "BLE.hpp" -bool WiFi::authFailed = false; +std::atomic WiFi::authFailed{false}; EventGroupHandle_t WiFi::s_wifi_event_group = NULL; esp_netif_t* WiFi::netif = NULL; esp_event_handler_instance_t WiFi::instance_any_id = NULL; @@ -49,6 +49,13 @@ void WiFi::event_handler(void* arg, esp_event_base_t event_base, } xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } + else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { + // This is triggered when the scan finishes! + printf("Scan complete, processing results...\n"); + + // Call a function to process results and notify BLE + processScanResults(); + } // 3. We got an IP Address -> Success! else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data; @@ -62,7 +69,7 @@ void WiFi::init() { // 1. Init Network Interface ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); - esp_netif_create_default_wifi_sta(); + netif = esp_netif_create_default_wifi_sta(); // 2. Init WiFi Driver wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); @@ -155,6 +162,11 @@ bool WiFi::awaitConnected() { uint8_t attempts = 0; while (!isConnected() && attempts < 20) { + if (!isBLEClientConnected) { + printf("BLE Disconnected: Aborting WiFi connection attempt.\n"); + esp_wifi_disconnect(); + return false; + } if (authFailed) { printf("SSID/Password was wrong! Aborting connection attempt.\n"); return false; @@ -167,24 +179,29 @@ bool WiFi::awaitConnected() { } // ------------- non-class -------------- -void scanAndUpdateSSIDList() { +void WiFi::scanAndUpdateSSIDList() { printf("Starting WiFi Scan...\n"); // 1. Start Scan (Blocking Mode = true) // In blocking mode, this function waits here until scan is done (~2 seconds) + esp_wifi_sta_enterprise_disable(); + esp_wifi_disconnect(); + wifi_scan_config_t scan_config = { .ssid = NULL, .bssid = NULL, .channel = 0, .show_hidden = false }; - esp_err_t err = esp_wifi_scan_start(&scan_config, true); + esp_err_t err = esp_wifi_scan_start(&scan_config, false); if (err != ESP_OK) { printf("Scan failed!\n"); return; } +} +void WiFi::processScanResults() { // 2. Get the results uint16_t ap_count = 0; esp_wifi_scan_get_ap_num(&ap_count); @@ -199,14 +216,14 @@ void scanAndUpdateSSIDList() { cJSON *root = cJSON_CreateArray(); for (int i = 0; i < ap_count; i++) { - cJSON *item = cJSON_CreateObject(); - // ESP-IDF stores SSID as uint8_t, cast to char* - cJSON_AddStringToObject(item, "ssid", (char *)ap_list[i].ssid); - cJSON_AddNumberToObject(item, "rssi", ap_list[i].rssi); - // Add encryption type if you want (optional) - cJSON_AddNumberToObject(item, "auth", ap_list[i].authmode); - - cJSON_AddItemToArray(root, item); + cJSON *item = cJSON_CreateObject(); + // ESP-IDF stores SSID as uint8_t, cast to char* + cJSON_AddStringToObject(item, "ssid", (char *)ap_list[i].ssid); + cJSON_AddNumberToObject(item, "rssi", ap_list[i].rssi); + // Add encryption type if you want (optional) + cJSON_AddNumberToObject(item, "auth", ap_list[i].authmode); + + cJSON_AddItemToArray(root, item); } // 4. Convert to String @@ -215,11 +232,12 @@ void scanAndUpdateSSIDList() { // 5. Update BLE if (ssidListChar != nullptr) { - ssidListChar->setValue(std::string(json_string)); - ssidListChar->notify(); + ssidListChar->setValue(json_string); + ssidRefreshChar->setValue("Ready"); + ssidRefreshChar->notify(); } - // 6. Cleanup Memory (CRITICAL in C++) + // 6. Cleanup Memory free(ap_list); cJSON_Delete(root); // This deletes all children (items) too free(json_string); // cJSON_Print allocates memory, you must free it diff --git a/include/WiFi.hpp b/include/WiFi.hpp index 297cfd8..353a999 100644 --- a/include/WiFi.hpp +++ b/include/WiFi.hpp @@ -11,8 +11,11 @@ class WiFi { const wifi_auth_mode_t authMode); static bool attemptConnect(const std::string ssid, const std::string uname, const std::string password, const wifi_auth_mode_t authMode); + static bool isConnected(); + static void scanAndUpdateSSIDList(); private: - static bool authFailed; + static void processScanResults(); + static std::atomic authFailed; static bool awaitConnected(); static esp_event_handler_instance_t instance_any_id; static esp_event_handler_instance_t instance_got_ip; @@ -20,7 +23,6 @@ class WiFi { static esp_netif_t* netif; static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); - static bool isConnected(); static std::string getIP(); }; diff --git a/include/bmHTTP.cpp b/include/bmHTTP.cpp new file mode 100644 index 0000000..e35f6ce --- /dev/null +++ b/include/bmHTTP.cpp @@ -0,0 +1,47 @@ +#include "bmHTTP.hpp" +#include "esp_http_client.h" +#define httpSrv "http://192.168.1.190:3000/" + +std::string webToken; + +bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse) { + std::string url = std::string(httpSrv) + endpoint; + + esp_http_client_config_t config = {}; + config.url = url.c_str(); + config.method = HTTP_METHOD_GET; + + esp_http_client_handle_t client = esp_http_client_init(&config); + + // Add authorization header + std::string authHeader = "Bearer " + token; + esp_http_client_set_header(client, "Authorization", authHeader.c_str()); + + esp_err_t err = esp_http_client_perform(client); + bool success = false; + + if (err == ESP_OK) { + int status_code = esp_http_client_get_status_code(client); + int content_length = esp_http_client_get_content_length(client); + + printf("HTTP Status = %d, content_length = %d\n", status_code, content_length); + + if (status_code == 200) { + std::string responseData = ""; + char buffer[512]; // Read in 512-byte blocks + int read_len; + + // Read until the server stops sending (read_len <= 0) + while ((read_len = esp_http_client_read(client, buffer, sizeof(buffer))) > 0) + responseData.append(buffer, read_len); + + if (!responseData.empty()) { + JSONresponse = cJSON_Parse(responseData.c_str()); + success = (JSONresponse != NULL); + } + } + } else printf("HTTP request failed: %s\n", esp_err_to_name(err)); + + esp_http_client_cleanup(client); + return success; +} \ No newline at end of file diff --git a/include/bmHTTP.hpp b/include/bmHTTP.hpp new file mode 100644 index 0000000..c467497 --- /dev/null +++ b/include/bmHTTP.hpp @@ -0,0 +1,10 @@ +#ifndef BMHTTP +#define BMHTTP +#include +#include "cJSON.h" + +extern std::string webToken; + +bool httpGET(std::string endpoint, std::string token, cJSON* &JSONresponse); + +#endif \ No newline at end of file diff --git a/include/defines.h b/include/defines.h index d9494a2..6c3072d 100644 --- a/include/defines.h +++ b/include/defines.h @@ -12,6 +12,8 @@ #define passTag "PW" #define authTag "AuthMode" #define unameTag "UNAME" +#define nvsAuth "AUTH" +#define tokenTag "TOKEN" #define getMovingCW(port) ((movingCW & (1 << port)) >> port) #define setMovingCW(port) (movingCW |= (1 << port)) diff --git a/include/setup.cpp b/include/setup.cpp index f414525..26c68e3 100644 --- a/include/setup.cpp +++ b/include/setup.cpp @@ -5,8 +5,7 @@ void initialSetup() { NimBLEAdvertising* pAdv = initBLE(); - while (1) { // try to connect to wifi too. - BLEtick(pAdv); + while (!BLEtick(pAdv)) { vTaskDelay(pdMS_TO_TICKS(10)); } } \ No newline at end of file