StickC与BLE HID的NimBLE通讯实验-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 4299|回复: 0

StickC与BLE HID的NimBLE通讯实验

[复制链接]
发表于 2020-7-31 12:02 | 显示全部楼层 |阅读模式
安装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”以开始下载
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_298776_6f0da76f-f425.png
sketch>ZIP>包含库>安装ZIP格式库并指定下载的ZIP即可使用,找到NimBLE_Client示例进行修改,与HID连接并使用“Report Charcteristic”定义UUID
  1. // UUID HID
  2. static NimBLEUUID serviceUUID("1812");
  3. // UUID Report Charcteristic
  4. static NimBLEUUID charUUID("2a4d");
复制代码
修改NimBLEAdvertisedDeviceCallbacks以使用HID UUID
  1. class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {

  2.   void onResult(NimBLEAdvertisedDevice *advertisedDevice) {
  3.     Serial.print("Advertised Device found: ");
  4. // toString()有问题吗?
  5. //        Serial.println(advertisedDevice->toString().c_str());
  6.     Serial.printf("name:%s,address:%s", advertisedDevice->getName().c_str(), advertisedDevice->getAddress().toString().c_str());
  7.     Serial.printf("UUID:%s\n", advertisedDevice->haveServiceUUID() ? advertisedDevice->getServiceUUID().toString().c_str() : "none");

  8.     // HID UUID检查是否正确并停止扫描
  9.     if (advertisedDevice->isAdvertisingService(serviceUUID)) {
  10.       Serial.println("Found Our Service");
  11.       /** stop scan before connecting */
  12.       NimBLEDevice::getScan()->stop();
  13.       /** Save the device reference in a global for the client to use*/
  14.       advDevice = advertisedDevice;
  15.       /** Ready to connect now */
  16.       doConnect = true;
  17.     }
  18.   }
  19.   ;
  20. };
复制代码
在connectToServer()中,使用HID UUID重写UUID“ DEAD”,“ BEEF”,“ BAAD”,“ F00D”等。此外,在示例中,假设一个UUID中存在多个特征。在下图中,HID UUID(1812)下有多个报告特征(2a4d)(似乎只有一个)除非您不小心绘制了正确的特征,否则它将永远不会移动。顺便说一句,正确的答案是该图像中带有2个描述符。

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_298776_32cd38ea-9968.png
因此,对其进行修改以获取多个部分以获取特征在库中,我发现了std :: vector * NimBLERemoteService :: getCharacteristics(bool refresh),所以修改196行

  1.   NimBLERemoteService *pSvc = nullptr;
  2. // 使用向量处理多个
  3. //  NimBLERemoteCharacteristic *pChr = nullptr;
  4.   std::vector<NimBLERemoteCharacteristic*> *pChrs = nullptr;

  5.   NimBLERemoteDescriptor *pDsc = nullptr;

  6.   // 获取HID服务
  7.   pSvc = pClient->getService(serviceUUID);
  8.   if (pSvc) { /** make sure it's not null */
  9.     // 获取多个Characteristics(参数为真)
  10.     pChrs = pSvc->getCharacteristics(true);
  11.   }

  12.   if (pChrs) { /** make sure it's not null */
  13.     //将具有“Notify”属性的属性从多个“Report Characterisitics”注册到“Callback”。
  14.     for (int i = 0; i < pChrs->size(); i++) {

  15.       if (pChrs->at(i)->canNotify()) {
  16.         if (!pChrs->at(i)->registerForNotify(notifyCB)) {
  17.           /** Disconnect if subscribe failed */
  18.           pClient->disconnect();
  19.           return false;
  20.         }
  21.       }
  22.     }
  23.   }
