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]