chegewara / esp32-hid-keyboard-client

41 stars 5 forks source link

Reading of a descriptor of HID Service #3

Open mkardan opened 4 years ago

mkardan commented 4 years ago

Hi there,

I just wondering of there is an example of reading the descriptor of a service that I can take a look on?

Cheers, FakeStar

chegewara commented 4 years ago

The same way as characteristic: https://github.com/nkolban/esp32-snippets/blob/faf2839905c908777a7dbdefd7eb9f933b0f1716/cpp_utils/tests/BLETests/SampleSensorTag.cpp#L72

mkardan commented 4 years ago

Thanks a lot Chegewara! :)

mkardan commented 4 years ago

Hi Chegewara, A quick one; is it normal to get the following once I've tried to read a descriptor please? The client keeps rebooting. "Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled. Core 0 register dump: PC : 0x400d409a PS : 0x00060830 A0 : 0x800d1a63 A1 : 0x3ffcdef0 A2 : 0x00000000 A3 : 0xfb7f4d54 A4 : 0x3ffc52d0 A5 : 0x00000024 A6 : 0x3ffdfd5c A7 : 0x00010000 A8 : 0x800d4096 A9 : 0x3ffcde60
A10 : 0x3ffcdf14 A11 : 0xfb7f4d54 A12 : 0x3ffc52d0 A13 : 0xfb7f4d54 A14 : 0x00060e23 A15 : 0x00000000 SAR : 0x00000018 EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000108 LBEG : 0x4000c2e0 LEND : 0x4000c2f6 LCOUNT : 0x00000000

Backtrace: 0x400d409a:0x3ffcdef0 0x400d1a60:0x3ffcdf50 0x400d33e9:0x3ffcdf70 0x400d3eb9:0x3ffce020 0x401053d5:0x3ffce070 0x400fe176:0x3ffce0c0 0x4008e089:0x3ffce0f0

Rebooting..._ "

I've managed to interpret the codes through exception decoder:

Decoding stack results 0x400d40d2: BLERemoteCharacteristic::getDescriptor(BLEUUID) at C:\Users\mk\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.1\libraries\BLE\src\BLERemoteCharacteristic.cpp line 330 0x400d13d8: MyClientCallback::onConnect(BLEClient) at C:\Users\mk\Documents\Arduino\HID_Host\main/main.ino line 106 0x400d33d9: BLEClient::gattClientEventHandler(esp_gattc_cb_event_t, unsigned char, esp_ble_gattc_cb_param_t) at C:\Users\mk\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.1\libraries\BLE\src\BLEClient.cpp line 212 0x400d3ec1: BLEDevice::gattClientEventHandler(esp_gattc_cb_event_t, unsigned char, esp_ble_gattc_cb_param_t*) at C:\Users\mk\Documents\ArduinoData\packages\esp32\hardware\esp32\1.0.1\libraries\BLE\src\BLEDevice.cpp line 173 0x400e2941: btc_gattc_cb_handler at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/profile/std/gatt/btc_gattc.c line 31 0x400de686: btc_task at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/bt/bluedroid/btc/core/btc_task.c line 110 0x40092545: vPortTaskWrapper at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/freertos/port.c line 143

Thanks a lot, FakeStar

chegewara commented 4 years ago

Im guessing you are trying to read descriptor that does not exist or you didnt get service and or characteristic before reading descriptor.

mkardan commented 4 years ago

Thanks a lot that was the issue. :)

mkardan commented 4 years ago