复制代码
通过此修改,您可以连接到HID设备。以BLE手机远程快门为例,每次按下按钮,都会执行notifyCB()并获取数据

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_298776_a4e6662b-7cd3.png
  1. void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
  2.   std::string str = (isNotify == true) ? "Notification" : "Indication";
  3.   str += " from ";
  4.   str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
  5.   str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
  6.   str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
  7.   Serial.print(str.c_str());
  8.   Serial.print("\ndata: ");
  9.   for (int i = 0; i < length; i++) {
  10. //将uint8_t显示为头0的字符串
复制代码
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_298776_03714108-5a34.png
顺便说一句,如果它是BLE HID设备,则可以连接任何设备,我认为最好在实际操作时检查name或address

M5StickC中的操作
现在您可以从HID设备获取输入数据,尝试操作M5StickC,由于红色LED已连接到GPIO10,让我们使其闪烁,按下远程快门上的iOS按钮(大)

  1. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  2. data: 01 00
  3. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  4. data: 00 00
复制代码
当您按下android按钮(小)时
  1. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  2. data: 00 28
  3. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  4. data: 01 00
  5. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  6. data: 00 00
  7. Notification from ff:ff:c3:19:fa:0b: Service = 0x1812, Characteristic = 0x2a4d
  8. data: 00 00
复制代码
数据将以这种方式发送,因此当00 28,01 00到来时,打开LED,收到01 00时我将其关闭

  1. // //点亮后标记跳过01 00
  2. bool on = false;

  3. /** Notification / Indication receiving handler callback */
  4. void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
  5. Serial.print("data: ");
  6.   for (int i = 0; i < length; i++) {
  7.     // 将uint8_t显示为头0的字符串
  8.     Serial.printf("%02X ", pData[i]);
  9.   }
  10.   Serial.print("\n");
  11.   if (pData[1] & 0x28) {
  12.     //如果00 28,请打开LED并设置一个标志
  13.     digitalWrite(GPIO_NUM_10, LOW);
  14.     on = true;
  15.   }
  16.   if (pData[0] & 0x01) {
  17.     // 如果设置了标志(立即在00 28之后),请跳过它。
  18.     if (on) {
  19.     on = false;
  20.     return;
  21.   }
  22. //关闭LED
  23.     digitalWrite(GPIO_NUM_10, HIGH);
  24.   }

  25. }
复制代码
顺便说一下,草图的大小为42%,比Arduino-BLE小得多。也许Arduino-BLE的使用率将达到70%到80%...

