├── .gitignore ├── .travis.yml ├── .vscode └── extensions.json ├── LICENSE.md ├── README.md ├── include └── README ├── lib └── README ├── platformio.ini ├── src ├── .vscode │ ├── arduino.json │ └── c_cpp_properties.json └── esp32_wifi_ble_config_advanced.cpp └── test └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Bernd Giesecke 4 | 5 | Copyright (c) 2020 Uri Shani 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WiFi configuration over BLE 2 | 3 | A esp32-Arduino sketch used to configure WiFi credentials over Bluetooth LE on a ESP32 WROOM. \ 4 | A web based app for configuration can be found [here](https://urishx.github.io/Nuxt_esp32_web-ble_wifi_config/), the code lives in [my github repo](https://github.com/UriShX/Nuxt_esp32_web-ble_wifi_config). This app is written in NuxtJS, and is MIT licensed. \ 5 | An older version of the web app can be found [here](https://urishx.github.io/esp32_web-ble_wifi_config/), with it's code [on Github](https://github.com/UriShX/esp32_web-ble_wifi_config). This version is written with KnockoutJS and JQuery, and is also MIT licensed, but less secure and the code is harder to follow. 6 | 7 | ### Requirements: 8 | ArduinoJson v.5 (checked with 5.13.4) 9 | 10 | #### Confirmed working environments: 11 | * Arduino 1.8.11 & esp32-arduino 1.0.4 12 | * PlatformIO Home 3.1.0, Core 4.2.1, Espressif 32 1.11.2 13 | 14 | ### Based on Bernd Giescke's (beegee1962) sketch for WiFi configuration over BLE: 15 | Documentation: https://desire.giesecke.tk/index.php/2018/04/06/esp32-wifi-setup-over-ble/ \ 16 | Code: https://bitbucket.org/beegee1962/esp32_wifi_ble_esp32/src/master/ 17 | 18 | ### Additions made by Uri Shani (UriShX), 02/2020: 19 | 1. Additional characteristic for getting SSID list over BLE (read only) 20 | 2. Additional characteristic for serving connection status as notifications, every 1 second 21 | 22 | Published under the MIT license, see [LICENSE.md](https://github.com/UriShX/esp32_wifi_ble_advanced/LICENSE.md) -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | board = esp32dev 13 | framework = arduino 14 | platform = espressif32 15 | board_build.partitions = min_spiffs.csv 16 | lib_deps = ArduinoJson@5.13.4 17 | monitor_speed = 115200 18 | -------------------------------------------------------------------------------- /src/.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "sketch": "beegee1962-esp32_wifi_ble_esp32.ino", 3 | "board": "esp32:esp32:lolin32", 4 | "configuration": "FlashFreq=80,PartitionScheme=min_spiffs,CPUFreq=240,UploadSpeed=921600", 5 | "port": "/dev/ttyUSB0" 6 | } -------------------------------------------------------------------------------- /src/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "/home/urish/.arduino15/packages/esp32/tools/**", 7 | "/home/urish/Dropbox/Arduino/libraries/**", 8 | "/home/urish/.arduino15/packages/esp32/hardware/esp32/1.0.4/**" 9 | ], 10 | "forcedInclude": [], 11 | "intelliSenseMode": "gcc-x64", 12 | "compilerPath": "/usr/bin/clang", 13 | "cStandard": "c11", 14 | "cppStandard": "c++17" 15 | } 16 | ], 17 | "version": 4 18 | } -------------------------------------------------------------------------------- /src/esp32_wifi_ble_config_advanced.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * WiFi configuration over BLE 3 | * 4 | * Used to configure WiFi credentials over Bluetooth LE on a ESP32 WROOM. 5 | * 6 | * Requirements: 7 | * ArduinoJson v.5 (checked with 5.13.4) 8 | * Confirmed working envioronments: 9 | * - Arduino 1.8.11 & esp32-arduino 1.0.4 10 | * - PlatformIO Home 3.1.0, Core 4.2.1, Espressif 32 1.11.2 11 | * 12 | * Based on Bernd Giescke's (beegee1962) sketch for WiFi configuration over BLE: 13 | * Documentation: https://desire.giesecke.tk/index.php/2018/04/06/esp32-wifi-setup-over-ble/ 14 | * Code: https://bitbucket.org/beegee1962/esp32_wifi_ble_esp32/src/master/ 15 | * 16 | * Additions made by Uri Shani (UriShX), 02/2020: 17 | * 1. Additional characteristic for getting SSID list over BLE (read only) 18 | * 2. Additional characteristic for serving connection status as notifications, every 1 second 19 | * 20 | * Published under the MIT license, see LICENSE.md 21 | */ 22 | 23 | // Default Arduino includes 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | // Includes for JSON object handling 30 | // Requires ArduinoJson library, ver.<6 (latest checked: 5.13.4) 31 | // https://arduinojson.org 32 | // https://github.com/bblanchon/ArduinoJson 33 | #include 34 | 35 | // Includes for BLE 36 | #include 37 | #include 38 | #include 39 | #include 40 | // BLE notify and indicate properties, used for connection status update 41 | #include 42 | 43 | // Flash storage of variables (instead of EEPROM) 44 | #include 45 | 46 | /** freeRTOS task handle */ 47 | TaskHandle_t sendBLEdataTask; 48 | /** freeRTOS mutex handle */ 49 | SemaphoreHandle_t connStatSemaphore; 50 | 51 | /** Build time */ 52 | const char compileDate[] = __DATE__ " " __TIME__; 53 | 54 | /** Unique device name */ 55 | char apName[] = "ESP32-xxxxxxxxxxxx"; 56 | /** Selected network 57 | true = use primary network 58 | false = use secondary network 59 | */ 60 | bool usePrimAP = true; 61 | /** Flag if stored AP credentials are available */ 62 | bool hasCredentials = false; 63 | /** Number of found SSIDs */ 64 | int apNum = 0; 65 | /** Time of last SSID scan */ 66 | unsigned long apScanTime; 67 | /** Connection status */ 68 | volatile bool isConnected = false; 69 | /** Connection change status */ 70 | bool connStatusChanged = false; 71 | /** BLE connection status */ 72 | volatile bool deviceConnected = false; 73 | /** int representation of connected to primary ssid (1), secondary (2), or disconnected (0) */ 74 | uint16_t sendVal = 0x0000; 75 | /** WiFi authentication mode types for enum parsing, based on esp_wifi_types.h */ 76 | const String authModes[] = {"open", "WEP", "WPA_PSK", "WPA2_PSK", "WPA_WPA2_PSK", "WPA2_ENTERPRISE", "MAX"}; 77 | 78 | /** 79 | * Create unique device name from MAC address 80 | **/ 81 | void createName() { 82 | uint8_t baseMac[6]; 83 | // Get MAC address for WiFi station 84 | esp_read_mac(baseMac, ESP_MAC_WIFI_STA); 85 | // Write unique name into apName 86 | sprintf(apName, "ESP32-%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]); 87 | } 88 | 89 | // List of Service and Characteristic UUIDs 90 | // service & wifi uuid are what's used in the original sketch, to maintain compatibility 91 | // list & status uuid were randomly generated using https://www.uuidgenerator.net/ 92 | #define SERVICE_UUID "0000aaaa-ead2-11e7-80c1-9a214cf093ae" 93 | #define WIFI_UUID "00005555-ead2-11e7-80c1-9a214cf093ae" 94 | #define WIFI_LIST_UUID "1d338124-7ddc-449e-afc7-67f8673a1160" 95 | #define WIFI_STATUS_UUID "5b3595c4-ad4f-4e1e-954e-3b290cc02eb0" 96 | 97 | /** SSIDs of local WiFi networks */ 98 | String ssidPrim; 99 | String ssidSec; 100 | /** Password for local WiFi network */ 101 | String pwPrim; 102 | String pwSec; 103 | 104 | /** Characteristic for digital output */ 105 | BLECharacteristic *pCharacteristicWiFi; 106 | /** Characteristic for found WiFi list */ 107 | BLECharacteristic *pCharacteristicList; 108 | /** Characteristic for connection status */ 109 | BLECharacteristic *pCharacteristicStatus; 110 | /** BLE Advertiser */ 111 | BLEAdvertising* pAdvertising; 112 | /** BLE Service */ 113 | BLEService *pService; 114 | /** BLE Server */ 115 | BLEServer *pServer; 116 | 117 | /** Buffer for JSON string */ 118 | // MAx size is 51 bytes for frame: 119 | // {"ssidPrim":"","pwPrim":"","ssidSec":"","pwSec":""} 120 | // + 4 x 32 bytes for 2 SSID's and 2 passwords 121 | StaticJsonBuffer<200> jsonBuffer; 122 | // {"SSID":["","","","","","","","","",""]} + 10 x 32 bytes for 10 SSIDs, and some spare 123 | StaticJsonBuffer<500> ssidBuffer; 124 | 125 | /** WiFi SSIDs scan 126 | * Separated from ScanWiFi(), so could be used independetley 127 | * @return int - number of found access points 128 | */ 129 | int actualWiFiScan() { 130 | Serial.println("Start scanning for networks"); 131 | 132 | WiFi.disconnect(true); 133 | WiFi.enableSTA(true); 134 | WiFi.mode(WIFI_STA); 135 | 136 | // Scan for AP 137 | apScanTime = millis(); 138 | int _apNum = WiFi.scanNetworks(false,true,false,1000); 139 | if (_apNum == 0) { 140 | Serial.println("Found no networks?????"); 141 | return false; 142 | } 143 | 144 | return _apNum; 145 | } 146 | 147 | /** 148 | scanWiFi 149 | Scans for available networks 150 | and decides if a switch between 151 | allowed networks makes sense 152 | 153 | @return bool 154 | True if at least one allowed network was found 155 | */ 156 | bool scanWiFi() { 157 | /** RSSI for primary network */ 158 | int8_t rssiPrim; 159 | /** RSSI for secondary network */ 160 | int8_t rssiSec; 161 | /** Result of this function */ 162 | bool result = false; 163 | 164 | apNum = actualWiFiScan(); 165 | 166 | byte foundAP = 0; 167 | bool foundPrim = false; 168 | 169 | for (int index=0; index rssiSec) { 200 | usePrimAP = true; // RSSI of primary network is better 201 | } else { 202 | usePrimAP = false; // RSSI of secondary network is better 203 | } 204 | result = true; 205 | break; 206 | } 207 | return result; 208 | } 209 | 210 | /** 211 | * MyServerCallbacks 212 | * Callbacks for client connection and disconnection 213 | */ 214 | class MyServerCallbacks: public BLEServerCallbacks { 215 | // TODO this doesn't take into account several clients being connected 216 | void onConnect(BLEServer* pServer) { 217 | Serial.println("BLE client connected"); 218 | deviceConnected = true; 219 | }; 220 | 221 | void onDisconnect(BLEServer* pServer) { 222 | Serial.println("BLE client disconnected"); 223 | deviceConnected = false; 224 | pAdvertising->start(); 225 | } 226 | }; 227 | 228 | /** 229 | * MyCallbackHandler 230 | * Callbacks for BLE client read/write requests 231 | */ 232 | class MyCallbackHandler: public BLECharacteristicCallbacks { 233 | void onWrite(BLECharacteristic *pCharacteristic) { 234 | std::string value = pCharacteristic->getValue(); 235 | if (value.length() == 0) { 236 | return; 237 | } 238 | Serial.println("Received over BLE: " + String((char *)&value[0])); 239 | 240 | // Decode data 241 | int keyIndex = 0; 242 | for (int index = 0; index < value.length(); index ++) { 243 | value[index] = (char) value[index] ^ (char) apName[keyIndex]; 244 | keyIndex++; 245 | if (keyIndex >= strlen(apName)) keyIndex = 0; 246 | } 247 | 248 | /** Json object for incoming data */ 249 | JsonObject& jsonIn = jsonBuffer.parseObject((char *)&value[0]); 250 | if (jsonIn.success()) { 251 | if (jsonIn.containsKey("ssidPrim") && 252 | jsonIn.containsKey("pwPrim") && 253 | jsonIn.containsKey("ssidSec") && 254 | jsonIn.containsKey("pwSec")) { 255 | ssidPrim = jsonIn["ssidPrim"].as(); 256 | pwPrim = jsonIn["pwPrim"].as(); 257 | ssidSec = jsonIn["ssidSec"].as(); 258 | pwSec = jsonIn["pwSec"].as(); 259 | 260 | Preferences preferences; 261 | preferences.begin("WiFiCred", false); 262 | preferences.putString("ssidPrim", ssidPrim); 263 | preferences.putString("ssidSec", ssidSec); 264 | preferences.putString("pwPrim", pwPrim); 265 | preferences.putString("pwSec", pwSec); 266 | preferences.putBool("valid", true); 267 | preferences.end(); 268 | 269 | Serial.println("Received over bluetooth:"); 270 | Serial.println("primary SSID: "+ssidPrim+" password: "+pwPrim); 271 | Serial.println("secondary SSID: "+ssidSec+" password: "+pwSec); 272 | connStatusChanged = true; 273 | hasCredentials = true; 274 | } else if (jsonIn.containsKey("erase")) { 275 | Serial.println("Received erase command"); 276 | Preferences preferences; 277 | preferences.begin("WiFiCred", false); 278 | preferences.clear(); 279 | preferences.end(); 280 | connStatusChanged = true; 281 | hasCredentials = false; 282 | ssidPrim = ""; 283 | pwPrim = ""; 284 | ssidSec = ""; 285 | pwSec = ""; 286 | 287 | int err; 288 | err=nvs_flash_init(); 289 | Serial.println("nvs_flash_init: " + err); 290 | err=nvs_flash_erase(); 291 | Serial.println("nvs_flash_erase: " + err); 292 | } else if (jsonIn.containsKey("reset")) { 293 | WiFi.disconnect(); 294 | esp_restart(); 295 | } 296 | } else { 297 | Serial.println("Received invalid JSON"); 298 | } 299 | jsonBuffer.clear(); 300 | }; 301 | 302 | void onRead(BLECharacteristic *pCharacteristic) { 303 | Serial.println("BLE onRead request"); 304 | String wifiCredentials; 305 | 306 | /** Json object for outgoing data */ 307 | JsonObject& jsonOut = jsonBuffer.createObject(); 308 | jsonOut["ssidPrim"] = ssidPrim; 309 | jsonOut["pwPrim"] = pwPrim; 310 | jsonOut["ssidSec"] = ssidSec; 311 | jsonOut["pwSec"] = pwSec; 312 | // Convert JSON object into a string 313 | jsonOut.printTo(wifiCredentials); 314 | 315 | // encode the data 316 | int keyIndex = 0; 317 | Serial.println("Stored settings: " + wifiCredentials); 318 | for (int index = 0; index < wifiCredentials.length(); index ++) { 319 | wifiCredentials[index] = (char) wifiCredentials[index] ^ (char) apName[keyIndex]; 320 | keyIndex++; 321 | if (keyIndex >= strlen(apName)) keyIndex = 0; 322 | } 323 | pCharacteristicWiFi->setValue((uint8_t*)&wifiCredentials[0],wifiCredentials.length()); 324 | jsonBuffer.clear(); 325 | } 326 | }; 327 | 328 | /** ListCallbackHandler 329 | * callback for SSID list read request 330 | */ 331 | class ListCallbackHandler: public BLECharacteristicCallbacks { 332 | void onRead(BLECharacteristic *pCharacteristic) { 333 | Serial.println("BLE onRead request"); 334 | String wifiSSIDsFound; 335 | 336 | byte counter = 0; 337 | 338 | while (!apNum && counter < 20) { 339 | if (millis() - apScanTime > 10000) apNum = actualWiFiScan(); 340 | counter++; 341 | delay(500); 342 | } 343 | 344 | /** Json object for outgoing data */ 345 | JsonObject& jsonOut = ssidBuffer.createObject(); 346 | JsonArray& SSID = jsonOut.createNestedArray("SSID"); 347 | for (int i = 0; i < apNum && i < 10; i++) { 348 | String ssid = WiFi.SSID(i); 349 | 350 | if (WiFi.encryptionType(i) != 0) { 351 | SSID.add(ssid); 352 | } 353 | } 354 | // Convert JSON object into a string 355 | jsonOut.printTo(wifiSSIDsFound); 356 | 357 | // encode the data (doesn't seem necessary, if added should be added to web app as well) 358 | Serial.println("Found SSIDs: " + wifiSSIDsFound); 359 | // int keyIndex = 0; 360 | // for (int index = 0; index < wifiSSIDsFound.length(); index ++) { 361 | // wifiSSIDsFound[index] = (char) wifiSSIDsFound[index] ^ (char) apName[keyIndex]; 362 | // keyIndex++; 363 | // if (keyIndex >= strlen(apName)) keyIndex = 0; 364 | // } 365 | pCharacteristicList->setValue((uint8_t*)&wifiSSIDsFound[0],wifiSSIDsFound.length()); 366 | ssidBuffer.clear(); 367 | } 368 | }; 369 | 370 | /** BLE notification task 371 | * works independently from loop(), in a separate freeRTOS task. 372 | * if the esp32 device (server) is connected to a client, update the client every 1 second 373 | * of the wifi connection status. 374 | * in order to not cause interference between the two tasks, a mutex semaphore is used by the 375 | * wifi connection callbacks which update the variable, loop(), and the notification task. 376 | */ 377 | void sendBLEdata(void * parameter) { 378 | TickType_t xLastWakeTime; 379 | TickType_t xPeriod = pdMS_TO_TICKS(1000); 380 | 381 | xLastWakeTime = xTaskGetTickCount(); 382 | 383 | bool notificationFlag = false; 384 | 385 | while(1) { 386 | // if the device is connected via BLE try to send notifications 387 | if (deviceConnected) { 388 | // Take mutex, set value, give mutex 389 | xSemaphoreTake(connStatSemaphore,0); 390 | pCharacteristicStatus->setValue(sendVal); 391 | xSemaphoreGive(connStatSemaphore); 392 | 393 | // test if notifications are enabled by client 394 | byte testNotify = *pCharacteristicStatus->getDescriptorByUUID((uint16_t)0x2902)->getValue(); 395 | 396 | // if enabled, send value over BLE 397 | if (testNotify == 1) { 398 | pCharacteristicStatus->notify(); // Send the value to the app! 399 | if (!notificationFlag) { 400 | Serial.println("started notification service"); 401 | notificationFlag = true; 402 | } 403 | } else if (notificationFlag){ 404 | // else print failure message to serial monitor 405 | Serial.print("notify failed, value of 0x2902 descriptor:\t"); 406 | Serial.println(testNotify, HEX);//*pCharacteristicMeas->getDescriptorByUUID((uint16_t)0x2902)->getValue(), HEX); 407 | notificationFlag = false; 408 | } 409 | } 410 | // sleep task for 1000 ms 411 | vTaskDelayUntil(&xLastWakeTime, xPeriod); 412 | } 413 | } 414 | 415 | /** 416 | * initBLE 417 | * Initialize BLE service and characteristic 418 | * Start BLE server and service advertising 419 | */ 420 | void initBLE() { 421 | // Initialize BLE and set output power 422 | BLEDevice::init(apName); 423 | BLEDevice::setPower(ESP_PWR_LVL_P7); 424 | 425 | // Create BLE Server 426 | pServer = BLEDevice::createServer(); 427 | 428 | // Set server callbacks 429 | pServer->setCallbacks(new MyServerCallbacks()); 430 | 431 | // Create BLE Service 432 | pService = pServer->createService(BLEUUID(SERVICE_UUID),20); 433 | 434 | // Create BLE Characteristic for WiFi settings 435 | pCharacteristicWiFi = pService->createCharacteristic( 436 | BLEUUID(WIFI_UUID), 437 | // WIFI_UUID, 438 | BLECharacteristic::PROPERTY_READ | 439 | BLECharacteristic::PROPERTY_WRITE 440 | ); 441 | pCharacteristicWiFi->setCallbacks(new MyCallbackHandler()); 442 | 443 | // Create BLE characteristic for found SSIDs 444 | pCharacteristicList = pService->createCharacteristic( 445 | BLEUUID(WIFI_LIST_UUID), 446 | BLECharacteristic::PROPERTY_READ 447 | ); 448 | pCharacteristicList->setCallbacks(new ListCallbackHandler()); 449 | 450 | // Create BLE Characteristic for status notifications 451 | pCharacteristicStatus = pService->createCharacteristic( 452 | BLEUUID(WIFI_STATUS_UUID), 453 | BLECharacteristic::PROPERTY_NOTIFY 454 | ); 455 | // pCharacteristicStatus->setCallbacks(new MyCallbacks()); // If only notifications no need for callback? 456 | pCharacteristicStatus->addDescriptor(new BLE2902()); 457 | 458 | // Start the service 459 | pService->start(); 460 | 461 | // Start advertising 462 | pAdvertising = pServer->getAdvertising(); 463 | pAdvertising->addServiceUUID(SERVICE_UUID); 464 | pAdvertising->setScanResponse(true); 465 | pAdvertising->start(); 466 | } 467 | 468 | /** Callback for receiving IP address from AP */ 469 | void gotIP(system_event_id_t event) { 470 | isConnected = true; 471 | connStatusChanged = true; 472 | /** Check if ip corresponds to 1st or 2nd configured SSID 473 | * takes semaphore, sets (uint16_t)sendVal, and gives semaphore 474 | */ 475 | String connectedSSID = WiFi.SSID(); 476 | xSemaphoreTake(connStatSemaphore,portMAX_DELAY); 477 | if (connectedSSID == ssidPrim) { 478 | // Serial.println("connected to primary SSID"); 479 | sendVal = 0x0001; 480 | } else if (connectedSSID == ssidSec) { 481 | sendVal = 0x0002; 482 | } 483 | xSemaphoreGive(connStatSemaphore); 484 | } 485 | 486 | /** Callback for connection loss */ 487 | void lostCon(system_event_id_t event) { 488 | isConnected = false; 489 | connStatusChanged = true; 490 | /** if disconnected, take semaphore, set (uint16_t)sendVal = 0, give semaphore */ 491 | xSemaphoreTake(connStatSemaphore,portMAX_DELAY); 492 | sendVal = 0x0000; 493 | xSemaphoreGive(connStatSemaphore); 494 | } 495 | 496 | /** 497 | * Start connection to AP 498 | */ 499 | void connectWiFi() { 500 | // Setup callback function for successful connection 501 | WiFi.onEvent(gotIP, SYSTEM_EVENT_STA_GOT_IP); 502 | // Setup callback function for lost connection 503 | WiFi.onEvent(lostCon, SYSTEM_EVENT_STA_DISCONNECTED); 504 | 505 | WiFi.disconnect(true); 506 | WiFi.enableSTA(true); 507 | WiFi.mode(WIFI_STA); 508 | 509 | Serial.println(); 510 | Serial.print("Start connection to "); 511 | if (usePrimAP) { 512 | Serial.println(ssidPrim); 513 | WiFi.begin(ssidPrim.c_str(), pwPrim.c_str()); 514 | } else { 515 | Serial.println(ssidSec); 516 | WiFi.begin(ssidSec.c_str(), pwSec.c_str()); 517 | } 518 | } 519 | 520 | void setup() { 521 | // Create unique device name 522 | createName(); 523 | 524 | // Initialize Serial port 525 | Serial.begin(115200); 526 | // Send some device info 527 | Serial.print("Build: "); 528 | Serial.println(compileDate); 529 | 530 | // Set up mutex semaphore 531 | connStatSemaphore = xSemaphoreCreateMutex(); 532 | 533 | if(connStatSemaphore == NULL){ 534 | Serial.println("Error creating connStatSemaphore"); 535 | } 536 | 537 | // ble task 538 | xTaskCreate( 539 | sendBLEdata, 540 | "sendBLEdataTask", 541 | 2048, 542 | NULL, 543 | 1, 544 | &sendBLEdataTask 545 | ); 546 | delay(500); 547 | 548 | Preferences preferences; 549 | preferences.begin("WiFiCred", false); 550 | bool hasPref = preferences.getBool("valid", false); 551 | if (hasPref) { 552 | ssidPrim = preferences.getString("ssidPrim",""); 553 | ssidSec = preferences.getString("ssidSec",""); 554 | pwPrim = preferences.getString("pwPrim",""); 555 | pwSec = preferences.getString("pwSec",""); 556 | 557 | Serial.printf("%s,%s,%s,%s\n",ssidPrim,pwPrim,ssidSec,pwSec); 558 | 559 | if (ssidPrim.equals("") 560 | || pwPrim.equals("") 561 | || ssidSec.equals("") 562 | || pwPrim.equals("")) { 563 | Serial.println("Found preferences but credentials are invalid"); 564 | } else { 565 | Serial.println("Read from preferences:"); 566 | Serial.println("primary SSID: "+ssidPrim+" password: "+pwPrim); 567 | Serial.println("secondary SSID: "+ssidSec+" password: "+pwSec); 568 | hasCredentials = true; 569 | } 570 | } else { 571 | Serial.println("Could not find preferences, need send data over BLE"); 572 | } 573 | preferences.end(); 574 | 575 | // Start BLE server 576 | initBLE(); 577 | 578 | if (hasCredentials) { 579 | apScanTime = millis(); 580 | // Check for available AP's 581 | if (!scanWiFi()) { 582 | Serial.println("Could not find any AP"); 583 | } else { 584 | // If AP was found, start connection 585 | connectWiFi(); 586 | } 587 | } 588 | } 589 | 590 | void loop() { 591 | if (connStatusChanged) { 592 | if (isConnected) { 593 | Serial.print("Connected to AP: "); 594 | String connectedSSID = WiFi.SSID(); 595 | xSemaphoreTake(connStatSemaphore,portMAX_DELAY); 596 | if (sendVal == 1) Serial.println("connected to primary SSID"); 597 | else if (sendVal == 2) Serial.println("Connected to secondary SSID"); 598 | xSemaphoreGive(connStatSemaphore); 599 | Serial.print(connectedSSID); 600 | Serial.print(" with IP: "); 601 | Serial.print(WiFi.localIP()); 602 | Serial.print(" RSSI: "); 603 | Serial.println(WiFi.RSSI()); 604 | } else { 605 | if (hasCredentials) { 606 | Serial.println("Lost WiFi connection"); 607 | // Received WiFi credentials 608 | if (!scanWiFi()) { // Check for available AP's 609 | Serial.println("Could not find any AP"); 610 | } else { // If AP was found, start connection 611 | connectWiFi(); 612 | } 613 | } 614 | } 615 | connStatusChanged = false; 616 | } 617 | } 618 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | --------------------------------------------------------------------------------