Bluetooth & BLE DISCUSSION

How should I lay out GATT services and characteristics for a custom BLE sensor?

Started by kettie GATT profile designBLE characteristics128-bit UUIDCCCD notificationsEnvironmental Sensing service
4 replies 248 views 5 participants
Latest activity · 30 Sep 2026

How should I lay out GATT services and characteristics for a custom BLE sensor?

kettie Bluetooth & BLE Forum
#1

My BLE board measures temperature, humidity and battery voltage, and has two settings the app should be able to change (sample period and an alarm threshold). I understand that data lives in characteristics inside services, but I am unsure how to organise it: one characteristic with a packed struct, or one per value? Standard 16-bit UUIDs or my own 128-bit ones?

I would also like to know how notifications are switched on, because my app reads values fine but never receives updates.

Community replies 4

Re: How should I lay out GATT services and characteristics for a custom BLE sensor?

#2

Think of a service as a group of related values and a characteristic as one value with properties (read, write, notify, indicate). Use the standard services where one exists, because generic apps and phone operating systems already understand them: Battery Service 0x180F with Battery Level 0x2A19 (one byte, 0 to 100 percent), Environmental Sensing 0x181A with Temperature 0x2A6E (signed 16-bit in units of 0.01 °C) and Humidity 0x2A6F (unsigned 16-bit in units of 0.01 percent), and Device Information 0x180A for the firmware version.

Put your two settings in a custom service of your own.

Re: How should I lay out GATT services and characteristics for a custom BLE sensor?

#3

The 16-bit UUIDs are shorthand for entries assigned by the Bluetooth SIG; they expand into the base UUID 0000xxxx-0000-1000-8000-00805F9B34FB. You may not invent 16-bit values for your own services. For a custom service generate a random 128-bit UUID once, and derive the characteristic UUIDs from it by changing a couple of bytes so they are easy to recognise.

One side effect: a 128-bit UUID in the advertising packet costs 18 of the 31 bytes available in legacy advertising (16 for the UUID plus 2 for length and type), and the flags take another 3. That leaves 10 bytes, so the device name usually moves to the scan response.

Re: How should I lay out GATT services and characteristics for a custom BLE sensor?

#4

Notifications are off by default, and the client has to enable them per characteristic. Every characteristic with the notify or indicate property has a Client Characteristic Configuration Descriptor, UUID 0x2902. Writing 0x0001 to it enables notifications and 0x0002 enables indications (little-endian, so the bytes on air are 01 00).

On Android, calling setCharacteristicNotification() alone only tells the local stack to pass the data to your callback; you must also write the descriptor. Forgetting that write is the most common reason an app can read a value but never gets updates. On the peripheral, only send when the descriptor shows that the client has subscribed.

Re: How should I lay out GATT services and characteristics for a custom BLE sensor?

#5

On one characteristic versus several: separate characteristics are easier to inspect in generic tools and let the app subscribe to just what it needs. A packed struct in a single characteristic is more efficient when the values are always sampled together, since one notification carries everything and the readings stay consistent with each other. With the default 20-byte payload, temperature, humidity, battery and a timestamp fit easily.

If you pack, document the layout, use fixed-width integers rather than floats, and keep to the BLE convention of little-endian. Add a version byte so later firmware can change the format without breaking old app versions. For the settings, use write with response and validate the range in firmware.

TEP COMMUNITY