完整代码
  1. /** NimBLE_Server Demo:
  2. *
  3. *  Demonstrates many of the available features of the NimBLE client library.
  4. *  
  5. *  Created: on March 24 2020
  6. *      Author: H2zero
  7. *
  8. */

  9. #include <NimBLEDevice.h>
  10. #include <M5StickC.h>


  11. #include <vector>
  12. using namespace std;

  13. void scanEndedCB(NimBLEScanResults results);

  14. // UUID HID
  15. static NimBLEUUID serviceUUID("1812");
  16. // UUID Report Charcteristic
  17. static NimBLEUUID charUUID("2a4d");

  18. static NimBLEAdvertisedDevice *advDevice;

  19. static bool doConnect = false;
  20. static uint32_t scanTime = 0; /** 0 = scan forever */

  21. /**  None of these are required as they will be handled by the library with defaults. **
  22. **                       Remove as you see fit for your needs                        */
  23. class ClientCallbacks: public NimBLEClientCallbacks {
  24.   void onConnect(NimBLEClient *pClient) {
  25.     Serial.println("Connected");
  26.     /** After connection we should change the parameters if we don't need fast response times.
  27.      *  These settings are 150ms interval, 0 latency, 450ms timout.
  28.      *  Timeout should be a multiple of the interval, minimum is 100ms.
  29.      *  I find a multiple of 3-5 * the interval works best for quick response/reconnect.
  30.      *  Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
  31.      */
  32.     pClient->updateConnParams(120, 120, 0, 60);
  33.   }
  34.   ;

  35.   void onDisconnect(NimBLEClient *pClient) {
  36.     Serial.print(pClient->getPeerAddress().toString().c_str());
  37.     Serial.println(" Disconnected - Starting scan");
  38.     NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
  39.   }
  40.   ;

  41.   /** Called when the peripheral requests a change to the connection parameters.
  42.    *  Return true to accept and apply them or false to reject and keep
  43.    *  the currently used parameters. Default will return true.
  44.    */
  45.   bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) {
  46.     if (params->itvl_min < 24) { /** 1.25ms units */
  47.       return false;
  48.     }
  49.     else if (params->itvl_max > 40) { /** 1.25ms units */
  50.       return false;
  51.     }
  52.     else if (params->latency > 2) { /** Number of intervals allowed to skip */
  53.       return false;
  54.     }
  55.     else if (params->supervision_timeout > 100) { /** 10ms units */
  56.       return false;
  57.     }

  58.     return true;
  59.   }
  60.   ;

  61.   /********************* Security handled here **********************
  62.    ****** Note: these are the same return values as defaults ********/
  63.   uint32_t onPassKeyRequest() {
  64.     Serial.println("Client Passkey Request");
  65.     /** return the passkey to send to the server */
  66.     return 123456;
  67.   }
  68.   ;

  69.   bool onConfirmPIN(uint32_t pass_key) {
  70.     Serial.print("The passkey YES/NO number: ");
  71.     Serial.println(pass_key);
  72.     /** Return false if passkeys don't match. */
  73.     return true;
  74.   }
  75.   ;

  76.   /** Pairing proces\s complete, we can check the results in ble_gap_conn_desc */
  77.   void onAuthenticationComplete(ble_gap_conn_desc *desc) {
  78.     if (!desc->sec_state.encrypted) {
  79.       Serial.println("Encrypt connection failed - disconnecting");
  80.       /** Find the client with the connection handle provided in desc */
  81.       NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
  82.       return;
  83.     }
  84.   }
  85.   ;
  86. };

  87. /** Define a class to handle the callbacks when advertisments are received */
  88. class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks {

  89.   void onResult(NimBLEAdvertisedDevice *advertisedDevice) {
  90.     Serial.print("Advertised Device found: ");
  91. //        Serial.println(advertisedDevice->toString().c_str());
  92.     Serial.printf("name:%s,address:%s", advertisedDevice->getName().c_str(), advertisedDevice->getAddress().toString().c_str());
  93.     Serial.printf("UUID:%s\n", advertisedDevice->haveServiceUUID() ? advertisedDevice->getServiceUUID().toString().c_str() : "none");

  94.     if (advertisedDevice->isAdvertisingService(serviceUUID)) {
  95.       Serial.println("Found Our Service");
  96.       /** stop scan before connecting */
  97.       NimBLEDevice::getScan()->stop();
  98.       /** Save the device reference in a global for the client to use*/
  99.       advDevice = advertisedDevice;
  100.       /** Ready to connect now */
  101.       doConnect = true;
  102.     }
  103.   }
  104.   ;
  105. };

  106. const uint8_t START = 0x08;
  107. const uint8_t SELECT = 0x04;

  108. const int INDEX_BUTTON = 6;

  109. bool on = false;

  110. /** Notification / Indication receiving handler callback */
  111. void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify) {
  112.   std::string str = (isNotify == true) ? "Notification" : "Indication";
  113.   str += " from ";
  114.   str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString();
  115.   str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
  116.   str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
  117. //    str += ", Value = " + std::string((char*)pData, length);
  118.   Serial.print(str.c_str());
  119.   Serial.print("\ndata: ");
  120.   for (int i = 0; i < length; i++) {
  121.     // uint8_tを頭0のstringで表示する
  122.     Serial.printf("%02X ", pData[i]);
  123.   }
  124.   Serial.print("\n");
  125.   if (pData[1] & 0x28) {
  126.     digitalWrite(GPIO_NUM_10, LOW);
  127.     on = true;
  128.   }
  129.   if (pData[0] & 0x01) {
  130.     if (on) {
  131.     on = false;
  132.     return;
  133.   }
  134.     digitalWrite(GPIO_NUM_10, HIGH);
  135.   }

  136. }

  137. /** Callback to process the results of the last scan or restart it */
  138. void scanEndedCB(NimBLEScanResults results) {
  139.   Serial.println("Scan Ended");
  140. }

  141. /** Create a single global instance of the callback class to be used by all clients */
  142. static ClientCallbacks clientCB;

  143. /** Handles the provisioning of clients and connects / interfaces with the server */
  144. bool connectToServer() {
  145.   NimBLEClient *pClient = nullptr;

  146.   /** Check if we have a client we should reuse first **/
  147.   if (NimBLEDevice::getClientListSize()) {
  148.     /** Special case when we already know this device, we send false as the
  149.      *  second argument in connect() to prevent refreshing the service database.
  150.      *  This saves considerable time and power.
  151.      */
  152.     pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
  153.     if (pClient) {
  154.       if (!pClient->connect(advDevice, false)) {
  155.         Serial.println("Reconnect failed");
  156.         return false;
  157.       }
  158.       Serial.println("Reconnected client");
  159.     }
  160.     /** We don't already have a client that knows this device,
  161.      *  we will check for a client that is disconnected that we can use.
  162.      */
  163.     else {
  164.       pClient = NimBLEDevice::getDisconnectedClient();
  165.     }
  166.   }

  167.   /** No client to reuse? Create a new one. */
  168.   if (!pClient) {
  169.     if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
  170.       Serial.println("Max clients reached - no more connections available");
  171.       return false;
  172.     }

  173.     pClient = NimBLEDevice::createClient();

  174.     Serial.println("New client created");

  175.     pClient->setClientCallbacks(&clientCB, false);
  176.     /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
  177.      *  These settings are safe for 3 clients to connect reliably, can go faster if you have less
  178.      *  connections. Timeout should be a multiple of the interval, minimum is 100ms.
  179.      *  Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
  180.      */
  181.     pClient->setConnectionParams(12, 12, 0, 51);
  182.     /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
  183.     pClient->setConnectTimeout(5);

  184.     if (!pClient->connect(advDevice)) {
  185.       /** Created a client but failed to connect, don't need to keep it as it has no data */
  186.       NimBLEDevice::deleteClient(pClient);
  187.       Serial.println("Failed to connect, deleted client");
  188.       return false;
  189.     }
  190.   }

  191.   if (!pClient->isConnected()) {
  192.     if (!pClient->connect(advDevice)) {
  193.       Serial.println("Failed to connect");
  194.       return false;
  195.     }
  196.   }

  197.   Serial.print("Connected to: ");
  198.   Serial.println(pClient->getPeerAddress().toString().c_str());
  199.   Serial.print("RSSI: ");
  200.   Serial.println(pClient->getRssi());

  201.   /** Now we can read/write/subscribe the charateristics of the services we are interested in */
  202.   NimBLERemoteService *pSvc = nullptr;
  203. //  NimBLERemoteCharacteristic *pChr = nullptr;
  204.   std::vector<NimBLERemoteCharacteristic*> *pChrs = nullptr;

  205.   NimBLERemoteDescriptor *pDsc = nullptr;

  206.   pSvc = pClient->getService(serviceUUID);
  207.   if (pSvc) { /** make sure it's not null */
  208.     pChrs = pSvc->getCharacteristics(true);
  209.   }

  210.   if (pChrs) { /** make sure it's not null */

  211.     for (int i = 0; i < pChrs->size(); i++) {

  212.       if (pChrs->at(i)->canNotify()) {
  213.         /** Must send a callback to subscribe, if nullptr it will unsubscribe */
  214.         if (!pChrs->at(i)->registerForNotify(notifyCB)) {
  215.           /** Disconnect if subscribe failed */
  216.           pClient->disconnect();
  217.           return false;
  218.         }
  219.       }
  220.     }
  221.   }

  222.   else {
  223.     Serial.println("DEAD service not found.");
  224.   }

  225.   Serial.println("Done with this device!");
  226.   return true;
  227. }

  228. void setup() {
  229.   Serial.begin(115200);
  230.   Serial.println("Starting NimBLE Client");
  231.   M5.begin();
  232.   pinMode(GPIO_NUM_10, OUTPUT);
  233.   digitalWrite(GPIO_NUM_10, HIGH);

  234.   /** Initialize NimBLE, no device name spcified as we are not advertising */
  235.   NimBLEDevice::init("");

  236.   /** Set the IO capabilities of the device, each option will trigger a different pairing method.
  237.    *  BLE_HS_IO_KEYBOARD_ONLY    - Passkey pairing
  238.    *  BLE_HS_IO_DISPLAY_YESNO   - Numeric comparison pairing
  239.    *  BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
  240.    */
  241. //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
  242. //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
  243.   /** 2 different ways to set security - both calls achieve the same result.
  244.    *  no bonding, no man in the middle protection, secure connections.
  245.    *
  246.    *  These are the default values, only shown here for demonstration.
  247.    */
  248. //NimBLEDevice::setSecurityAuth(false, false, true);
  249.   NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/BLE_SM_PAIR_AUTHREQ_SC);

  250.   /** Optional: set the transmit power, default is 3db */
  251.   NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */

  252.   /** Optional: set any devices you don't want to get advertisments from */
  253. // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
  254.   /** create new scan */
  255.   NimBLEScan *pScan = NimBLEDevice::getScan();

  256.   /** create a callback that gets called when advertisers are found */
  257.   pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());

  258.   /** Set scan interval (how often) and window (how long) in milliseconds */
  259.   pScan->setInterval(10000);
  260.   pScan->setWindow(9999);

  261.   /** Active scan will gather scan response data from advertisers
  262.    *  but will use more energy from both devices
  263.    */
  264.   pScan->setActiveScan(true);
  265.   /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
  266.    *  Optional callback for when scanning stops.
  267.    */
  268.   pScan->start(scanTime, scanEndedCB);
  269. }

  270. void loop() {
  271.   M5.update();
  272.   if (M5.BtnA.wasReleased()) {
  273.     M5.Axp.PowerOff();
  274.   }
  275.   while (!doConnect) {
  276.     M5.update();
  277.     if (M5.BtnA.wasReleased()) {
  278.       M5.Axp.PowerOff();
  279.     }
  280.     delay(1);
  281.   }

  282.   doConnect = false;

  283.   /** Found a device we want to connect to, do it now */
  284.   if (connectToServer()) {
  285.     Serial.println("Success! we should now be getting notifications, scanning for more!");
  286.   }
  287.   else {
  288.     Serial.println("Failed to connect, starting scan");
  289.   }

  290. //    NimBLEDevice::getScan()->start(scanTime,scanEndedCB);
  291. }
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 02:41 , Processed in 0.123683 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表