Hello again, Sorry to keep hassling you. I am trying to read a descriptor - attached file but it seems the program does not return anything after I use get descriptor command- I have inserted a debug line [Serial.println(" - the descriptor value is: "); - [Line 214]

It seems the program gets to that point, finds the charachteristic but nothing after this line get executed. Do you have any idea why is it happening?

Thanks a lot, FakeStar

mkardan commented 4 years ago

Have to find out why it doesn't attach either C++ or text files.. sorry

mkardan commented 4 years ago

[sorry had to paste the code here as it didn't accept it as an attachment.]

/**

include

include "BLEDevice.h"

//#include "BLEScan.h"

include "HIDKeys.h"

// The remote service we wish to connect to.

static BLEUUID serviceUUID((uint16_t) 0x1812);

// The characteristic of the remote service we are interested in.

static BLEUUID charUUID((uint16_t) 0x2a22); // static BLEUUID desUUID((uint16_t) 0x2907); static BLEUUID desUUID((uint16_t) 0x2902);

static bool doConnect = false;

static bool connected = false;

static bool doScan = false;

static BLERemoteCharacteristic* pRemoteCharacteristic;

static BLEAdvertisedDevice* myDevice;

static void notifyCallback(

BLERemoteCharacteristic* pBLERemoteCharacteristic,

uint8_t* pData,

size_t length,

bool isNotify) {

ifdef DEBUG

Serial.print("Notify callback for characteristic ");

Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());

Serial.print(" of data length ");

Serial.println(length);

Serial.print("data: ");

for (size_t i = 0; i < length; i++)

{

  Serial.printf("%2x", pData[i]);

}

Serial.println("");

endif

if(pData[0] == 0x0 && pData[2] != 0x0) {

  Serial.printf("%c", keys[pData[2]]);

} else if(pData[0] == 0x02 && pData[2] != 0x0) {

  Serial.printf("%c", shift_keys[pData[2]]);      

}

}

class MyClientCallback : public BLEClientCallbacks {

void onConnect(BLEClient* pclient) {

connected = true;

}

void onDisconnect(BLEClient* pclient) {

connected = false;

Serial.println("onDisconnect");

}

};

bool connectToServer() {

Serial.print("Forming a connection to ");

Serial.println(myDevice->getAddress().toString().c_str());

BLEClient*  pClient  = BLEDevice::createClient();

Serial.println(" - Created client");

pClient->setClientCallbacks(new MyClientCallback());

// Connect to the remove BLE Server.

if(!pClient->connect(myDevice)) {  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)

  return false;

}

Serial.println(" - Connected to server");

// Obtain a reference to the service we are after in the remote BLE server.

BLERemoteService* pRemoteService = pClient->getService(serviceUUID);

if (pRemoteService == nullptr) {

  Serial.print("Failed to find our service UUID: ");

  Serial.println(serviceUUID.toString().c_str());

  pClient->disconnect();

  return false;

}

Serial.println(" - Found our service");

// Obtain a reference to the characteristic in the service of the remote BLE server.

do {

  pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);

  if (pRemoteCharacteristic == nullptr) {

    Serial.print("Failed to find our characteristic UUID: ");

    Serial.println(charUUID.toString().c_str());

    pClient->disconnect();

    return false;

  }

  if(pRemoteCharacteristic->canNotify())

    break;

} while(1);

Serial.println(" - Found our characteristic");

//pRemoteCharacteristic->registerForNotify(notifyCallback);

// Serial.println(" - the descriptor value is: "); BLERemoteDescriptor *pRemoteDescriptor = pRemoteCharacteristic->getDescriptor(BLEUUID(desUUID));

Serial.println(pRemoteDescriptor->readValue().c_str());

Serial.println(" - the descriptor value is: "); // debug line 
pRemoteCharacteristic->registerForNotify(notifyCallback);

return true;

}

/**

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

/**

// Test to find out if the the descriptor of the device is the same as the target descriptor

} // onResult

}; // MyAdvertisedDeviceCallbacks

void setup() {

Serial.begin(115200);

Serial.println("Starting Arduino BLE Client application...");

BLEDevice::init("");

// Retrieve a Scanner and set the callback we want to use to be informed when we

// have detected a new device. Specify that we want active scanning and start the

// scan to run for 5 seconds.

BLEScan* pBLEScan = BLEDevice::getScan();

pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());

pBLEScan->setInterval(1349);

pBLEScan->setWindow(449);

pBLEScan->setActiveScan(true);

pBLEScan->start(5, false);

} // End of setup.

// This is the Arduino main loop function.

void loop() {

// If the flag "doConnect" is true then we have scanned for and found the desired

// BLE Server with which we wish to connect. Now we connect to it. Once we are

// connected we set the connected flag to be true.

if (doConnect == true) {

if (connectToServer()) {

  Serial.println("We are now connected to the BLE Server.");

} else {

  Serial.println("We have failed to connect to the server; there is nothin more we will do.");

}

doConnect = false;

}

// If we are connected to a peer BLE Server, update the characteristic each time we are reached

// with the current time since boot.

if (connected) {

// do nothing, all data is handled in notifications callback

}else if(doScan){

BLEDevice::getScan()->start(0);  // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino

}

delay(1000); // Delay a second between loops.

} // End of loop

chegewara commented 4 years ago

The problem may be in BLE library or my code you are trying to use, because HID device can have multiple characteristics with the same UUID. What you need to do is more debugging and understand HID device you want to connect with.

mkardan commented 4 years ago

Hello again,

I still cannot get any value back from the characteristics of an HID service. Could you please look into this for me?

I even tried to read the HID information (0x2a4a) with no luck-

I have to set the protocol mode (0x2a4e) to zero in order to tell the device that I want to use the boot host. Then I have to connect to boot keyboard input report characteristic (0x2a22) in order to set client descriptor 2902 and subscribe to notification. But seems like I cannot even read-back the protocol mode not even the HID information back form the device.

I know these characteristic work as I have checked the same device with nRFConnect and they all work fine.

Any help will be really appreciated.

Many thanks,

/**

include

include "BLEDevice.h"

//#include "BLEScan.h"

include "HIDKeys.h"

// The remote service we wish to connect to. static BLEUUID serviceUUID((uint16_t) 0x1812); // The characteristic of the remote service we are interested in. //static BLEUUID charUUID((uint16_t) 0x2a4d); static BLEUUID charUUID((uint16_t) 0x2a22); // Keyboard Boot Input charachteristic. static BLEUUID protUUID((uint16_t) 0x2a4a); // Protocol Mode: Write 0x00
static BLEUUID desUUID((uint16_t) 0x2902); // Descriptors to be set to

static bool doConnect = false; static bool connected = false; static bool doScan = false;

const uint8_t NotificationOn[]={0x2,0x0};

static BLERemoteCharacteristic pRemoteCharacteristic_inptrt; static BLERemoteCharacteristic pRemoteCharachterstic_ptr; //static BLERemoteDescriptor pRemoteDescriptor; static BLEAdvertisedDevice myDevice; uint8_t val_prt={0x00}; //uint8_t val_des={0x01,0x00};

// Call back from the boot keyboard input report notification static void notifyCallback( BLERemoteCharacteristic pRemoteCharacteristic_inptrt, uint8_t pData, size_t length, bool isNotify) { Serial.println("we are in notify routine.");

// set the value of the protocol to 0

ifdef DEBUG

Serial.print("Notify callback for characteristic ");
Serial.print(pRemoteCharacteristic_inptrt->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
for (size_t i = 0; i < length; i++)
{
  Serial.printf("%2x", pData[i]);
}
Serial.println("");

endif

/*Iif(pData[0] == 0x0 && pData[2] != 0x0) {
  Serial.printf("%c", keys[pData[2]]);
} else if(pData[0] == 0x02 && pData[2] != 0x0) {
  Serial.printf("%c", shift_keys[pData[2]]);      
}*/
std::string barcode= pRemoteCharacteristic_inptrt->readValue();
Serial.println("barcode is:");
Serial.println(barcode.c_str());

}

// Client is connected or not class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { // pRemoteCharachterstic_ptr->writeValue(0); connected = true; }

void onDisconnect(BLEClient* pclient) { connected = false; Serial.println("onDisconnect"); } };

