Bluetooth Low Energy: Use Bluez Stack as a Peripheral (With Custom Services and Characteristics)

Bluetooth Low Energy: Use BlueZ stack as a peripheral (with custom services and characteristics)

You can see gatt-example practice, or defined profiles under profile/ directory such as alert/server.c. Basically, you just have to register your service using gatt_service_add() function, following the existing code. For example :

 gatt_service_add(adapter, GATT_PRIM_SVC_UUID, 0xFFFF,
/* Char 1 */
GATT_OPT_CHR_UUID16, 0xAAAA,
GATT_OPT_CHR_PROPS, ATT_CHAR_PROPER_READ,
GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, read_func_callback,

/* Char 2 Define here */
...
/* Char 3 Define here */
...
GATT_OPT_INVALID);
}

Also, I forgot the details but in order to get alert server working, you need to enable experimental (and maintainer mode?) during configuration by adding "--enable-maintainer-mode" and "--enable-experimental"

To run, run the compiled "bluetoothd" with -n and -d options to debug (also -E for enabling experimental services). You may want to reset your adapter again after running bluetoothd. And then you can connect from remote device using gatttool (also with bluetoothd running on remote device).

BlueZ BLE Ecrypted Characteristic Read fails after bonding and connecting

So, after many hours of trial and error, I have been able to reproduce the failure case and successful case consistently.

Failure Case:

  1. Boot up the Pi.
  2. Start the agent, advertisement and gatt server.
  3. Bond the device. Connect it.
  4. Try to read the encrypted characteristic. It fails.

Success Case:

  1. Boot up the Pi.
  2. Restart bluetooth service.
  3. Start the agent, advertisement and gatt server.
  4. Bond the device. Connect it.
  5. Try to read the encrypted characteristic. It succeeds.

So, for the time being, the workaround seems to be restarting the bluetooth service after boot up before starting the agents and advertisements.

Fixing the root cause:
The solution to this problem is given in this Github link.

After much digging, I noticed that this problem is caused by the state
the bluetooth chip is in at the time BlueZ is fired up (you can check
the state with hciconfig hci0). If it's in "UP RUNNING" state or in
"UP RUNNING PSCAN ISCAN" state, BlueZ complains with one or more of
these:

Failed to set mode: Rejected (0x0b) Failed to set mode: Rejected
(0x0b) Failed to set privacy: Rejected (0x0b)

But if it's in "DOWN" state, BlueZ starts with no issues. So, you
first have to do hciconfig hci0 down before the bluetooth service
starts up. But before you can use hciconfig, you also need to ensure
the sys-subsystem-bluetooth-devices-hci0.device service has started!

Solution 1:

I ended up disabling the automatic boot sequence, and run this script instead:

systemctl start sys-subsystem-bluetooth-devices-hci0.device; hciconfig hci0 down; systemctl start bluetooth

Solution 2:

Delaying the running of bthelper by a couple of seconds fixed this issue for me, without me having to disable the automatic boot sequence and run any manual commands.

I added an ExecStartPre line to /lib/systemd/system/bthelper@.service
such that the Service section now looks like this:

[Service]
Type=simple
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/bthelper %I

I tried Solution 2 and it worked.

BLE GATT Design - Discreet or generic characteristics

One good thing with using as few services/characteristics as possible is that Service Discovery time will be much shorter than if you have a large GATT db. The Service Discovery protocol is a very inefficient one, resulting in a huge amount of round-trips. This will directly affect the time it takes to establish a connection. If your devices are bonded though, Service Discovery can fortunately be skipped.

If you only have a simple device, like a heart rate monitor, cycling speed sensor or similar where the GATT philosophy makes sense and it doesn't result in that many characteristics, you are usually fine following this approach.

If you however plan to make use of more advanced communication including tons of features, which might change in firmware upgrades, it might be easier to just have TX/RX characteristics and encode/decode the data yourself with some custom protocol (such as the first byte represents opcode and the rest of the data are parameters). Otherwise if you use the GATT structure and add a characteristic or service and assuming you are using bonding so that the GATT db will be cached in the client, you will most likely need to properly use and implement the Service Changed Indication feature, keep track of which clients you have sent this indication to, and hope that Android or whatever Bluetooth stack runs on the client is bug-free and correctly refreshes the GATT cache and notifies your app when the GATT db was updated (Android does not really do this before Android 12).

Error handling can also be easier with only TX/RX characteristics. What happens in your app if one or a few expected characteristics are missing, but others are present? (I noticed this bug once in a cheap Lenovo tablet) Your onServicesDiscovered method must probably be more advanced to verify that all characteristics are correct before continuing if you have many characteristics/services.

Also note that, you could use L2CAP CoC as well if you just want to send/receive data, which is implemented in both Android >= 10 and iOS.

Use BlueZ Stack As A Peripheral (Advertiser)

With your Bluetooth dongle plugged in, running the following command will tell you the device name and give its state:

$ hciconfig

The output should look something like this:

hci0:    Type: BR/EDR  Bus: USB
BD Address: 00:01:02:aa:bb:cc ACL MTU: 1021:8 SCO MTU: 64:1
DOWN
RX bytes:1000 acl:0 sco:0 events:47 errors:0
TX bytes:1072 acl:0 sco:0 commands:47 errors:0

This indicates the device is called hci0 is in a down state. Issue the following command to bring it up:

$ sudo hciconfig hci0 up

Now it should look like:

$ hciconfig
hci0: Type: BR/EDR Bus: USB
BD Address: 00:01:02:aa:bb:cc ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING
RX bytes:1000 acl:0 sco:0 events:47 errors:0
TX bytes:1072 acl:0 sco:0 commands:47 errors:0

Next, execute the following example command to configure the advertising data to be sent.

$ sudo hcitool -i hci0 cmd 0x08 0x0008 1e 02 01 1a 1a ff 4c 00 02 15 e2 c5 6d b5 df fb 48 d2 b0 60 d0 f5 a7 10 96 e0 00 00 00 00 c5 00 00 00 00 00 00 00 00 00 00 00 00 00

You can change the hex bytes (starting with 1e) to send different byte sequences for your advertisement. One that literally sends the ASCII codes for "HELLO WORLD" would use: 48 45 4c 4c 4f 57 4f 52 4c 44 (EDIT: But you will also have to prefix this message with a valid header, see here.)

Now, use the following command to activate advertising on the dongle, this will start sending "Helo World" packets.

$ sudo hciconfig hci0 leadv 0

EDIT: the above command makes the advertised service connectable. If you don't want to allow connections, change it to $ sudo hciconfig hci0 leadv 3

You can also disable advertising using the following command:

$ sudo hciconfig hci0 noleadv



Related Topics



Leave a reply



Submit