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

vany5921 发表于 2020-7-31 12:02

StickC与BLE HID的NimBLE通讯实验

安装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);
}
Serial.print("\n");
if (pData & 0x28) {
    //如果00 28,请打开LED并设置一个标志
    digitalWrite(GPIO_NUM_10, LOW);
    on = true;
}
if (pData & 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);
}
Serial.print("\n");
if (pData & 0x28) {
    digitalWrite(GPIO_NUM_10, LOW);
    on = true;
}
if (pData & 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);
}

页: [1]
查看完整版本: StickC与BLE HID的NimBLE通讯实验