// Connect to the server and create the client; Main function. bool connectToServer() {

// pRemoteCharachterstic_ptr->writeValue(0);

Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());

BLEClient*  pClient  = BLEDevice::createClient();
Serial.println(" - Created client");

pClient->setClientCallbacks(new MyClientCallback());

// Connect to the remove BLE Server.
if(!pClient->connect(myDevice)) {  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
  return false;
}
Serial.println(" - Connected to server");

// Obtain a reference to the service we are after in the remote BLE server.
BLERemoteService* pRemoteService = pClient->getService(serviceUUID);

if (pRemoteService == nullptr) {
  Serial.print("Failed to find our service UUID: ");
  Serial.println(serviceUUID.toString().c_str());
  pClient->disconnect();
  return false;
}
Serial.println(" - Found our service");

// Obtain a reference to the characteristic in the service of the remote BLE server.

/ do {/ //check if the charac //hteristic is the issue is withthe qrite value-- try to remove write value and see if the retrive //descriptor changes. Serial.println("what exactly causes the retrive descriptoe issue?"); pRemoteCharachterstic_ptr=pRemoteService->getCharacteristic(protUUID); //->writeValue(0); // pRemoteCharachterstic_ptr->writeValue(0,false); //std::string ptr_value = pRemoteCharachterstic_ptr->readValue(); Serial.println("setting the value of the protococol charachteristic"); std::string value_prt= pRemoteCharachterstic_ptr->readValue(); Serial.println(value_prt.c_str()); // Serial.println(pRemoteCharachterstic_ptr->readValue().c_str());

  pRemoteCharacteristic_inptrt = pRemoteService->getCharacteristic(charUUID);
  Serial.println("setting the descriptor");

  if (pRemoteCharacteristic_inptrt == nullptr) {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(charUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }

  if(pRemoteCharacteristic_inptrt->canNotify()){
     // BLERemoteDescriptor *pRemoteDescriptor = pRemoteCharacteristic_inptrt->getDescriptor(desUUID);
     // pRemoteDescriptor->writeValue(1);
      Serial.println("The Charachteristic Can Notify.");

  BLERemoteDescriptor *pRemoteDescriptor = pRemoteCharacteristic_inptrt->getDescriptor(desUUID);//->writeValue(1);//,true);
  pRemoteDescriptor->writeValue(1);
  pRemoteCharacteristic_inptrt->registerForNotify(notifyCallback,false);

delay(200);

    //   pRemoteCharacteristic_inptrt->registerForNotify(notifyCallback,true);
    // std::string read_value = pRemoteDescriptor->readValue();
    // Serial.println(read_value.c_str());  
  //  break;}
  }

// } while(1);*/

Serial.println(" - Found our characteristic");

//set the descriptor

//BLERemoteDescriptor *pRemoteDescriptor = pRemoteCharacteristic_inptrt->getDescriptor(desUUID); //pRemoteDescriptor->writeValue(1);

//set the protocol mode to the boot host 
/*pRemoteCharachterstic_prt->writeValue(val_prt);
Serial.println("The protocol mode is set to the Boot Host.");
std::string read_value_prt = pRemoteCharachterstic_prt->readValue();
Serial.println("The protocol value is:");
Serial.println(read_value_prt.c_str());
// Write 1 in descriptor of the rport boot
//
pRemoteCharacteristic_inptrt->registerForNotify(notifyCallback);*/

return true;

} /**

void setup() { Serial.begin(115200); Serial.println("Starting Arduino BLE Client application..."); BLEDevice::init("");

// Retrieve a Scanner and set the callback we want to use to be informed when we // have detected a new device. Specify that we want active scanning and start the // scan to run for 5 seconds. BLEScan* pBLEScan = BLEDevice::getScan(); pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); pBLEScan->start(5, false); } // End of setup.

// This is the Arduino main loop function. void loop() {

// If the flag "doConnect" is true then we have scanned for and found the desired // BLE Server with which we wish to connect. Now we connect to it. Once we are // connected we set the connected flag to be true. if (doConnect == true) { Serial.println("in loop= doconnect== true"); if (connectToServer()) { Serial.println("in loop= ConnectToServer== true"); / Serial.println("We are now connected to the BLE Server."); Serial.println("Characteristic Value is: "); //Serial.println(pRemoteCharacteristic->readValue().c_str()); pRemoteCharachterstic_prt->writeValue(val_prt,1); Serial.println("The value of the protocol is:"); Serial.println(pRemoteCharachterstic_prt->readValue().c_str());/

} else {
  Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
doConnect = false;

}

// If we are connected to a peer BLE Server, update the characteristic each time we are reached // with the current time since boot. if (connected==true) { // Serial.println("in loop= connected== true"); // do nothing, all data is handled in notifications callback }else if(doScan==true){ Serial.println("in loop= doscan== true"); BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino }

delay(1000); // Delay a second between loops. } // End of loop

chegewara commented 4 years ago

Thats strange it works fine with nRF connect, because some time ago android changed usage of HID and is the same as on iOS, you shouldnt be able to read HID characteristics. Only non-HID can be read now.

mkardan commented 4 years ago

Yes, it works fine nRF connect and I also can read Battery Level in Battery Service. Do you know what is the reason I cannot set/read HID characteristics/descriptors? Cheers

chegewara commented 4 years ago

Maybe issue with pairing/bonding?

mkardan commented 4 years ago

Funny enough, it founds the HID server and the characteristics, I just cannot write/read to/from the charachtersitics and descriptors.

chegewara commented 4 years ago

This is how BLE works according to specs. You cant protect against discovering but you can against read/write.

mkardan commented 4 years ago

Let's say my device is protected against read and write, any clue how can I remove the protection? Thank you.

chegewara commented 4 years ago

esp32 device or other device?

mkardan commented 4 years ago

I am trying to implement a host/client in ESP32, my HID device is a barcode scanner.

chegewara commented 4 years ago

As far as i remember protection only works when you also use security, which is pairing with pass key.

mkardan commented 4 years ago

Well, interestingly the Bluetooth scanner does not require a pass key when I connect it to the operation system. However as I cannot read back the characteristics I set, I thought it might be to do with the security?

mkardan commented 4 years ago

Hi,

I really hope you can help me as I am stuck in this for a couple of months now; I am not a software developer so please bear with me as I am risking my job on this. The project that I am working on is developing an HID over GATT client on ESP32 to work with an off the shelf Ble barcode scanner. The scanner does not have any passcode to connect to it & it get discovered as an HID keyboard. I have used nRF connect on Android 7.0 and managed to: 1- Set the device in boot host mode : writing 0 in protocol mode characteristic 2- Set 2902 to 0x01 to enable the notification 3- Subscribe to notification 4- Reading the data on the value field of Keyboard Boot Input

I’ve tried to replicate above procedure on the client code supplied here. 1- I wrote zero in Protocol Mode characteristics ( I cannot test these as it doesn’t return any value when I try to read this back) 2- I set the 2902 descriptor to 1 – outside of the call back- (I cannot test this also) 3- I call for register for notification and notification call back but nothing happens… I am not sure if I am facing any sort of security issue or what as I cannot read anything back from HID service. – I can read battery level back with no problem-

chegewara commented 4 years ago
  1. Why change it to boot host mode? It is only used by keyboards and mice before system starts and drivers are not loaded yet.

2- I set the 2902 descriptor to 1 – outside of the call back- (I cannot test this also)

you can test it, you can always try to read descriptor

Try nRF connect without changing boot mode, and see if you receive any notifications and when, what you have to do to receive notifications etc

HID protocol is not easy, and trying to work with it not being programmer is almost not possible. There is too many info, device types, each product can use its own HID semantic to proceed with HID data. You have to read and parse report map, then understand it and prepare host app, or write universal HID driver. I know HID a bit, but i dont want to write universal HID driver, too much work.

mkardan commented 4 years ago

Thanks a lot for your support, I try to read back the descriptor if I can.

Btw do you recommend using BTStack from Bluekitchen? https://github.com/bluekitchen/btstack/blob/master/example/hid_host_demo.c

chegewara commented 4 years ago

I dont know this library, but i know that author is much better bluetooth expert than i am. If you know C, then its worth to try. Besides this repo is just example as a proof of concept and starting point, his library can be better developed.

hardmax commented 3 years ago

Hi mkardan,

I wish made the same, connect a Bluetooth Scanner to ESP32, and read some data to send to ESP32. Do you have some advance about it?

Regards