// central_bleuart_scaner.ino Rev 1.1 // 2nd Dec 2017 /* Based on Adafruit example central_scan.ino modifications (c)2014-2017 Forward Computing and Control Pty. Ltd. NSW Australia, www.forward.com.au This code is not warranted to be fit for any purpose. You may only use it at your own risk. This code may be freely used for both private and commercial use Provide this copyright is maintained. */ /********************************************************************* This is an example for our nRF52 based Bluefruit LE modules Pick one up today in the adafruit shop! Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! MIT license, check LICENSE for more information All text above, and the splash screen below must be included in any redistribution *********************************************************************/ /* This sketch scans for advertising BLE peripherals and checks if they have a Nordic UART service. Once a device has been checked it is added to a block list to prevent double scaning Restart this sketch to do a rescan Results are reported on the Serail output at 9600 baud /* LED PATTERNS ------------ LED_RED - Blinks pattern changes based on the number of Nordic UART connections found LED_BLUE - Blinks constantly when scanning */ #include // Uncomment next line to get output from scan report //#define DEBUG_REPORT // Uncomment next line to get scan output //#define DEBUG_SCAN // BLE_CENTRAL_MAX_CONN == 4 so max 4 BLE devices connected to this one. const int MAX_IGNORE_DEVICES = 20; // ignore upto 20 BLE devices that do not have Nordic UART Service to suppress duplicate output msgs // Struct containing peripheral info const size_t NAME_SIZE = 33; typedef struct { uint8_t addr[BLE_GAP_ADDR_LEN]; //addr[6] uint16_t conn_handle; // Each prph need its own bleuart client service BLEClientUart bleuart; char name[NAME_SIZE]; } prph_info_t; const size_t BLE_NAME_LEN = 33; // including null // Struct containing list of devices to ignore in scan typedef struct { uint8_t bleName[BLE_NAME_LEN]; bool foundName; // resolved a non blank name bool connected; // set to true if we have connected to this device uint8_t addr_type; uint8_t addr[BLE_GAP_ADDR_LEN]; //addr[6] } scan_add_ignore_t; scan_add_ignore_t ignoreDevices[MAX_IGNORE_DEVICES]; size_t ignoredDevicesIdx = 0; // none so far /* Peripheral info array (one per peripheral device) There are 'BLE_CENTRAL_MAX_CONN' central connections, but the the connection handle can be numerically larger (for example if the peripheral role is also used, such as connecting to a mobile device). As such, we need to convert connection handles <-> the array index where appropriate to prevent out of array accesses. Note: One can simply declares the array with BLE_MAX_CONN and use connection handle as index directly with the expense of SRAM. */ prph_info_t prphs[BLE_CENTRAL_MAX_CONN]; // Software Timer for blinking the RED LED SoftwareTimer blinkTimer; uint8_t connection_num = 0; // for blink pattern bool checkEqualAddress(ble_gap_addr_t peerAddr, scan_add_ignore_t ignoreAddr) { for (int i = 0; i < BLE_GAP_ADDR_LEN; i++) { if (peerAddr.addr[i] != ignoreAddr.addr[i]) { return false; } } // also check type if (peerAddr.addr_type != ignoreAddr.addr_type) { return false; } return true; // valid } void printAddress(uint8_t *addr) { for (int i = BLE_GAP_ADDR_LEN - 1; i >= 0; i--) { if (addr[i] < 16) { Serial.print('0'); } Serial.print(addr[i], HEX); if (i > 0) { Serial.print(':'); } } } // clean up name // replace \r with space, and \n with space char* cleanUpName(uint8_t *name) { uint8_t *str = name; while (*str != '\0') { if ((*str == '\r') || (*str == '\n')) { *str = ' '; } str++; } return (char*)name; } char* cleanUpName(char* name) { return cleanUpName((uint8_t *) name); } void setup() { Serial.begin(9600); // delay startup a few seconds to give time to open the Monitor for (int i = 5; i > 0; i--) { delay(1000); Serial.print(i); Serial.print(' '); } Serial.println(); // Initialize blinkTimer for 200 ms and start it blinkTimer.begin(200, blink_timer_callback); blinkTimer.start(); Serial.println("Central Scanner for Nordic UART Services"); Serial.println(" Send any key to Serial to clear blocked list and restart Full scan"); Serial.println("-----------------------------------------\n"); // Enable both peripheral and central Bluefruit.begin(true, true); Bluefruit.setName("Central Scanner"); // Init peripheral pool for (uint8_t idx = 0; idx < BLE_CENTRAL_MAX_CONN; idx++) { // BLE_CENTRAL_MAX_CONN == 4 so max 4 BLE devices connected to this one. // Invalid all connection handle prphs[idx].conn_handle = BLE_CONN_HANDLE_INVALID; memset(prphs[idx].name, 0, NAME_SIZE); // set to nulls // All of BLE Central Uart Serivce prphs[idx].bleuart.begin(); } // Init ignored devices for (uint8_t idx = 0; idx < MAX_IGNORE_DEVICES; idx++) { // BLE_CENTRAL_MAX_CONN == 4 so max 4 BLE devices connected to this one. // All of BLE Central Uart Serivce ignoreDevices[idx].addr_type = 0; memset(ignoreDevices[idx].addr, 0, BLE_GAP_ADDR_LEN); // set to zeros ignoreDevices[idx].foundName = false; // resolved a non blank name ignoreDevices[idx].connected = false; // set to true if we have connected to this device } // Callbacks for Central Bluefruit.Central.setConnectCallback(connect_callback); Bluefruit.Central.setDisconnectCallback(disconnect_callback); /* Start Central Scanning - Enable auto scan if disconnected - Interval = 100 ms, window = 80 ms - Don't use active scan (used to retrieve the optional scan response adv packet) - Start(0) = will scan forever since no timeout is given */ Bluefruit.Scanner.setRxCallback(scan_callback); Bluefruit.Scanner.restartOnDisconnect(true); Bluefruit.Scanner.setInterval(160, 80); // in units of 0.625 ms Bluefruit.Scanner.useActiveScan(false); // Don't request scan response data Bluefruit.Scanner.start(0); // 0 = Don't stop scanning after n seconds } /** Callback invoked when scanner picks up an advertising packet @param report Structural advertising data This method used to check if advertising data contains the BleUart service UUID but now skip this check as some BLE devices do not provide this info in the advertising packet */ void scan_callback(ble_gap_evt_adv_report_t* report) { // check ignore devices bool haveConnected = true; bool foundIgnored = false; int i = 0; for (; i < ignoredDevicesIdx; i++) { if (checkEqualAddress(report->peer_addr, ignoreDevices[i])) { return; // if (ignoreDevices[i].connected) { // return; // skip this one // } else { // haveConnected = false; // foundIgnored = true; // break; // try and connect again // } } } uint8_t bleName[BLE_NAME_LEN]; bool foundName = false; memset(bleName, 0, sizeof(bleName)); /* Complete Local Name */ if (Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME, bleName, BLE_NAME_LEN - 1)) { foundName = true; } /* Shortened Local Name */ if (!foundName && (Bluefruit.Scanner.parseReportByType(report, BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME, bleName, BLE_NAME_LEN - 1))) { foundName = true; } if (!foundName) { strcpy((char*)bleName, "-Unknown Name-"); } char *deviceName = cleanUpName(bleName); // replace /r /n with spaces Serial.println(); Serial.print("Found BLE Device "); printAddress(report->peer_addr.addr); Serial.print(" Power Level (rssi): "); Serial.println(report->rssi); // if (!haveConnected) { // Serial.println(" Did not sucessfully connect last time. Trying to connect again now."); // if (foundIgnored) { // ignoreDevices[i].connected = true; // only try and connect twice first time and now // } // } // Serial.println(); #ifdef DEBUG_SCAN Serial.print(" const char *BLE_NAME = \""); Serial.print(deviceName); Serial.println("\";"); Serial.print(" const char *BLE_ADDRESS = \""); printAddress(report->peer_addr.addr); Serial.println("\";"); Serial.print(" const uint8_t BLE_ADDRESS_TYPE = "); Serial.print(report->peer_addr.addr_type); Serial.println(';'); #endif DEBUG #ifdef DEBUG_REPORT Serial.println("report"); // ble_gap_addr_t peer_addr; /**< Bluetooth address of the peer device. */ // int8_t rssi; /**< Received Signal Strength Indication in dBm. */ // uint8_t scan_rsp : 1; /**< If 1, the report corresponds to a scan response and the type field may be ignored. */ // uint8_t type : 2; /**< See @ref BLE_GAP_ADV_TYPES. Only valid if the scan_rsp field is 0. */ // uint8_t dlen : 5; /**< Advertising or scan response data length. */ // uint8_t data[BLE_GAP_ADV_MAX_SIZE]; Serial.println(" peer_addr"); Serial.print(" addr_type: "); Serial.println(report->peer_addr.addr_type); /**< See @ref BLE_GAP_ADDR_TYPES. */ Serial.print(" addr: "); printAddress(report->peer_addr.addr); Serial.println(); //for(int i=0; ipeer_addr.addr[i]);Serial.print(' ');} /**< 48-bit address, LSB format. */ Serial.print(" rssi: "); Serial.println(report->rssi); Serial.print(" scan_rsp "); Serial.println(report->scan_rsp); Serial.print(" type "); Serial.println(report->type); Serial.print(" dlen "); Serial.println(report->dlen); Serial.println(" data"); for (int i = 0; i < report->dlen; i++) { if (i >= BLE_GAP_ADV_MAX_SIZE) { break; } Serial.print(report->data[i], HEX); Serial.print(' '); } Serial.println(); #endif // add to ignore list always // always add to ignored if (ignoredDevicesIdx < MAX_IGNORE_DEVICES) { for (int i = 0; i < BLE_GAP_ADDR_LEN; i++) { ignoreDevices[ignoredDevicesIdx].addr[i] = report->peer_addr.addr[i]; } ignoreDevices[ignoredDevicesIdx].addr_type = report->peer_addr.addr_type; ignoreDevices[ignoredDevicesIdx].foundName = foundName; ignoreDevices[ignoredDevicesIdx].connected = false; memcpy( ignoreDevices[ignoredDevicesIdx].bleName, deviceName, BLE_NAME_LEN); ignoredDevicesIdx++; } Bluefruit.Central.connect(report); // always connect and check then } /** Callback invoked when an connection is established @param conn_handle */ void connect_callback(uint16_t conn_handle) { Serial.println(); // Find an available ID to use int id = findConnHandle(BLE_CONN_HANDLE_INVALID); // Eeek: Exceeded the number of connections !!! if ( id < 0 ) return; prph_info_t* peer = &prphs[id]; peer->conn_handle = conn_handle; Bluefruit.Gap.getPeerName(conn_handle, peer->name, 32); ble_gap_addr_t peerAddr = Bluefruit.Gap.getPeerAddr(conn_handle); Serial.print("Connected to "); printAddress(peerAddr.addr); Serial.print(" Looking for Nordic UART service"); Serial.print(" ... "); // update ignored list bool found = false; int i = 0; for (; i < ignoredDevicesIdx; i++) { if (checkEqualAddress(peerAddr, ignoreDevices[i])) { found = true; break; } } if (found) { ignoreDevices[i].connected = true; // don't try and connect again } if ( peer->bleuart.discover(conn_handle) ) { Serial.println("Found it"); bool haveNotify = peer->bleuart.enableTXD(); if (haveNotify) { Serial.println(" --- Valid Address and type for Wifi2BLE bridge code (central_bleuart_bridge.ino) --- "); Serial.print(" const char *BLE_NAME = \""); Serial.print(cleanUpName(peer->name)); Serial.println("\";"); Serial.print(" const char *BLE_ADDRESS = \""); printAddress(peerAddr.addr); Serial.println("\";"); Serial.print(" const uint8_t BLE_ADDRESS_TYPE = "); Serial.print(peerAddr.addr_type); Serial.println(';'); Serial.println(" --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---"); connection_num++; Bluefruit.Central.disconnect(conn_handle); return; } else { Serial.println("TX Notify enable failed!!"); Serial.println("Set Notify for TX characteristic, 6E400003-B5A3-F393-E0A9-E50E24DCCA9E, in BLE device code"); } } else { Serial.println("None found OR Not discoverable!"); Serial.println(" !!!! Could Not Identify a Nordic UART Service !!!! "); Serial.println(" !!!! If you were expecting a Nordic UART Service, move the Scanner closer to the BLE device !!!! "); } // print the details if (strlen(peer->name) == 0) { // use scan name' uint8_t bleName[BLE_NAME_LEN]; memset(bleName, 0, sizeof(bleName)); if (!found) { strcpy((char*)bleName, "-Unknown Name-"); } else { strcpy((char*)bleName, (char*)ignoreDevices[i].bleName); } Serial.print(" BLE name "); Serial.print(cleanUpName(bleName)); } else { // use discovered name Serial.print(" BLE name "); Serial.print(cleanUpName(peer->name)); } Serial.print(" BLE Address "); printAddress(peerAddr.addr); Serial.print(" AddressType : "); Serial.print(peerAddr.addr_type); Serial.println(); Bluefruit.Central.disconnect(conn_handle); } /** Callback invoked when a connection is dropped @param conn_handle @param reason */ void disconnect_callback(uint16_t conn_handle, uint8_t reason) { (void) conn_handle; (void) reason; // Mark the ID as invalid int id = findConnHandle(conn_handle); // Non-existant connection, something went wrong, DBG !!! if ( id < 0 ) return; // Serial.print(prphs[id].name); // Serial.println(" disconnected!"); memset(prphs[id].name, 0, NAME_SIZE); // set to nulls // Mark conn handle as invalid prphs[id].conn_handle = BLE_CONN_HANDLE_INVALID; } unsigned long restartTimer = 0; bool restartTimerRunning = false; void loop() { // Read from HW Serial (normally USB Serial) if (Serial.available() > 0) { Bluefruit.Scanner.stop(); // reset blocked list to rescan while (Serial.available()) { char c = Serial.read(); } if (!restartTimerRunning) { Serial.println(); Serial.println(" =================== Restart Scan ALL in 2sec =============="); Serial.println(); } restartTimerRunning = true; restartTimer = millis(); connection_num = 0; // clear connections now to reset flasher } if (restartTimerRunning && ((millis() - restartTimer) > 2000)) { // restart 2 sec after Serial input stops restartTimerRunning = false; ignoredDevicesIdx = 0; Bluefruit.Scanner.start(0); } } /** Find the connection handle in the peripheral array @param conn_handle Connection handle @return array index if found, otherwise -1 */ int findConnHandle(uint16_t conn_handle) { for (int id = 0; id < BLE_CENTRAL_MAX_CONN; id++) { if (conn_handle == prphs[id].conn_handle) { return id; } } return -1; } /** Software Timer callback is invoked via a built-in FreeRTOS thread with minimal stack size. Therefore it should be as simple as possible. If a periodically heavy task is needed, please use Scheduler.startLoop() to create a dedicated task for it. More information http://www.freertos.org/RTOS-software-timer.html */ void blink_timer_callback(TimerHandle_t xTimerID) { (void) xTimerID; // Period of sequence is 16 times (1 second). // RED LED will toggle first 2*n times (on/off) and remain off for the rest of period // Where n = number of connection static uint8_t count = 0; if (connection_num == 0) { count = 0; digitalWrite(LED_RED, LOW); return; // zero count on each reset } if ( count < 2 * connection_num) { digitalToggle(LED_RED); } count++; if (count >= 16) { count = 0; } }