Search code examples
bluetooth-lowenergygatt

BLE GATT Design - Discreet or generic characteristics


I'm working on designing a custom GATT service for a device and I'm contemplating using a more generic approach for our services/characteristics versus the standard discreet services and characteristics for each data point.

In our scenario this device will not need to be used with other servers/vendors so we will only be the company needing to interface with the device. So it seems like abstracting out each discreet data point has less value.

The generic approach would entail a single service with three characteristics:

  • genericCustomService
    • readNotifyCharacteristic - data needing read and/or notify operations
      • input
        • fieldIdentifier
      • output
        • fieldIdentifier
        • fieldDataLength
        • fieldData
    • writeCharacteristic - data needing write operations
      • input
        • fieldIdentifier
        • fieldDataLength
        • fieldData
      • output
        • success or fail

The discreet approach (best practice from what I can tell) would mean multiple services with characteristics grouped logically:

  • customService1
    • characteristic1.1
    • characteristic1.2
    • characteristic1.3
  • customService2
    • characteristic2.1
    • characteristic2.2
    • characteristic2.3
    • characteristic2.4
  • customService3
    • etc...

Implementation seems like it would be easier for the generic approach both on the device BLE server and client.

Thoughts/feedback from real experiences?


Solution

  • 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.