Im trying to read the value stored in a BluetoothGattCharacteristic. The following is my BluetoothGattCallback code, where most of the action takes place:
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Getting services....");
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService serv = gatt.getService(Constants.MY_UUID);
if (serv != null) {
BluetoothGattCharacteristic characteristic = serv.getCharacteristic(Constants.ANOTHER_UUID);
boolean res = gatt.readCharacteristic(characteristic);
if (res) {
Log.d(TAG, "res was true");
} else {
Log.d(TAG, "res was false");
}
}
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Succesfully read characteristic: " + characteristic.getValue().toString());
} else {
Log.d(TAG, "Characteristic read not successful");
}
}
};
So to read from the characteristic, i'm attempting to use the gatt.readCharacteristic() method, which takes a characteristic and returns a boolean indicating a successful operation or not. Here, this method is returning false (printing "res was false"), indicating it failed.
There is no error message being printed. What is the proper way to read a characteristic? Why would this method be returning false?
EDIT:
As suggested by Inferno, went ahead and downloaded the needed sources and then set a breakpoint in the BluetoothGatt readCharacteristic() method:
Here is the readCharacteristic() method in android-23..\BluetoothGatt
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() &
BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
(characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) is returning 0 so false is being immediately returned. Now according to the debugger characteristic.getProperties() is returning a value of 8, while BluetoothGattCharacteristic.PROPERTY_READ has a static int value of 0x02.
As I understand, 0x08 & 0x02 == 0. Since the PROPERTY_READ is a hardcoded value, I assume something is wrong with the value returned from characteristic.getProperties(). What could be going wrong here?
What is the proper way to read a characteristic?
First of all, you call gatt.readCharacteristic(characteristic) from inside of the onServicesDiscovered() callback, which is alright. I can't see any serious flaws in your code.
What you could add in onConnectionStateChange() is an additional check before you verify newState == BluetoothProfile.STATE_CONNECTED:
if (status == BluetoothGatt.GATT_SUCCESS) { ...
Why would this method be returning false?
I checked the android source of BluetoothGatt here and it turns out, the return value of false is returned in many different cases as you can see in the code below:
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() &
BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
if (mService == null || mClientIf == 0) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized(mDeviceBusy) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readCharacteristic(mClientIf, device.getAddress(),
characteristic.getInstanceId(), AUTHENTICATION_NONE);
} catch (RemoteException e) {
Log.e(TAG,"",e);
mDeviceBusy = false;
return false;
}
return true;
}
So what I recommend you to do is, start the debugger in Android Studio and set a breakpoint inside the readCharacteristic() method (in BluetoothGatt.java) and carefully step through the code to see where false gets returned. That way you will hopefully be able to localize the issue. Besides that, anything else would be wild guessing.
Of course you need to have the sources downloaded to be able to view BluetoothGatt.java. But Android Studio will give you a small yellow bar at the top of the editor which asks you if you want to download and install. Just do it and restart Android Studio after the download is complete. Then you should be able to set a breakpoint in BluetoothGatt.java.
UPDATE:
As I understand, 0x08 & 0x02 == 0. Since the PROPERTY_READ is a
hardcoded value, I assume something is wrong with the value returned
from characteristic.getProperties(). What could be going wrong here?
According to BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 533, the value of 0x8 which is returned by characteristic.getProperties() means, that your characteristic has write only permissions. Not a surprise that all reading attempts fail. In other words: your bluetooth device does not allow you to read that particular characteristic.
Quote from the specification:
The Characteristic Properties bit field determines how the Characteristic Value
can be used, or how the characteristic descriptors (see Section 3.3.3) can be
accessed.
I was trying to read data back from a cow brush scratcher that had BLE chip.
It was under a read characteristic on a BLE module.
The data was coming back in hex i.e. 0x00 for BRUSH_OFF & 0x01 for BRUSH_ON
I was trying to read in this data in my android app and it kept coming back as blank.
Problem is 0x00 = NUll in ascii and 0x01 = SOH ascii it cannot be displayed on the screen.
0x30 = 0 in ascii 0x31 = 1 in ascii
Maybe you have escape characters coming back in hex and they cannot be read.
I spent months trying to figure out why i couldn't read back the values.
Hope this might help you.
I'm trying to read the initial state of a BLE device when I connect to it. Here's the code I have to try to do that:
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
if(status == BluetoothGatt.GATT_SUCCESS)
{
Log.i(TAG, gatt.getDevice().toString() + "Discovered Service Status: " + gattStatusToString(status));
for(BluetoothGattService service : gatt.getServices())
{
Log.i(TAG, "Discovered Service: " + service.getUuid().toString() + " with " + "characteristics:");
for(BluetoothGattCharacteristic characteristic : service.getCharacteristics())
{
// Set notifiable
if(!gatt.setCharacteristicNotification(characteristic, true))
{
Log.e(TAG, "Failed to set notification for: " + characteristic.toString());
}
// Enable notification descriptor
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCC_UUID);
if(descriptor != null)
{
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
// Read characteristic
if(!gatt.readCharacteristic(characteristic))
{
Log.e(TAG, "Failed to read characteristic: " + characteristic.toString());
}
}
}
}
else
{
Log.d(TAG, "Discover Services status: " + gattStatusToString(status));
}
}
But the read fails every time! Later if I initiate a read based on UI interaction it reads just fine! Any ideas about what's going on here?
In the Android BLE implementation, the gatt operation calls need to be queued so that only one operation (read, write, etc.) is in effect at a time. So for example, after gatt.readCharacteristic(characteristicX) is called, you need to wait for the gatt callbackBluetoothGattCallback.onCharacteristicRead() to indicate the read is finished. If you initiate a second gatt.readCharacteristic() operation before the previous one completes, the second one will fail (by returning false) This goes for all of the gatt.XXX() operations.
Its a little work, but I think the best solution is to create a command queue for all the gatt operations and run them one at a time. You can use the command pattern to accomplish this.
I am able to discover, connect to bluetooth.
Source Code---
Connect via bluetooth to Remote Device:
//Get the device by its serial number
bdDevice = mBluetoothAdapter.getRemoteDevice(blackBox);
//for ble connection
bdDevice.connectGatt(getApplicationContext(), true, mGattCallback);
Gatt CallBack for Status:
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
//Connection established
if (status == BluetoothGatt.GATT_SUCCESS
&& newState == BluetoothProfile.STATE_CONNECTED) {
//Discover services
gatt.discoverServices();
} else if (status == BluetoothGatt.GATT_SUCCESS
&& newState == BluetoothProfile.STATE_DISCONNECTED) {
//Handle a disconnect event
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//Now we can start reading/writing characteristics
}
};
Now I want to send commands to Remote BLE device but don't know how to do that.
Once the command is sent to the BLE device, the BLE device will respond by broadcasting
data which my application can receive.
You need to break this process into a few steps, when you connect to a BLE device and discover Services:
Display available gattServices in onServicesDiscovered for your callback
To check whether you can write a characteristic or not
check for BluetoothGattCharacteristic PROPERTIES -I didn't realize that need to enable the PROPERTY_WRITE on the BLE hardware and that wasted a lot of time.
When you write a characteristic, does the hardware perform any action to explicitly indicate the operation (in my case i was lighting an led)
Suppose mWriteCharacteristic is a BluetoothGattCharacteristic
The part where to check the PROPERTY should be like:
if (((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) |
(charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) > 0) {
// writing characteristic functions
mWriteCharacteristic = characteristic;
}
And, to write your characteristic:
// "str" is the string or character you want to write
byte[] strBytes = str.getBytes();
byte[] bytes = activity.mWriteCharacteristic.getValue();
YourActivity.this.mWriteCharacteristic.setValue(bytes);
YourActivity.this.writeCharacteristic(YourActivity.this.mWriteCharacteristic);
Those are the useful parts of the code that you need to implement precisely.
Refer this github project for an implementation with just a basic demo.
A noob-friendly guide to make Android interact with a LED-lamp.
Step 1.
Get an tool to scan your BLE device. I used "Bluetooth LE Lab" for Win10, but this one will do it as well: https://play.google.com/store/apps/details?id=com.macdom.ble.blescanner
Step 2.
Analyse the behavior of the BLE device by entering data, I recommend to enter hex values.
Step 3.
Get the sample of the Android docs. https://github.com/googlesamples/android-BluetoothLeGatt
Step 4.
Modify the UUIDs you find in SampleGattAttributes
My config:
public static String CUSTOM_SERVICE = "0000ffe5-0000-1000-8000-00805f9b34fb";
public static String CLIENT_CHARACTERISTIC_CONFIG = "0000ffe9-0000-1000-8000-00805f9b34fb";
private static HashMap<String, String> attributes = new HashMap();
static {
attributes.put(CUSTOM_SERVICE, CLIENT_CHARACTERISTIC_CONFIG);
attributes.put(CLIENT_CHARACTERISTIC_CONFIG, "LED");
}
Step 5.
In BluetoothService.java modify onServicesDiscovered:
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
for (BluetoothGattService gattService : gatt.getServices()) {
Log.i(TAG, "onServicesDiscovered: ---------------------");
Log.i(TAG, "onServicesDiscovered: service=" + gattService.getUuid());
for (BluetoothGattCharacteristic characteristic : gattService.getCharacteristics()) {
Log.i(TAG, "onServicesDiscovered: characteristic=" + characteristic.getUuid());
if (characteristic.getUuid().toString().equals("0000ffe9-0000-1000-8000-00805f9b34fb")) {
Log.w(TAG, "onServicesDiscovered: found LED");
String originalString = "560D0F0600F0AA";
byte[] b = hexStringToByteArray(originalString);
characteristic.setValue(b); // call this BEFORE(!) you 'write' any stuff to the server
mBluetoothGatt.writeCharacteristic(characteristic);
Log.i(TAG, "onServicesDiscovered: , write bytes?! " + Utils.byteToHexStr(b));
}
}
}
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
Convert the byte-String using this function:
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
PS: The above code is far away from production, but I hope it helps those, who are new to BLE.
I am working in an app for Android that uses BLE. I want to write inside a characteristic of the device Service that I am connected to.
My function is this:
public void writeCharacteristic(BluetoothGattCharacteristic characteristic,
boolean enabled, String text) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
characteristic.setValue("7");
boolean status = mBluetoothGatt.writeCharacteristic(characteristic);
}
I do not why the value is not written inside the characteristic.
I followed the steps in this link:
write with BLE
anyone knows why my code does not work?
Thank you very much.
Regards
P.D. apologies for my English.
Maybe your characteristic accepts a byte[] value. Try setting the characteristic value with byte array by converting String parameter into byte[]. Your method should be like this:
public void writeCharacteristic(BluetoothGattCharacteristic characteristic,
String text) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
byte[] data = hexStringToByteArray(text);
characteristic.setValue(data);
boolean status = mBluetoothGatt.writeCharacteristic(characteristic);
}
private byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
Also note that, status variable returns true, if the write operation was initiated successfully. So, in order to get write operation result status use onCharacteristicWrite callback of BluetoothGattCallback and check the status in it.
After spending all morning at the computer trying different functions and forms, I found the solution thanks to a friend from work.
We have to convert the text to byte, then put that byte into a byte array and send. Fixed.
byte pepe = (byte) Integer.parseInt(text);
byte[] charLetra = new byte[1];
charLetra[0] = pepe;
LumChar.setValue(charLetra);
boolean status = mBluetoothGatt.writeCharacteristic(LumChar);
Anyway thank you very much for your help.
Regards.
I am new to USB and to Android so please forgive me if I don't explain myself clearly.
I have a USB HID device that I can communicate with in Windows. I am trying to establish communication using an Acer Iconia A500 tablet running Android 3.1.
I am able to find the device, enumerate it, get its only available interface, get the only available endpoint (0), and determine what type of endpoint it is (transfer interrupt from device to host).
My understanding of the USB spec is that all HID devices are required at a munimum to have a control endpoint (Endpoint 0) and an interrupt IN endpoint. But it seems that endpoint 0 here is the interrupt In endpoint, not the control endpoint.
Yet in order for the device to enumerate it must successfully transfer its descriptor data across the control endpoint. I deduce that the control endpoint therefore must be getting found (and used) because the host does, in fact, enumerate the device.
This is as far as I am able to proceed, as stated above, the only interface/endpoint presented to me at the application level is an interrupt type going from device to host. No endpoint available to my app going from host to device, interrupt or control. So the device waits to be told what to do and the host waits for something to happen in the device. Not very stimulating.
Bear in mind that this device responds properly when connected to Windows, e.g. I am able to send a report containing 13 bytes of data that causes the device to light an LED. So it seems to be complying with the USB HID spec. As an act of desperation I have tried using this one endpoint as both a control endpoint and as a interrupt OUT endpoint, using controltransfer() and UsbRequest() to submit the data to the device, no response in either case.
So my question is: "The control transfer endpoint is (?) being used to set up the device, why am I not able to find & use it?"
Thanks for any insight, below is the relevant code, I can include the rest in its entirety if needed:
private UsbManager mUsbManager;
private UsbDevice mDevice;
private UsbDeviceConnection mConnectionRead;
private UsbDeviceConnection mConnectionWrite;
private UsbEndpoint mEndpointRead;
private UsbEndpoint mEndpointWrite;
// check for existing devices
for (UsbDevice device : mUsbManager.getDeviceList().values())
{
//Need to filter for my device when other HIDs are also connected, but for now...
String devName = device.getDeviceName();
if (DEBUG == 1){
Toast.makeText(UsbHidDeviceTesterActivity.this, "My device got connected: " + devName, Toast.LENGTH_LONG).show();
}
//mDevice = device;
setHIDDevice(device);
}
private boolean setHIDDevice(UsbDevice device)
{
UsbInterface usbInterfaceRead = null;
UsbInterface usbInterfaceWrite = null;
UsbEndpoint ep1 = null;
UsbEndpoint ep2 = null;
boolean UsingSingleInterface = true;
mDevice = device;
//This HID device is using a single interface
if (UsingSingleInterface)
{
//usbInterfaceRead = device.getInterface(0x00);//only 1 EP on this interface
usbInterfaceRead = findInterface(device);
//Try getting an interface at next index
//usbInterfaceWrite = device.getInterface(0x01);//throws exception
// Try using the same interface for reading and writing
usbInterfaceWrite = usbInterfaceRead;
int endPointCount = usbInterfaceWrite.getEndpointCount();
if (DEBUG == 2)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Endpoints: " + endPointCount, Toast.LENGTH_LONG).show();
//Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface: " + usbInterfaceRead, Toast.LENGTH_LONG).show();
}
if (endPointCount == 1)//only getting 1 endpoint
{
ep1 = usbInterfaceRead.getEndpoint(0);
//As an act of desperation try equating ep2 to this read EP, so that we can later attempt to write to it anyway
ep2 = usbInterfaceRead.getEndpoint(0);
}
else if (endPointCount == 2)
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceRead.getEndpoint(1);
}
}
else // ! UsingSingleInterface
{
usbInterfaceRead = device.getInterface(0x00);
usbInterfaceWrite = device.getInterface(0x01);
if ((usbInterfaceRead.getEndpointCount() == 1) && (usbInterfaceWrite.getEndpointCount() == 1))
{
ep1 = usbInterfaceRead.getEndpoint(0);
ep2 = usbInterfaceWrite.getEndpoint(0);
}
if (DEBUG == 3)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Using Dual Interface", Toast.LENGTH_LONG).show();
}
}
//because ep1 = ep2 this will now not cause a return unless no ep is found at all
if ((ep1 == null) || (ep2 == null))
{
if (DEBUG == 4)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "One EP is null", Toast.LENGTH_LONG).show();
}
return false;
}
// Determine which endpoint is the read, and which is the write
if (ep1.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)//I am getting a return of 3, which is an interrupt transfer
{
if (ep1.getDirection() == UsbConstants.USB_DIR_IN)//I am getting a return of 128, which is a device-to-host endpoint
{
mEndpointRead = ep1;
if (DEBUG == 5)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 type: " + ep1.getType(), Toast.LENGTH_LONG).show();
}
}
if (ep1.getDirection() == UsbConstants.USB_DIR_OUT)//nope
{
mEndpointWrite = ep1;
if (DEBUG == 6)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "EP1 is a write", Toast.LENGTH_LONG).show();
}
}
}
if (ep2.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)
{
if (ep2.getDirection() == UsbConstants.USB_DIR_IN)
{
//Try treating it as a write anyway
//mEndpointRead = ep2;
mEndpointWrite = ep2;
}
else if (ep2.getDirection() == UsbConstants.USB_DIR_OUT)
{
//usbEndpointWrite = ep2;
mEndpointWrite = ep2;
}
}
//check that we should be able to read and write
if ((mEndpointRead == null) || (mEndpointWrite == null))
{
return false;
}
if (device != null)
{
UsbDeviceConnection connection = mUsbManager.openDevice(device);
if (connection != null && connection.claimInterface(usbInterfaceRead, true))
{
Log.d(TAG, "open SUCCESS");
mConnectionRead = connection;
// Start the read thread
//Comment out while desperately attempting to write on this connection/interface
//Thread thread = new Thread(this);
//thread.start();
}
else
{
Log.d(TAG, "open FAIL");
mConnectionRead = null;
}
}
if (UsingSingleInterface)
{
mConnectionWrite = mConnectionRead;
}
else //! UsingSingleInterface
{
mConnectionWrite = mUsbManager.openDevice(device);
mConnectionWrite.claimInterface(usbInterfaceWrite, true);
}
return true;
}
// searches for an interface on the given USB device
private UsbInterface findInterface(UsbDevice device) {
Log.d(TAG, "findInterface " + device);
int count = device.getInterfaceCount();
if (DEBUG == 7)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Interface count: " + count, Toast.LENGTH_LONG).show();
}
for (int i = 0; i < count; i++) {
UsbInterface intf = device.getInterface(i);
String InterfaceInfo = intf.toString();
Log.d(TAG, "Interface: " + InterfaceInfo);
//Class below is 3 for USB_HID
if (intf.getInterfaceClass() == 3 && intf.getInterfaceSubclass() == 0 &&
intf.getInterfaceProtocol() == 0) {
return intf;
}
//....try just returning the interface regardless of class/subclass
//return intf;
}
return null;
}
private boolean sendControlTransfer(byte[] dataToSend)
{
synchronized (this)
{
if (mConnectionRead != null)
{
//byte[] message = new byte[13]; // or 14?
byte[] message = dataToSend;
if (DEBUG == 9)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Sending Control Transfer", Toast.LENGTH_LONG).show();
}
//first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b)
//To set direction as 'host to Device' we need 0, To set type to HID we need 11 (3), and for recipient we want 00001
//second field 0x09 is class specific request code, 0x09 is listed as 'reserved for future use'
//third field 0x200 is value
//int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0);
//try with type set to HID
int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0);
if (DEBUG == 10)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Transfer returned " + transfer, Toast.LENGTH_LONG).show();
}
}
}
return true;
}
private boolean sendInterruptTransfer(byte[] dataToSend)
{
int bufferDataLength = mEndpointWrite.getMaxPacketSize();//The write endpoint is null unless we just copy the read endpoint
if (DEBUG == 12)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Max Packet Size: " + bufferDataLength, Toast.LENGTH_LONG).show();
}
ByteBuffer buffer = ByteBuffer.allocate(bufferDataLength + 1);
UsbRequest request = new UsbRequest();
buffer.put(dataToSend);
request.initialize(mConnectionWrite, mEndpointWrite);
request.queue(buffer, bufferDataLength);
try
{
/* only use requestwait on a read
if (request.equals(mConnectionWrite.requestWait()))
{
return true;
}
*/
}
catch (Exception ex)
{
// An exception has occurred
if (DEBUG == 13)
{
Toast.makeText(UsbHidDeviceTesterActivity.this, "Caught Write Exception", Toast.LENGTH_LONG).show();
}
}
return true;
}
So, I have been researching similar things. I cannot confirm, but what I believe is happening is:
Android does not list the control endpoint when it enumerates it's endpoints. It only lists other endpoints.
A connection to any endpoint can send control transfers to endpoint 0, through the controlTransfer method, which (quoting from the api) "Performs a control transaction on endpoint zero for this device."
So, in your above code, I would use the 0th endpoint as an interrupt input endpoint, but it will still allow for control transfers.
An example of someone using a HID device is the Missle Launcher demo, the device it uses is a HID device with an interrupt endpoint.
You can get a full list of the details of interfaces and endpoint by using the following:
UsbManager mManager = (UsbManager) getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = mManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext())
{
UsbDevice device = deviceIterator.next();
Log.i(TAG,"Model: " + device.getDeviceName());
Log.i(TAG,"ID: " + device.getDeviceId());
Log.i(TAG,"Class: " + device.getDeviceClass());
Log.i(TAG,"Protocol: " + device.getDeviceProtocol());
Log.i(TAG,"Vendor ID " + device.getVendorId());
Log.i(TAG,"Product ID: " + device.getProductId());
Log.i(TAG,"Interface count: " + device.getInterfaceCount());
Log.i(TAG,"---------------------------------------");
// Get interface details
for (int index = 0; index < device.getInterfaceCount(); index++)
{
UsbInterface mUsbInterface = device.getInterface(index);
Log.i(TAG," ***** *****");
Log.i(TAG," Interface index: " + index);
Log.i(TAG," Interface ID: " + mUsbInterface.getId());
Log.i(TAG," Inteface class: " + mUsbInterface.getInterfaceClass());
Log.i(TAG," Interface protocol: " + mUsbInterface.getInterfaceProtocol());
Log.i(TAG," Endpoint count: " + mUsbInterface.getEndpointCount());
// Get endpoint details
for (int epi = 0; epi < mUsbInterface.getEndpointCount(); epi++)
{
UsbEndpoint mEndpoint = mUsbInterface.getEndpoint(epi);
Log.i(TAG," ++++ ++++ ++++");
Log.i(TAG," Endpoint index: " + epi);
Log.i(TAG," Attributes: " + mEndpoint.getAttributes());
Log.i(TAG," Direction: " + mEndpoint.getDirection());
Log.i(TAG," Number: " + mEndpoint.getEndpointNumber());
Log.i(TAG," Interval: " + mEndpoint.getInterval());
Log.i(TAG," Packet size: " + mEndpoint.getMaxPacketSize());
Log.i(TAG," Type: " + mEndpoint.getType());
}
}
}
Log.i(TAG," No more devices connected.");
}
Control transfer doesn't show any interface descriptor and its endpoint number is 0 by default, for both in and out transfer.
if you have other interfaces the index of those interfaces should start from 0 i.e. default control transfer interface does not count.
So your interface 0 holds the endpoint 1 descriptor. use the UsbEndpoint methods to find the attributes of the endpoint whether it is interrupt type or not. if it is then endpoint type by UsbEndpoint.getType() should return 0x03 and endpoint number by UsbEndpoint.getEndpointNumber() should return 0x81 which is usual value for endpoint 1.
below your code is wrong:
//first field ox21 is bin 00100001 which splits into 0 01 00001 for direction(1bit)/type(2b)/recipient(5b)
//To set direction as 'host to Device' we need 0, **To set type to HID we need 11 (3)**, and for recipient we want 00001
//second field 0x09 is class specific request code, **0x09 is listed as 'reserved for future use'**
//**third field 0x200 is value**
//int transfer = mConnectionRead.controlTransfer(0x21, 0x9, 0x200, 0, message, message.length, 0);
//try with type set to HID
int transfer = mConnectionRead.controlTransfer(0xC1, 0x9, 0x200, 0, message, message.length, 0);
Type 2 bits is used to indicate class specific request, i.e. its value is 01,
0x09 is Hid class specific request SET_REPORT, not reserved.
value is the wValue which is used as Report ID for Hid class, for your case it is probably 0, if you have only one report at you HID descriptor.
and the 4 th parameter is wIndex which should be used to indicate the recipient, for your case it should be 0x01 for interface as recipient.
So your code for control transfer for Read or receive data form device should be:
int transfer = mConnectionRead.controlTransfer(0xA1, 0x01, 0x00, 0x01, message, message.length, 0);
where 0x01 in second parameter is GET_REPORT is Hid calls specific request.
And your code for control transfer for Write or send data to device should be:
int transfer = mConnectionWrite.controlTransfer(0x21, 0x09, 0x00, 0x01, message, message.length, 0);
Since you only have Interrupt IN endpoint 1, Bulk or Interrupt transfer should be like:
int transfer = bulkTransfer (ep1, message, message.length, 0);
to have the interrupt Out endpoint there should be a endpoint descriptor for that at the interface descriptor of firmware of your device.
Maybe it is a late answer or off topic. However, I hope someone will one day find this answer useful.
Github now contains great Android libraries to communicate with custom HID devices:
Mine in Kotlin
in Java
The good thing is that if you are lucky, then you only need to know the device VID:PID and the commands it accepts. You don't need to worry about USB protocol or communication details.
If interested about how USB works, then you can have a closer look at library source code.