BLE HID Service not showing up - (HID over GATT Profile)

Hi all,

I have been playing around with the BLE APIs in v1.3.0-rc.1 on an Argon, and I am running into some trouble with certain services. I have also tried compiling device-os myself with the ble/fix/v1.3.1-rc.1 changes that have recently been merged into the develop branch

I am trying to implement the HID over GATT Profile spec which defines an HID service, along with the Device Info service and Battery service.

While I haven’t been able to find documentation of this working for anyone using the Particle device-os APIs, there is an example: hid_keyboard.ino using the Adafruit_nRF52_Arduino core libraries and their implementation of the BLE APIs, so I have been using that as my “documentation” (especially because they’re for the nRF52840 which is the same MCU the argon uses.) The aforementioned core/libraries have even been updated recently with “support for the Particle Xenon”. I have not yet tried this, but I suspect that using their bootloader I could flash and run the above hid_keyboard.ino on an Argon by replacing device-os with Adafruit’s Arduino core.

I digress, using hid_keyboard.ino from Adafruit as my guide, I have tried to implement the HID service by replicating the GATT services and characteristics using Particle’s BLE API.

The issue is this: using Nordic’s nRF Connect App I am able to see the device, it is advertising successfully and I can see it advertising the HID service (0x1812) but upon connecting, I am only able to see the battery and device info services, the HID service does not show up. I have read through the HID service XML at Bluetooth SIG GATT Services and I believe I have the mandatory characteristics added, yet I cannot get the HID service to show up. For the battery and device information services, they only show up when I have added their mandatory characteristics, even if I never assign a value to the battery level characteristic or the PnpID characteristic.

Interestingly, iOS always shows the device name in System Settings > Bluetooth as Argon-XXXXXX but MacOS shows the local name that I set, AKHID, and even shows a Keyboard Icon. I don’t know how MacOS determines what icon to display for a device but I am also appending the appearance type 961 which corresponds to BLE_APPEARANCE_HID_KEYBOARD.

This is slightly unrelated, but when I connect in the nRF Connect app I see an additional custom service that starts with UUID prefix 6FA90001 even though I haven’t added this service. Is this service implemented by the device-os? Maybe for mobile setup purposes?

