|
安装NimBLE
以前尝试过使用ESP-IDF4.0中包含的NimBLE,但是不能很好地工作,因此请下载此Github库。GitHub - h2zero/NimBLE-Arduino: A fork of the NimBLE library structured for compilation with Ardruino, designed for use with ESP32.按页面上的“Code”按钮,然后选择“Download ZIP”以开始下载
sketch>ZIP>包含库>安装ZIP格式库并指定下载的ZIP即可使用,找到NimBLE_Client示例进行修改,与HID连接并使用“Report Charcteristic”定义UUID
- // UUID HID
- static NimBLEUUID serviceUUID("1812");
- // UUID Report Charcteristic
- static NimBLEUUID charUUID("2a4d");
复制代码 修改NimBLEAdvertisedDeviceCallbacks以使用HID UUID
- class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
- void onResult(NimBLEAdvertisedDevice *advertisedDevice) {
- Serial.print("Advertised Device found: ");
- // toString()有问题吗?
- // Serial.println(advertisedDevice->toString().c_str());
- Serial.printf("name:%s,address:%s", advertisedDevice->getName().c_str(), advertisedDevice->getAddress().toString().c_str());
- Serial.printf("UUID:%s\n", advertisedDevice->haveServiceUUID() ? advertisedDevice->getServiceUUID().toString().c_str() : "none");
- // HID UUID检查是否正确并停止扫描
- if (advertisedDevice->isAdvertisingService(serviceUUID)) {
- Serial.println("Found Our Service");
- /** stop scan before connecting */
- NimBLEDevice::getScan()->stop();
- /** Save the device reference in a global for the client to use*/
- advDevice = advertisedDevice;
- /** Ready to connect now */
- doConnect = true;
- }
- }
- ;
- };
复制代码 在connectToServer()中,使用HID UUID重写UUID“ DEAD”,“ BEEF”,“ BAAD”,“ F00D”等。此外,在示例中,假设一个UUID中存在多个特征。在下图中,HID UUID(1812)下有多个报告特征(2a4d)(似乎只有一个)除非您不小心绘制了正确的特征,否则它将永远不会移动。顺便说一句,正确的答案是该图像中带有2个描述符。
因此,对其进行修改以获取多个部分以获取特征在库中,我发现了std :: vector * NimBLERemoteService :: getCharacteristics(bool refresh),所以修改196行
- NimBLERemoteService *pSvc = nullptr;
- // 使用向量处理多个
- // NimBLERemoteCharacteristic *pChr = nullptr;
- std::vector<NimBLERemoteCharacteristic*> *pChrs = nullptr;
- NimBLERemoteDescriptor *pDsc = nullptr;
- // 获取HID服务
- pSvc = pClient->getService(serviceUUID);
- if (pSvc) { /** make sure it's not null */
- // 获取多个Characteristics(参数为真)
- pChrs = pSvc->getCharacteristics(true);
- }
- if (pChrs) { /** make sure it's not null */
- //将具有“Notify”属性的属性从多个“Report Characterisitics”注册到“Callback”。
- for (int i = 0; i < pChrs->size(); i++) {
- if (pChrs->at(i)->canNotify()) {
- if (!pChrs->at(i)->registerForNotify(notifyCB)) {
- /** Disconnect if subscribe failed */
- pClient->disconnect();
- return false;
- }
- }
- }
- }
复制代码 通过此修改,您可以连接到HID设备。以BLE手机远程快门为例,每次按下按钮,都会执行notifyCB()并获取数据
- void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
- std::string str = (isNotify == true) ? "Notification" : "Indication";
- str += " from ";
- str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
- str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
- str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
- Serial.print(str.c_str());
- Serial.print("\ndata: ");
- for (int i = 0; i < length; i++) {
- //将uint8_t显示为头0的字符串
复制代码
顺便说一句,如果它是BLE HID设备,则可以连接任何设备,我认为最好在实际操作时检查name或address
M5StickC中的操作
现在您可以从HID设备获取输入数据,尝试操作M5StickC,由于红色LED已连接到GPIO10,让我们使其闪烁,按下远程快门上的iOS按钮(大)
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 01 00
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 00 00
复制代码 当您按下android按钮(小)时
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 00 28
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 01 00
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 00 00
- Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
- data: 00 00
复制代码 数据将以这种方式发送,因此当00 28,01 00到来时,打开LED,收到01 00时我将其关闭
- // //点亮后标记跳过01 00
- bool on = false;
- /** Notification / Indication receiving handler callback */
- void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
- Serial.print("data: ");
- for (int i = 0; i < length; i++) {
- // 将uint8_t显示为头0的字符串
- Serial.printf("%02X ", pData[i]);
- }
- Serial.print("\n");
- if (pData[1] & 0x28) {
- //如果00 28,请打开LED并设置一个标志
- digitalWrite(GPIO_NUM_10, LOW);
- on = true;
- }
- if (pData[0] & 0x01) {
- // 如果设置了标志(立即在00 28之后),请跳过它。
- if (on) {
- on = false;
- return;
- }
- //关闭LED
- digitalWrite(GPIO_NUM_10, HIGH);
- }
- }
复制代码 顺便说一下,草图的大小为42%,比Arduino-BLE小得多。也许Arduino-BLE的使用率将达到70%到80%...
完整代码
- /** NimBLE_Server Demo:
- *
- * Demonstrates many of the available features of the NimBLE client library.
- *
- * Created: on March 24 2020
- * Author: H2zero
- *
- */
- #include <NimBLEDevice.h>
- #include <M5StickC.h>
- #include <vector>
- using namespace std;
- void scanEndedCB(NimBLEScanResults results);
- // UUID HID
- static NimBLEUUID serviceUUID("1812");
- // UUID Report Charcteristic
- static NimBLEUUID charUUID("2a4d");
- static NimBLEAdvertisedDevice *advDevice;
- static bool doConnect = false;
- static uint32_t scanTime = 0; /** 0 = scan forever */
- /** None of these are required as they will be handled by the library with defaults. **
- ** Remove as you see fit for your needs */
- class ClientCallbacks: public NimBLEClientCallbacks {
- void onConnect(NimBLEClient *pClient) {
- Serial.println("Connected");
- /** After connection we should change the parameters if we don't need fast response times.
- * These settings are 150ms interval, 0 latency, 450ms timout.
- * Timeout should be a multiple of the interval, minimum is 100ms.
- * I find a multiple of 3-5 * the interval works best for quick response/reconnect.
- * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
- */
- pClient->updateConnParams(120, 120, 0, 60);
- }
- ;
- void onDisconnect(NimBLEClient *pClient) {
- Serial.print(pClient->getPeerAddress().toString().c_str());
- Serial.println(" Disconnected - Starting scan");
- NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
- }
- ;
- /** Called when the peripheral requests a change to the connection parameters.
- * Return true to accept and apply them or false to reject and keep
- * the currently used parameters. Default will return true.
- */
- bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) {
- if (params->itvl_min < 24) { /** 1.25ms units */
- return false;
- }
- else if (params->itvl_max > 40) { /** 1.25ms units */
- return false;
- }
- else if (params->latency > 2) { /** Number of intervals allowed to skip */
- return false;
- }
- else if (params->supervision_timeout > 100) { /** 10ms units */
- return false;
- }
- return true;
- }
- ;
- /********************* Security handled here **********************
- ****** Note: these are the same return values as defaults ********/
- uint32_t onPassKeyRequest() {
- Serial.println("Client Passkey Request");
- /** return the passkey to send to the server */
- return 123456;
- }
- ;
- bool onConfirmPIN(uint32_t pass_key) {
- Serial.print("The passkey YES/NO number: ");
- Serial.println(pass_key);
- /** Return false if passkeys don't match. */
- return true;
- }
- ;
- /** Pairing proces\s complete, we can check the results in ble_gap_conn_desc */
- void onAuthenticationComplete(ble_gap_conn_desc *desc) {
- if (!desc->sec_state.encrypted) {
- Serial.println("Encrypt connection failed - disconnecting");
- /** Find the client with the connection handle provided in desc */
- NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
- return;
- }
- }
- ;
- };
- /** Define a class to handle the callbacks when advertisments are received */
- class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {
- void onResult(NimBLEAdvertisedDevice *advertisedDevice) {
- Serial.print("Advertised Device found: ");
- // Serial.println(advertisedDevice->toString().c_str());
- Serial.printf("name:%s,address:%s", advertisedDevice->getName().c_str(), advertisedDevice->getAddress().toString().c_str());
- Serial.printf("UUID:%s\n", advertisedDevice->haveServiceUUID() ? advertisedDevice->getServiceUUID().toString().c_str() : "none");
- if (advertisedDevice->isAdvertisingService(serviceUUID)) {
- Serial.println("Found Our Service");
- /** stop scan before connecting */
- NimBLEDevice::getScan()->stop();
- /** Save the device reference in a global for the client to use*/
- advDevice = advertisedDevice;
- /** Ready to connect now */
- doConnect = true;
- }
- }
- ;
- };
- const uint8_t START = 0x08;
- const uint8_t SELECT = 0x04;
- const int INDEX_BUTTON = 6;
- bool on = false;
- /** Notification / Indication receiving handler callback */
- void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
- std::string str = (isNotify == true) ? "Notification" : "Indication";
- str += " from ";
- str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
- str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
- str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
- // str += ", Value = " + std::string((char*)pData, length);
- Serial.print(str.c_str());
- Serial.print("\ndata: ");
- for (int i = 0; i < length; i++) {
- // uint8_tを頭0のstringで表示する
- Serial.printf("%02X ", pData[i]);
- }
- Serial.print("\n");
- if (pData[1] & 0x28) {
- digitalWrite(GPIO_NUM_10, LOW);
- on = true;
- }
- if (pData[0] & 0x01) {
- if (on) {
- on = false;
- return;
- }
- digitalWrite(GPIO_NUM_10, HIGH);
- }
- }
- /** Callback to process the results of the last scan or restart it */
- void scanEndedCB(NimBLEScanResults results) {
- Serial.println("Scan Ended");
- }
- /** Create a single global instance of the callback class to be used by all clients */
- static ClientCallbacks clientCB;
- /** Handles the provisioning of clients and connects / interfaces with the server */
- bool connectToServer() {
- NimBLEClient *pClient = nullptr;
- /** Check if we have a client we should reuse first **/
- if (NimBLEDevice::getClientListSize()) {
- /** Special case when we already know this device, we send false as the
- * second argument in connect() to prevent refreshing the service database.
- * This saves considerable time and power.
- */
- pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
- if (pClient) {
- if (!pClient->connect(advDevice, false)) {
- Serial.println("Reconnect failed");
- return false;
- }
- Serial.println("Reconnected client");
- }
- /** We don't already have a client that knows this device,
- * we will check for a client that is disconnected that we can use.
- */
- else {
- pClient = NimBLEDevice::getDisconnectedClient();
- }
- }
- /** No client to reuse? Create a new one. */
- if (!pClient) {
- if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
- Serial.println("Max clients reached - no more connections available");
- return false;
- }
- pClient = NimBLEDevice::createClient();
- Serial.println("New client created");
- pClient->setClientCallbacks(&clientCB, false);
- /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
- * These settings are safe for 3 clients to connect reliably, can go faster if you have less
- * connections. Timeout should be a multiple of the interval, minimum is 100ms.
- * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
- */
- pClient->setConnectionParams(12, 12, 0, 51);
- /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
- pClient->setConnectTimeout(5);
- if (!pClient->connect(advDevice)) {
- /** Created a client but failed to connect, don't need to keep it as it has no data */
- NimBLEDevice::deleteClient(pClient);
- Serial.println("Failed to connect, deleted client");
- return false;
- }
- }
- if (!pClient->isConnected()) {
- if (!pClient->connect(advDevice)) {
- Serial.println("Failed to connect");
- return false;
- }
- }
- Serial.print("Connected to: ");
- Serial.println(pClient->getPeerAddress().toString().c_str());
- Serial.print("RSSI: ");
- Serial.println(pClient->getRssi());
- /** Now we can read/write/subscribe the charateristics of the services we are interested in */
- NimBLERemoteService *pSvc = nullptr;
- // NimBLERemoteCharacteristic *pChr = nullptr;
- std::vector<NimBLERemoteCharacteristic*> *pChrs = nullptr;
- NimBLERemoteDescriptor *pDsc = nullptr;
- pSvc = pClient->getService(serviceUUID);
- if (pSvc) { /** make sure it's not null */
- pChrs = pSvc->getCharacteristics(true);
- }
- if (pChrs) { /** make sure it's not null */
- for (int i = 0; i < pChrs->size(); i++) {
- if (pChrs->at(i)->canNotify()) {
- /** Must send a callback to subscribe, if nullptr it will unsubscribe */
- if (!pChrs->at(i)->registerForNotify(notifyCB)) {
- /** Disconnect if subscribe failed */
- pClient->disconnect();
- return false;
- }
- }
- }
- }
- else {
- Serial.println("DEAD service not found.");
- }
- Serial.println("Done with this device!");
- return true;
- }
- void setup() {
- Serial.begin(115200);
- Serial.println("Starting NimBLE Client");
- M5.begin();
- pinMode(GPIO_NUM_10, OUTPUT);
- digitalWrite(GPIO_NUM_10, HIGH);
- /** Initialize NimBLE, no device name spcified as we are not advertising */
- NimBLEDevice::init("");
- /** Set the IO capabilities of the device, each option will trigger a different pairing method.
- * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing
- * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing
- * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
- */
- //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
- //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
- /** 2 different ways to set security - both calls achieve the same result.
- * no bonding, no man in the middle protection, secure connections.
- *
- * These are the default values, only shown here for demonstration.
- */
- //NimBLEDevice::setSecurityAuth(false, false, true);
- NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/BLE_SM_PAIR_AUTHREQ_SC);
- /** Optional: set the transmit power, default is 3db */
- NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
- /** Optional: set any devices you don't want to get advertisments from */
- // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
- /** create new scan */
- NimBLEScan *pScan = NimBLEDevice::getScan();
- /** create a callback that gets called when advertisers are found */
- pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
- /** Set scan interval (how often) and window (how long) in milliseconds */
- pScan->setInterval(10000);
- pScan->setWindow(9999);
- /** Active scan will gather scan response data from advertisers
- * but will use more energy from both devices
- */
- pScan->setActiveScan(true);
- /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
- * Optional callback for when scanning stops.
- */
- pScan->start(scanTime, scanEndedCB);
- }
- void loop() {
- M5.update();
- if (M5.BtnA.wasReleased()) {
- M5.Axp.PowerOff();
- }
- while (!doConnect) {
- M5.update();
- if (M5.BtnA.wasReleased()) {
- M5.Axp.PowerOff();
- }
- delay(1);
- }
- doConnect = false;
- /** Found a device we want to connect to, do it now */
- if (connectToServer()) {
- Serial.println("Success! we should now be getting notifications, scanning for more!");
- }
- else {
- Serial.println("Failed to connect, starting scan");
- }
- // NimBLEDevice::getScan()->start(scanTime,scanEndedCB);
- }
复制代码
|
|