Skip to content

Insights

Technical note

BLE GATT for accessory control

GATT is a small, cranky bus with a good costume. Design a control model, not a pile of characteristics that grew in review.

Bluetooth Low Energy’s GATT is how a lot of consumer accessories expose control: a phone writes a characteristic, a speaker or a display or a sensor notifies a status. It looks like a database of key-value pairs. It behaves like a lossy, rate-limited, poorly-debuggable remote procedure layer that also has to share a radio with your update path and your audio or sensor stream.

This note is about using GATT as a control plane for a product you intend to keep shipping. It is firmware-side. We are not designing an antenna, and we are not selling a module. We assume the radio is already on the board and that someone can capture a sniffer trace when it misbehaves.

Central and peripheral with ATT/GATT and a small set of services and characteristics
A central speaks ATT to a peripheral. Services are groups. Characteristics are the verbs and nouns. Keep both lists short.

Services are modules, not a junk drawer

Give each service a job: identify the device, control playback or a setpoint, report status, move a file for OTA. Do not put OTA, UI, and telemetry in one UUID because the first sprint was in a hurry. A phone client and a future second accessory will both have to implement against this list. A short list with stable UUIDs is a gift. A growing list of “temp” characteristics is a tax.

Use adopted services when they match. A battery service that is actually the battery saves everyone a custom UUID. When the product behavior is yours — a mode the SIG did not dream of — a custom service is correct. Document it in one page: UUID, characteristics, types, units, and what a write does if the device is busy.

Characteristics that do one thing

A characteristic should be a value or a command, not both, if you can help it. If you need a command that can fail, give it a status the client can read or a notify that says accepted, rejected, or completed. Silent writes are how app authors invent retries that make everything worse.

Decide types and endianness once. A packed struct that only one engineer can parse is not “efficient.” It is a second protocol. Prefer integers with units in the document, enums with room to grow, and a version field on the service if you will add modes. Breaking a characteristic in the field means a firmware and an app that must ship together. That is allowed. It should be a decision.

Notifications are for values that change when the product changes. Indications are for values you cannot afford to lose without a retry story. Do not notify a 200-byte status at 50 Hz because the UI is animated. The radio and the battery will bill you. If the UI wants a smooth needle, sample on the device and send less.

MTU, pairing, and the boring security

Negotiate an MTU you have measured on the worst phone you claim to support. Then design payloads that survive a smaller MTU anyway. A control point that requires 180 bytes on a 23-byte ATT MTU is a paper design.

Pairing and bonding are product features. If the accessory can spend money, move a motor, or unlock something, treat bonding as required and write the reset story — how a new owner takes the device. If the accessory is a display of public status, say why you did not bond. Do not copy a “just works” setting from a development kit into a product that will live in a house with other radios.

Power and coexistence

Connection interval, slave latency, and advertising duty cycle are the power budget. They are also the latency budget. Write both. A remote that feels dead is a firmware bug even if the coulomb counter is happy. A remote that lasts four hours is a product bug even if the UI is snappy.

If the same radio, or a nearby radio, also carries audio or a high-rate sensor, GATT control must have a priority story. “Best effort” is a story. “Control writes complete in N milliseconds or the firmware nacks” is a design. OTA on the same radio is the extreme case: stop pretending control is real-time while you move a megabyte, or pause the transfer when a control write arrives.

Test like a rude client

Write the happy path. Then write the rude path: connect and drop, write while applying an update, write an out-of-range enum, subscribe twice, never subscribe, send a short write, send a long write, bond and forget the bond. Phone OS versions will do all of these without asking you. A sniffer trace in the repository next to the document will save a week later.

Version the document with the firmware. When a field unit misbehaves, you want to know which GATT contract it thought it had. The principal has shipped BLE GATT control on consumer-audio firmware; that program is prior work, not an ASHNAY product. The public lesson is the contract, not a SKU.

Boundary

We write the GATT server and the firmware around it. We read the module datasheet and the schematic enough to bring the driver and the stack up. We do not design the antenna. We do not certify a radio as a lab. If the module cannot hold a connection in the enclosure you chose, that is a hardware conversation we can join as software people who have seen the traces. It is not a promise that firmware can fix a layout.

ASHNAY SOFTWARE takes this as device firmware. The offer is a control model you can implement against, and source another engineer can extend without adding a seventeenth characteristic named temp2.

All notes