My code is included below, as well as screenshots from the BLE app I am using and a MacOS screenshot. (Keyboard_types.h only contains the HID report map, and I am using the one from Nordic’s own ble_app_hids_keyboard example project.

Can anyone offer some advice for what troubleshooting steps I should try next? I have tried assigning values to the characteristics as well but that made no difference as far as I can tell.

Thanks, have a great day.

#include "Keyboard_types.h"

SYSTEM_MODE(MANUAL);

#define DEBUG_BUILD

BleAdvertisingData advData;

BleUuid hogpService(0x1812);
BleUuid deviceInfoService(0x180A);
BleUuid batteryService(0x180F);

#define BLE_WIRING_DEBUG_ENABLED 1

const uint8_t RESPONSE_HID_INFORMATION[] = {0x11, 0x01, 0x00, 0x03};
const uint8_t keyvalue[] = {0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00};

SerialLogHandler serialLogHandler(LOG_LEVEL_TRACE);


BleCharacteristic controlPointChar("control", (BleCharacteristicProperty::WRITE_WO_RSP), BleUuid(0x2A4C), hogpService);
BleCharacteristic reportMapChar("reportMap", BleCharacteristicProperty::READ, BleUuid(0x2A4B), hogpService);
BleCharacteristic bootKeyboardInputCharacteristic("bootInput", (BleCharacteristicProperty)18U, BleUuid(0x2A22), hogpService);
BleCharacteristic bootKeyboardOutputCharacteristic("bootOutput", (BleCharacteristicProperty)14U, BleUuid(0x2A32), hogpService);

// BleCharacteristic reportChar("report", (BleCharacteristicProperty)18, BleUuid(0x2A4D), hogpService);
BleCharacteristic hidInfoChar("hidinfo", BleCharacteristicProperty::READ, BleUuid(0x2A4A), hogpService);
BleCharacteristic pnpIDCharacteristic("pnp", BleCharacteristicProperty::READ, BleUuid(0x2A50), deviceInfoService);
BleCharacteristic battLevelCharacteristic("batt", BleCharacteristicProperty::READ, BleUuid(0x2A19), batteryService);

system_tick_t begin;

void setup() {

	Serial.begin(9600);
	waitUntil(Serial.isConnected);

	
	BLE.addCharacteristic(battLevelCharacteristic);
	BLE.addCharacteristic(pnpIDCharacteristic);
	BLE.addCharacteristic(hidInfoChar);	
	BLE.addCharacteristic(reportMapChar);
	BLE.addCharacteristic(bootKeyboardInputCharacteristic);
	BLE.addCharacteristic(bootKeyboardOutputCharacteristic);

	// BLE.addCharacteristic(reportChar);
	BLE.addCharacteristic(controlPointChar);
	hidInfoChar.setValue(RESPONSE_HID_INFORMATION, sizeof(RESPONSE_HID_INFORMATION));
	//reportMapChar.setValue(hid_map, sizeof(hid_map));
	
	
	advData.appendLocalName("AKHID");
	uint8_t flagsValue = BLE_SIG_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
	advData.append(BleAdvertisingDataType::FLAGS, &flagsValue, sizeof(flagsValue));
	uint16_t appear = BLE_APPEARANCE_HID_KEYBOARD;
	advData.append(BleAdvertisingDataType::APPEARANCE, reinterpret_cast<const uint8_t*>(&appear), sizeof(uint16_t));
	
	advData.appendServiceUUID(hogpService, true);

	//BLE.setAdvertisingInterval(32);
	//BLE.setPPCP(9, 12, 0, 3000);
	uint8_t *data = advData.data();
	size_t len = advData.length();
	Serial.printf("L: %u, D: ", len);
	for(size_t i = 0; i < len; i++) {

		Serial.printf("%02X", data[i]);
	}
	Serial.println();
	BLE.advertise(&advData);
	begin = millis();

}

void loop() {


	if(BLE.connected()) {

		if ((millis()-begin) > (6*1000)) {

			//protocolChar.setValue((uint8_t)1);
			begin = millis();
			// reportChar.setValue(value, 8);
		}

	} 

}

MacOS Screenshot:

Are you building locally or in the cloud.
I have had issues with services showing up when compiling some code in the cloud but the same code worked when build locally (via Particle Workbench).

I have reported that issue but haven’t heard anything back since.

@ScruffR I am building locally only. Initially I was using workbench and 1.3.0-rc.1, and then I cloned the device-os repo and built/flashed the develop branch using workbench’s local compiler shell and selecting the device-os location using DEVICE_OS_PATH

I have built your code via Workbench and it seems to work

1 Like

Interesting, I will try again today with a different Argon. That looks like the Android nRF Connect app, I’m curious if my problems were just related to the iOS nRF Connect app, I will find something else to test with as well. I’ll let you know what I find out, thanks for testing this!

@ScruffR I switched to another device, this time I am using a xenon instead of an argon but I’m getting the exact same thing. I’m trying to find someone with an Android device that I can use to test with.

I’m thinking maybe iOS and MacOS enforce some strict connection parameters? Not sure why else it wouldn’t be working for me

I also tried both Argon and Xenon and both behave as expected.

Try removing the Battery Characteristic (or any other obvious “unrelated” change) just to make sure you are in fact building the correct project :wink:

I’ll double check when I get home but I definitely am using the code that I pasted in my original post. Did you use what I posted verbatim?

On another note, do you know what that 3rd service is that I am seeing? It has a full 128bit UUID so it’s some sort of custom service but I never implement it. Is it implemented by the device-os? Does it show up for you as well?

Thanks again for helping me test so far!

Yes, I did but I've also seen several instances where members were dealing with an unexplainable issue till they realised they were working on one source file but their build was actually using another.

Don't quote me on that, but I think that is Particle's own setup service.

1 Like

Cool, that makes sense about that custom UUID/service.

I can’t think of much else as to why my HID service won’t show up on my iOS/MacOS devices, although I did find these Connection Parameters for a stable connection from Apple’s docs. I will try making sure my program adheres to (as closely as possible) this spec and I’ll report back if that changes anything.

(Still trying to find an Android device I can use to test with, as that is the only primary difference between your test and my test, I believe)

Ok I think this explains the issue I was having in the Apple Accessory Design Guidelines:

When third party iOS applications discover services on the accessory, the following services are used internally by iOS and are filtered out from the list of discovered services:
● Generic Attribute Profile Service
● Generic Access Profile Service
● Bluetooth Low Energy HID Service
● Apple Notification Center Service

It seems that iOS and MacOS were simply filtering out the GAP and HID services that I am implementing! I wasn't able to get an Android device to test with but I am assuming these services would show up. Thanks for your help troubleshooting @ScruffR

1 Like