Developer Guides
Included with Warsha BLE Studio
Open Bluetooth Low Energy Protocols — A Reference for Hardware Emulation
Per-protocol specifications, data formats, control flows, and real-world usage. Each protocol has its own section with a consistent structure: Purpose, Where it is used, GATT structure, Data formats, How to use it, and Emulation notes. All UUIDs and bit-level layouts were validated against the Bluetooth SIG specifications, Apple and Google developer documentation, and vendor SDKs (Nordic, Silicon Labs, Espressif).
Conventions used throughout
- UUIDs are 16-bit SIG short forms (e.g.,
0x180D) unless written as a full 128-bit UUID. - Characteristic fields are little-endian unless stated otherwise (beacon UUID/major/minor are the big-endian exception).
- “Flags-prefixed” means the packet starts with a 1–2 octet flags field whose bits declare which optional fields follow; a 0 bit omits that field entirely.
- Fixed-point values carry an implied resolution: multiply the physical value by the inverse resolution before transmitting.
Table of contents
- Part I — Foundations
- Part II — Connected GATT protocols
- Heart Rate Service (0x180D)
- Fitness Machine Service (0x1826)
- Cycling Power Service (0x1818)
- Cycling Speed and Cadence Service (0x1816)
- Running Speed and Cadence Service (0x1814)
- Environmental Sensing Service (0x181A)
- Automation IO Service (0x1815)
- HID over GATT Profile (0x1812)
- Apple MIDI over BLE (BLE-MIDI)
- Nordic UART Service (NUS)
- Battery Service (0x180F)
- Device Information Service (0x180A)
- Current Time Service (0x1805)
- Medical and Wellness services (cluster)
- Part III — Connectionless beacon protocols
- Part IV — Emulation and validation
- Appendix A — Architectural comparison matrix
- Appendix B — Master UUID quick reference
- Appendix C — Revisions
- Appendix D — Authoritative references
Part I — Foundations
BLE data layers, UUIDs, and pairing
This section establishes vocabulary the rest of the document relies on.
GAP and GATT
BLE divides activity into two paradigms. The Generic Access Profile (GAP) governs connectionless behavior: advertising, scanning, discovery, and connection establishment. The Generic Attribute Profile (GATT) governs the connected state, where data is a client–server hierarchy of Services, Characteristics, and Descriptors. Beacon protocols (Part III) live entirely in GAP and never open a connection; every other protocol here is GATT.
The attribute hierarchy
A 16-bit UUID is shorthand for a value inside the SIG base UUID
0000xxxx-0000-1000-8000-00805F9B34FB. A Service groups Characteristics; a Characteristic holds a
value plus properties (Read, Write, Write Without Response, Notify, Indicate); a Descriptor attaches
metadata to a Characteristic.
GATT Server
|-- Service (e.g., Heart Rate 0x180D)
| |-- Characteristic (value + properties)
| | |-- Value (the bytes on the wire)
| | '-- Descriptor (metadata; e.g., CCCD 0x2902)
| '-- Characteristic ...
'-- Service ...
The most important descriptor is the Client Characteristic Configuration Descriptor (CCCD,
0x2902). A client writes 0x0001 to enable notifications or 0x0002 to enable indications;
0x0000 disables both.
- Notification — unacknowledged (fast, lossy). Used by streaming sensors.
- Indication — acknowledged at the ATT layer (slower, reliable). Used by control points.
The MTU
Payload sizing is bounded by the ATT_MTU. The default is 23 octets, leaving 20 octets of usable payload (3 are ATT overhead). Stacks can negotiate larger MTUs (185, 247, 512), but many do not. Several characteristics here are designed to fit the 23-octet default (Heart Rate RR-intervals, FTMS “More Data” segmentation). When emulating, never assume a large MTU is available.
Common data encodings
- Endianness — little-endian unless a spec says otherwise.
- Flag-prefixed packets — a 0 flag bit omits the corresponding field entirely.
- Fixed-point scaling — integers carry an implied resolution (e.g., 0.01 °C).
- IEEE-11073 medical floats — SFLOAT (16-bit: 4-bit signed exponent + 12-bit signed mantissa) and FLOAT (32-bit: 8-bit signed exponent + 24-bit signed mantissa), not IEEE-754. See the Medical section.
Pairing, bonding, and security
Some profiles (HID over GATT, most medical services) require an encrypted, authenticated link via the Security Manager Protocol (SMP) using one of these association models:
- Just Works — no user verification; encrypts the link but gives no MITM protection.
- Passkey Entry — a 6-digit number shown on one device, typed on the other; resists MITM.
- Numeric Comparison (LE Secure Connections) — both devices show a 6-digit value the user confirms; resists MITM.
After pairing, bonding stores the long-term keys so the encrypted link re-establishes automatically. If a client accesses a security-restricted characteristic without being bonded, the server returns an ATT error (Insufficient Authentication / Insufficient Encryption), which the host turns into a pairing prompt.
Generic Access and Generic Attribute services (0x1800, 0x1801)
Purpose. Every GATT device exposes two mandatory services that shape how the OS discovers, names,
and categorizes the device. Generic Access (0x1800) holds identity and appearance; Generic Attribute
(0x1801) lets the device tell clients its attribute table changed.
Where it is used. Present on literally every BLE peripheral. They make an emulated device show up with the right name and icon, and keep a bonded client’s cached view of the GATT table correct.
GATT structure — Generic Access (0x1800).
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Device Name | 0x2A00 | Read (Write opt.) | Name shown in scan lists and OS menus. |
| Appearance | 0x2A01 | Read | 16-bit category used to pick an icon / device class. |
| Peripheral Preferred Connection Parameters | 0x2A04 | Read (opt.) | Suggested interval / latency / timeout. |
| Central Address Resolution | 0x2AA6 | Read (opt.) | Whether the device supports privacy address resolution. |
GATT structure — Generic Attribute (0x1801).
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Service Changed | 0x2A05 | Indicate | Tells bonded clients the table changed; re-discover. |
| Client Supported Features | 0x2B29 | Read, Write | Client opts in to enhanced ATT features. |
| Database Hash | 0x2B2A | Read | Lets a client detect table changes without Service Changed. |
Data formats. Device Name is UTF-8. Appearance is a structured 16-bit value (10-bit category +
6-bit subcategory) that can also be broadcast in advertising (AD type 0x19). Well-known values:
- Generic HID
0x03C0; Keyboard0x03C1; Mouse0x03C2; Gamepad0x03C4. - Heart Rate Sensor
0x0340; Generic Cycling0x0480(Speed0x0482, Cadence0x0483, Power0x0484). - Generic Watch
0x00C0; Thermometer0x0300; Blood Pressure0x0380. Full list in Assigned Numbers.
How to use it.
- During discovery the client reads Device Name and Appearance; with the advertised service UUIDs, these let the OS choose an icon, category, and pairing UX.
- Set Peripheral Preferred Connection Parameters if the device has timing needs (HID, MIDI).
- If a bonded client cached your GATT table and it later changes, indicate Service Changed (
0x2A05) so the client re-discovers instead of using stale handles.
Emulation notes. Most stacks create these services automatically — populate sensible values. Set a clear Device Name and an Appearance matching what you emulate (e.g., keyboard appearance for HID). Service Changed only matters if your GATT layout changes for clients that bond and cache it.
Part II — Connected GATT protocols
The fitness family (Heart Rate, Fitness Machine, Cycling Power, Cycling Speed and Cadence, Running Speed and Cadence) shares the flag-prefixed packet pattern and is presented first.
Heart Rate Service (0x180D)
Purpose. Reports heart rate, optionally with energy expenditure and beat-to-beat (RR) intervals. The most universally supported open fitness service and the canonical first emulator target.
Where it is used.
- Chest straps / arm bands: Polar H10/H9, Wahoo TICKR, Garmin HRM-Dual, Scosche Rhythm.
- Watches broadcasting HR to gym equipment or apps (Apple Watch, Garmin, Wear OS).
- Consumers: Zwift, Peloton, Strava-linked apps, treadmill consoles.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Heart Rate Measurement | 0x2A37 | Notify | BPM + optional fields; primary stream. Carries CCCD 0x2902. |
| Body Sensor Location | 0x2A38 | Read | 0 Other, 1 Chest, 2 Wrist, 3 Finger, 4 Hand, 5 Ear Lobe, 6 Foot. |
| Heart Rate Control Point | 0x2A39 | Write | 0x01 = Reset Energy Expended. |
Data formats. 1-octet flags field then conditional fields:
- Bit 0 — Value format: 0 = BPM is
uint8, 1 = BPM isuint16. - Bits 1–2 — Sensor Contact: bit 2 = feature supported, bit 1 = skin contact detected.
- Bit 3 — Energy Expended present:
uint16in kilojoules follows the BPM. - Bit 4 — RR-Interval present: one or more
uint16values, units of 1/1024 s.
Minimal notification (uint8 BPM = 75, no options):
Byte 0 (Flags): 0x00 -> uint8 BPM, no contact/energy/RR
Byte 1 (BPM): 0x4B -> 75 bpm
With RR-intervals (flags 0x10), BPM 60:
Byte 0: 0x10 -> RR present
Byte 1: 0x3C -> 60 bpm
Byte 2-3: 0x00 0x04 -> RR = 1024/1024 s = 1.000 s
Byte 4-5: 0xE8 0x03 -> RR = 1000/1024 s = 0.977 s
RR values are variable-length; the client derives the count from the remaining packet length. At the 23-octet default MTU, up to 8–9 RR values fit per notification; overflow goes in the next notification.
How to use it.
- Discover services; find Heart Rate (
0x180D). - Read Body Sensor Location (
0x2A38) if present. - Write
0x0001to the Measurement CCCD (0x2902) to enable notifications. - Receive notifications (~1 Hz) and parse per the flags byte.
- To reset accumulated energy, write
0x01to the Control Point (0x2A39).
Emulation notes. Trivial: advertise 0x180D, expose the three characteristics, notify a
flags+BPM pair on a timer. Start with flags 0x00 and a uint8 BPM for broadest compatibility.
Fitness Machine Service (0x1826)
Purpose. Two-way communication between fitness equipment (GATT Server) and training apps (GATT Client): streams real-time metrics and accepts control commands (target power, resistance, simulated road grade). The protocol that makes a trainer respond to a virtual hill.
Where it is used.
- Smart trainers: Wahoo KICKR, Tacx Neo, Elite, Saris.
- Treadmills, rowers, ellipticals, indoor bikes with a “smart” Bluetooth mode.
- Apps: Zwift, TrainerRoad, Rouvy, Kinomap, MyWhoosh.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Fitness Machine Feature | 0x2ACC | Read | Bitfield: supported metrics and target/control capabilities. |
| Treadmill Data | 0x2ACD | Notify | Speed, incline, distance, pace. |
| Rower Data | 0x2AD1 | Notify | Stroke rate/count, pace, power. |
| Indoor Bike Data | 0x2AD2 | Notify | Speed, cadence, power, distance, energy, HR. |
| Fitness Machine Control Point | 0x2AD9 | Write, Indicate | Control opcodes; replies with an indication. |
| Fitness Machine Status | 0x2ADA | Notify | State changes and control-action results. |
Treadmill / Rower / Indoor Bike data are alternatives — a device exposes the one matching its equipment type. Simpler trainers may use the Cycling Power Service instead of, or alongside, FTMS.
Data formats — Indoor Bike Data (0x2AD2). 16-bit flags field; bit 0 (“More Data”) is inverted:
when 0, Instantaneous Speed is present.
Indoor Bike Data flags (octet 0-1, little-endian):
Bit 0 More Data (0 => Instantaneous Speed present)
Bit 1 Average Speed present
Bit 2 Instantaneous Cadence present
Bit 3 Average Cadence present
Bit 4 Total Distance present
Bit 5 Resistance Level present
Bit 6 Instantaneous Power present
Bit 7 Average Power present
Bit 8 Expended Energy present
Example payload (speed + cadence + power):
Byte 0-1 Flags: 0x44 0x00 -> bits 2 and 6 set; speed present (bit 0 = 0)
Byte 2-3 Speed: uint16, 0.01 km/h (3000 -> 30.00 km/h)
Byte 4-5 Cadence: uint16, 0.5 1/min (180 -> 90.0 RPM)
Byte 6-7 Power: sint16, 1 W (250 -> 250 W)
Known quirk. The legacy 2017 XML cross-referenced the definitions of Bit 1 (Average Speed) and Bit 2 (Instantaneous Cadence), so some early devices invert them. Keep a parser fallback.
Control Point opcodes (0x2AD9).
| Opcode | Command | Parameters | Effect |
|---|---|---|---|
0x00 | Request Control | none | Acquire exclusive control of the resistance loop. |
0x01 | Reset | none | Clear targets, stop the control loop. |
0x04 | Set Target Resistance | uint8 (0–100%) | Fixed brake resistance level. |
0x05 | Set Target Power | sint16 (1 W) | ERG mode: hold a wattage regardless of cadence. |
0x07 | Start or Resume | none | Begin/resume the session. |
0x08 | Stop or Pause | uint8 (1=stop, 2=pause) | End or pause the session. |
0x11 | Set Indoor Bike Simulation | sint16 Wind Speed (0.001 m/s), sint16 Grade (0.01%), uint8 C_rr (0.0001), uint8 Cw (0.01 kg/m) | SIM mode: physics-driven resistance. |
Correction: opcode
0x11has no “Drafting Factor” field; drafting is a proprietary app-layer concept (e.g., Zwift), not part of FTMS.
Simulated road-load model (SIM mode).
F_target = m*g*sin(theta) (grade / gravity)
+ m*g*C_rr*cos(theta) (rolling resistance)
+ 0.5*rho*(Cw*A)*(v + v_w)^2 (aerodynamic drag)
m=total mass g=9.80665 theta=grade(rad) C_rr=rolling coeff
Cw*A=drag area rho=air density v=velocity v_w=wind speed
How to use it. The Control Point is transactional: no Read, indications required before use, and it
replies to every write with 0x80 <reqOpcode> <result> (0x01 = Success).
Client (App) Server (Trainer)
|---- Enable Indications (CCCD 0x2902) ---->|
|---- 0x00 Request Control -------------->|
|<--- Indicate 0x80 0x00 0x01 (Success) ---|
|---- 0x07 Start ------------------------>|
|<--- Indicate 0x80 0x07 0x01 ------------|
|---- 0x05 Set Target Power [250 W] ----->|
|<--- Indicate 0x80 0x05 0x01 ------------|
Emulation notes. Medium–high complexity, mostly the Control Point state machine: gate writes behind
Request Control, return the right indication per opcode, reject unsupported opcodes with result 0x02
(Op Code Not Supported) or 0x05 (Control Not Permitted). Expose a realistic Feature bitfield — apps
read it to decide which controls to offer.
Cycling Power Service (0x1818)
Purpose. Reports instantaneous power and rich pedaling metrics (balance, torque, crank/wheel events). Predates FTMS for power reporting; standard for dedicated power meters and head units.
Where it is used.
- Power meters: Stages, Quarq, Favero Assioma, Garmin Rally, 4iiii.
- Head units / watches: Garmin Edge, Wahoo ELEMNT, Hammerhead.
- Smart trainers exposing CPS for power broadcast alongside FTMS.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Cycling Power Measurement | 0x2A63 | Notify | Power + optional balance/torque/revolution fields. |
| Cycling Power Feature | 0x2A65 | Read | Bitfield of supported features. |
| Sensor Location | 0x2A5D | Read | Crank, pedal, hub, shoe, etc. |
| Cycling Power Control Point | 0x2A66 | Write, Indicate | Calibration, crank length, offset compensation. |
| Cycling Power Vector | 0x2A64 | Notify | Optional per-crank-angle force/torque arrays. |
Data formats — Cycling Power Measurement (0x2A63). 16-bit flags, then mandatory sint16
Instantaneous Power (watts), then optional fields:
CPM flags (octet 0-1):
Bit 0 Pedal Power Balance present Bit 7 Extreme Torques present
Bit 1 Pedal Power Balance reference Bit 8 Extreme Angles present
Bit 2 Accumulated Torque present Bit 9 Top Dead Spot present
Bit 3 Accumulated Torque source Bit 10 Bottom Dead Spot present
Bit 4 Wheel Revolution Data present Bit 11 Accumulated Energy present
Bit 5 Crank Revolution Data present Bit 12 Offset Compensation Indicator
Bit 6 Extreme Forces present
Mandatory: Flags (uint16) + Instantaneous Power (sint16, W)
Crank Revolution Data (if bit 5): Cumulative Crank Revs (uint16)
+ Last Crank Event Time (uint16, 1/1024 s)
Cadence and speed are derived from cumulative revolution counts differentiated against event timestamps. CPS uses 1/2048 s resolution for the wheel event time (finer than CSC’s 1/1024 s).
How to use it. Stream via the Part I subscribe flow on 0x2A63. The Control Point (0x2A66)
mirrors FTMS’s transactional pattern: enable indications, write an opcode, await 0x20 <opcode> <result> (0x20 is the SIG Response Code op code for this control point — decimal 32).
Emulation notes. Medium complexity. Timestamp-resolution gotcha: if you implement speed/cadence in both CPS and CSC, use 1/2048 s for CPS and 1/1024 s for CSC; mixing them causes some apps (e.g., Wahoo) to report doubled or unstable values. Some Garmin watches require CSC for speed and will not read it from CPS, so a maximally compatible sensor implements both.
Cycling Speed and Cadence Service (0x1816)
Purpose. Speed and cadence only — no power. Cheaper and lower-power than CPS; what most magnet-based bike sensors expose.
Where it is used. Wahoo RPM, Garmin Speed/Cadence, Magene, CooSpo; head units / watches.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| CSC Measurement | 0x2A5B | Notify | Wheel and/or crank revolution data. |
| CSC Feature | 0x2A5C | Read | Which of wheel/crank data is supported. |
| Sensor Location | 0x2A5D | Read | Sensor placement. |
| SC Control Point | 0x2A55 | Write, Indicate | Set cumulative value, calibrate, update location. |
Data formats.
CSC Measurement: 8-bit flags + conditional fields
Bit 0 Wheel Revolution Data present
Bit 1 Crank Revolution Data present
Wheel data: Cumulative Wheel Revolutions (uint32)
+ Last Wheel Event Time (uint16, 1/1024 s)
Crank data: Cumulative Crank Revolutions (uint16)
+ Last Crank Event Time (uint16, 1/1024 s)
Speed = (Δ wheel revolutions × wheel circumference) / Δ time; cadence = Δ crank revolutions / Δ time. The client tracks deltas between notifications and handles 16-bit event-time rollover.
How to use it. Subscribe to 0x2A5B. To reset the odometer or set wheel circumference, enable
indications on the SC Control Point (0x2A55) and write the opcode (e.g., 0x01 Set Cumulative Value).
Emulation notes. Low complexity. Emit monotonically increasing revolution counts with realistic event-time stamps. Re-read the CPS timestamp-resolution gotcha if you implement both on one device.
Running Speed and Cadence Service (0x1814)
Purpose. The running analogue of CSC: instantaneous speed and cadence for footpods and run watches, with optional stride length and total distance.
Where it is used. Stryd, Garmin RD Pod, Polar, COROS pod; smart insoles; basic treadmills.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| RSC Measurement | 0x2A53 | Notify | Speed, cadence, optional stride length / distance. |
| RSC Feature | 0x2A54 | Read | Supported optional fields and walk/run capability. |
| Sensor Location | 0x2A5D | Read | Placement. |
| SC Control Point | 0x2A55 | Write, Indicate | Calibration, set cumulative distance. |
Data formats.
RSC Measurement: 8-bit flags + fields
Bit 0 Instantaneous Stride Length present
Bit 1 Total Distance present
Bit 2 Walking or Running status (0=walking, 1=running)
Mandatory: Flags (uint8)
+ Instantaneous Speed (uint16, 1/256 m/s)
+ Instantaneous Cadence (uint8, steps/min)
Optional: Instantaneous Stride Length (uint16, 1/100 m)
+ Total Distance (uint32, 1/10 m)
How to use it. Subscribe to 0x2A53 and parse per flags. The SC Control Point (0x2A55) shares
the CSC opcode style.
Emulation notes. Low complexity, structurally identical to CSC. Set the walk/run bit honestly so the client applies the right stride model.
Environmental Sensing Service (0x181A)
Purpose. Climate/meteorological readings as scaled fixed-point integers, with descriptors that notify only on meaningful change to save power. A clean test bed for GATT serialization and triggers.
Where it is used. Govee, SwitchBot, Xiaomi/Mijia, Aranet, RuuviTag; DIY weather/air-quality nodes; home-automation hubs.
GATT structure — common characteristics (each Read and optionally Notify; may repeat):
| Characteristic | UUID | Type | Resolution |
|---|---|---|---|
| Temperature | 0x2A6E | sint16 | 0.01 °C |
| Humidity | 0x2A6F | uint16 | 0.01 % |
| Pressure | 0x2A6D | uint32 | 0.1 Pa |
| True Wind Speed | 0x2A70 | uint16 | 0.01 m/s |
| True Wind Direction | 0x2A71 | uint16 | 0.01°, clockwise from north |
| Elevation | 0x2A6C | sint24 | 0.01 m |
Descriptors.
| Descriptor | UUID | Purpose |
|---|---|---|
| CCCD | 0x2902 | Enable notify/indicate. |
| Characteristic Presentation Format | 0x2904 | Units and exponent metadata. |
| ES Measurement | 0x290C | Sampling function, measurement period, update interval. |
| ES Trigger Setting | 0x290D | Condition that causes a notification (delta or interval). |
| ES Configuration | 0x290B | Combine multiple triggers with AND/OR logic. |
| Valid Range | 0x2906 | Lower/upper bounds of the value. |
Data formats. Scale by the inverse resolution, transmit little-endian:
Temperature 23.45 C -> x100 -> 2345 -> int16 -> 0x0929 (LE: 29 09)
Humidity 45.60 % -> x100 -> 4560 -> uint16-> 0x11D0 (LE: D0 11)
void serialize_temperature(float t, uint8_t *buf) {
int16_t v = (int16_t)(t * 100.0f);
buf[0] = v & 0xFF; // little-endian low byte
buf[1] = (v >> 8) & 0xFF; // high byte
}
How to use it. Read on demand, or subscribe via the CCCD. To reduce traffic, configure the ES
Trigger Setting (0x290D) — e.g., “notify only when temperature changes by ≥ 0.5 °C” — optionally
combined with a time bound via ES Configuration.
Emulation notes. Low complexity; a good correctness test bed because scanners auto-decode SIG characteristics, immediately revealing scaling/endianness mistakes. Implement at least one trigger descriptor.
Automation IO Service (0x1815)
Purpose. Raw digital and analog I/O lines (GPIO) over BLE — a wireless terminal strip. Read input pins and drive output pins; a generic bridge for relays, sensors, and simple control endpoints.
Where it is used. Industrial / building automation, PLC-adjacent I/O; smart-home relay boards (some integrate with Home Assistant); evaluation kits from Infineon/Cypress and Nordic.
GATT structure. Three characteristics. At least one of Digital or Analog must be present; both are read and write (handling input and output in one characteristic):
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Digital | 0x2A56 | Read, Write, (Notify) | Packed 2-bit states for n digital signals. |
| Analog | 0x2A58 | Read, Write, (Notify) | A uint16 analog value (one instance per channel). |
| Aggregate | 0x2A5A | Read, (Notify) | All readable Digital + Analog values in one payload. |
Correction. There is no “Digital Output” (
0x2A57) or “Analog Output” (0x2A59) in this service. Those UUIDs are legacy registry entries, not part of AIO; the read/write Digital and Analog characteristics handle both directions.
Data formats — Digital. Each signal packs into 2 bits, four signals per octet, LSB first:
Digital value (n signals), Octet 0:
+------------+------------+------------+------------+
| Pin 3 | Pin 2 | Pin 1 | Pin 0 |
| bits 7-6 | bits 5-4 | bits 3-2 | bits 1-0 |
+------------+------------+------------+------------+
2-bit state values:
00 Inactive (low) 10 Tri-state (high-Z / floating)
01 Active (high) 11 Unknown / invalid
Length = ceil(2n / 8) octets; trailing pad bits = 0.
Max signals = (ATT_MTU - 3) * 4.
n is declared by the mandatory Number of Digitals descriptor (0x2909). Analog is a plain
little-endian uint16. Aggregate concatenates all readable Digital values (byte-aligned) then all
readable Analog values, ordered by Characteristic Presentation Format index.
How to use it.
- Read Number of Digitals (
0x2909) to learn how many digital signals exist. - Read Digital (
0x2A56) or Analog (0x2A58) to sample inputs. - Write to the same characteristic to drive outputs (e.g., write
0x01into a pin’s 2-bit field to set it high). - If present, subscribe to Aggregate (
0x2A5A) for batched updates; when Aggregate is used, the per-characteristic Notify/Indicate is disabled.
Emulation notes. Low complexity. Watch the bit packing and trailing-pad rule. If you expose Aggregate, suppress notifications on the individual characteristics. Use secure pairing if any output controls something safety-relevant.
HID over GATT Profile (0x1812)
Purpose. Carries USB-style HID reports over BLE, so keyboards, mice, gamepads, and custom controls appear to the OS as standard input devices. Mandates an encrypted, bonded link.
Where it is used. Apple Magic Keyboard, Logitech, compact BLE keyboards; Xbox Wireless / DualSense / 8BitDo controllers; assistive input; DIY macropads (ZMK/QMK).
GATT structure. Combines HID with Battery and Device Information services.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| HID Information | 0x2A4A | Read | HID spec version, country code, flags (e.g., remote wake). |
| Report Map | 0x2A4B | Read | USB HID report descriptor: report layout and usages. |
| Report | 0x2A4D | Read, Write, Notify | Input/output/feature reports; repeats per report. |
| HID Control Point | 0x2A4C | Write No Resp | Suspend / exit-suspend signaling. |
| Protocol Mode | 0x2A4E | Read, Write No Resp | Boot vs Report protocol selection. |
| Boot Keyboard Input / Output | 0x2A22 / 0x2A32 | Read/Write/Notify | Fixed-format boot keyboard reports. |
| Boot Mouse Input | 0x2A33 | Read/Notify | Fixed-format boot mouse report. |
Each input Report carries a CCCD (0x2902) plus a Report Reference descriptor (0x2908) binding
it to a report in the Report Map.
Data formats — Report Reference (0x2908). Two octets that disambiguate multiple reports:
Report Reference (2 octets):
Byte 0 Report ID (matches an entry in the Report Map)
Byte 1 Report Type 0x01 Input (device -> host: key/sensor state)
0x02 Output (host -> device: LEDs, rumble)
0x03 Feature(static configuration)
The Report Map is an opaque USB HID descriptor blob declaring usages, button maps, and value ranges. A standard boot-keyboard input report is 8 bytes: modifier byte, reserved, then up to six key codes.
How to use it. The link must be encrypted before reports flow:
- Advertise with the HID appearance and
0x1812so the OS recognizes an input device. - On connection the host reads HID Information and the Report Map, enables notifications on input Report(s) via their CCCDs (
0x2902). - If the host accesses a protected Report before bonding, the server returns Insufficient Authentication; the OS runs pairing (Just Works / Passkey / Numeric Comparison).
- After bonding, the device notifies input reports per event; the host writes Output reports (e.g., keyboard LED state).
Boot Host limitation: a PC BIOS does not load a full BLE stack, so BLE HID boot mode is generally unavailable during early boot (USB is used there).
Emulation notes. High complexity: the Report Map must be a byte-perfect HID descriptor (one wrong usage tag and the OS misinterprets every report), and pairing/bonding is mandatory. Start from a known-good boot-keyboard or boot-mouse descriptor, get one input report notifying correctly, then add Report-protocol reports. Validate against a real OS, not a generic scanner. Pair with the Battery and Device Information services.
Apple MIDI over BLE (BLE-MIDI)
Purpose. Carries MIDI 1.0 messages over BLE with millisecond timestamps so musical timing survives the packet-based, buffered nature of BLE. Apple introduced it (iOS 8 / OS X 10.10); the MMA adopted it as the industry standard.
Where it is used. Roland/Yamaha MD-BT01 & UD-BT01, CME WIDI, Quicco mi.1; controllers/keyboards with built-in BLE-MIDI; DAWs (GarageBand, Logic, Ableton via a bridge); DIY on ESP32/nRF52/RP2040.
GATT structure. One custom service, one bidirectional characteristic.
| Element | UUID | Properties |
|---|---|---|
| MIDI Service | 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 | — |
| MIDI I/O Characteristic | 7772E5DB-3868-4112-A1A9-F2669D106BF3 | Read, Write Without Response, Notify |
Write Without Response for outbound MIDI (low overhead); Notify delivers inbound MIDI.
Data formats. Each packet: a header byte, then timestamp-prefixed MIDI events. The timestamp is 13 bits of milliseconds split across the header and a timestamp byte.
Packet:
+--------------------+--------------------+----------------------------+
| Header byte | Timestamp byte | MIDI message |
| 1 | 0 | tsHigh(6) | 1 | tsLow(7) | [Status][Data1][Data2] |
+--------------------+--------------------+----------------------------+
Header: bit7=1 (start), bit6=0 (reserved), bits5-0 = ts[12:7]
Timestamp: bit7=1 (start), bits6-0 = ts[6:0]
ts = (header_low6 << 7) | ts_low7 ; rolls over every 8192 ms
Multiple events fit in one packet, each preceded by its own timestamp byte. Running Status is supported: a status byte may be omitted when it matches the previous message (2+ byte messages only; not System Common/Real-Time).
Connection interval (timing). Connect at the lowest interval both ends support, with peripheral latency 0. Practical floors are ~11.25 ms on iOS and ~7.5 ms on macOS; a requested 15 ms may be scaled to 30 ms by some Apple devices unless the link qualifies for a tighter interval (e.g., when BLE HID is among connected services).
How to use it.
- Advertise the MIDI service UUID; the OS surfaces the device in its Bluetooth-MIDI settings.
- On connection, negotiate the MTU and the lowest mutually supported interval.
- Subscribe (CCCD) for inbound MIDI.
- To send, build a packet (header byte, then per event a timestamp byte + MIDI bytes) and write Without Response.
- On receipt, parse the header/timestamp pair then the MIDI stream, applying Running Status.
Emulation notes. Medium complexity — the bytes are easy, the timing is hard. Set an aggressive connection interval and always populate timestamps. Validate with a DAW or MIDI monitor (Pocket MIDI, MIDIBerry), not a generic BLE scanner.
Nordic UART Service (NUS)
Purpose. A vendor-defined, de-facto open standard that tunnels a raw byte stream over BLE — a wireless serial port. Carries no semantics; the go-to transport for custom protocols, debug consoles, and prototypes.
Where it is used. Adafruit Bluefruit, BBC micro:bit, Nordic Thingy, countless ESP32 projects; product debug/config channels; bridges exposing a device’s UART protocol to a phone app.
GATT structure. Naming is from the peripheral’s point of view: the central writes to RX and subscribes to TX.
| Element | UUID | Properties |
|---|---|---|
| NUS Service | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E | — |
| RX Characteristic | 6E400002-B5A3-F393-E0A9-E50E24DCCA9E | Write / Write Without Response |
| TX Characteristic | 6E400003-B5A3-F393-E0A9-E50E24DCCA9E | Notify |
Data formats. None — an opaque byte buffer up to ATT_MTU - 3 bytes per packet. Longer messages
are chunked and reassembled by the application. Often treated as a line-oriented text console; some run
a binary framing protocol on top.
How to use it.
- Central enables notifications on TX (
…0003) via its CCCD. - Central writes a chunk to RX (
…0002); the peripheral forwards it to its UART / command parser. - Peripheral emits data as TX notifications.
- Both sides chunk to the negotiated MTU and reassemble; negotiate a larger MTU for throughput.
Emulation notes. Low complexity and a natural fit — route TX/RX to the emulator’s own console. Because it is unstructured, NUS is the easiest way to add a control or scripting backchannel. The 128-bit UUIDs mean a scanner won’t auto-name it.
Battery Service (0x180F)
Purpose. Reports remaining charge as a percentage. One of the most widely implemented services; the OS surfaces it automatically (the battery icon next to a connected device comes from here).
Where it is used. Effectively every wearable, earbud, keyboard, mouse, sensor, remote; a companion to HID and most fitness/medical devices.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Battery Level | 0x2A19 | Read, Notify | uint8 percentage, 0–100. |
A CCCD (0x2902) lets the host be notified on change. Newer revisions add optional Battery Level
Status / Battery Health / Battery Information, but the single uint8 is the near-universal baseline.
Data formats. A single byte. 75% → 0x4B. No scaling, no flags.
How to use it. Read 0x2A19 on demand, or subscribe via the CCCD. Most devices notify only when
the level crosses an integer percentage to avoid waking the host.
Emulation notes. Very low complexity — a single byte on a slow timer. Including it makes an emulated device look complete; HID emulation is expected to pair it with this service.
Device Information Service (0x180A)
Purpose. Read-only metadata identifying the device. Carries no live data; its value is identification, diagnostics, and looking authentic to clients.
Where it is used. On the overwhelming majority of commercial BLE products; read by companion apps for “about this device,” firmware-update gating, and per-model behavior.
GATT structure. All Read-only; all optional, but Manufacturer and Model are the usual minimum.
| Characteristic | UUID | Type |
|---|---|---|
| Manufacturer Name String | 0x2A29 | UTF-8 string |
| Model Number String | 0x2A24 | UTF-8 string |
| Serial Number String | 0x2A25 | UTF-8 string |
| Hardware Revision String | 0x2A27 | UTF-8 string |
| Firmware Revision String | 0x2A26 | UTF-8 string |
| Software Revision String | 0x2A28 | UTF-8 string |
| System ID | 0x2A23 | 8-byte (manufacturer + OUI) |
| PnP ID | 0x2A50 | Vendor ID source, VID, PID, version |
Data formats. Strings are plain UTF-8 with no length prefix (the value length is the attribute
length). PnP ID (0x2A50) is structured (1-byte vendor ID source, uint16 VID, uint16 PID,
uint16 product version) and lets some hosts match a device to a driver or product database.
How to use it. The client reads whichever characteristics it cares about after discovery. No subscription, no control flow.
Emulation notes. Very low complexity. Populate at least Manufacturer and Model; add Firmware Revision if an app gates features by version, and PnP ID for HID emulation.
Current Time Service (0x1805)
Purpose. Exposes, and optionally accepts, wall-clock time — keeps watches, sensors, and displays in sync with a phone. A compact example of a structured (non-flag-prefixed) characteristic.
Where it is used. Smartwatches/bands syncing time; e-paper displays, clocks, data loggers.
GATT structure.
| Characteristic | UUID | Properties | Role |
|---|---|---|---|
| Current Time | 0x2A2B | Read, Notify, (Write) | Date/time, day of week, fractions, adjust reason. |
| Local Time Information | 0x2A0F | Read, (Write) | Time zone and DST offset. |
| Reference Time Information | 0x2A14 | Read | Time source and accuracy. |
Data formats — Current Time (0x2A2B), 10 bytes.
Year uint16 (e.g., 2026) [bytes 0-1, little-endian]
Month uint8 (1-12) [byte 2]
Day uint8 (1-31) [byte 3]
Hours uint8 (0-23) [byte 4]
Minutes uint8 (0-59) [byte 5]
Seconds uint8 (0-59) [byte 6]
Day of Week uint8 (1=Mon .. 7=Sun) [byte 7]
Fractions256 uint8 (1/256 s) [byte 8]
Adjust Reason uint8 (bitfield) [byte 9]
Adjust Reason bits: 0 manual update, 1 external reference,
2 time-zone change, 3 DST change.
How to use it. Read 0x2A2B or subscribe for notifications (the server sets Adjust Reason to say
why it changed). Where writable, a client can push the correct time. Local Time Information (0x2A0F)
conveys time zone (sint8, 15-minute units) and DST offset separately.
Emulation notes. Very low complexity. A good first “structured characteristic” exercise because it has fixed fields rather than a flags header.
Medical and Wellness services (cluster)
Purpose. A family of open SIG health profiles that share FTMS-style flag-prefixed packets but use IEEE-11073 medical floats and usually require pairing.
Where it is used. BP monitors (Omron, Withings), glucometers (Contour Next, Accu-Chek), smart thermometers, pulse oximeters, weight/body-composition scales, CGMs; consumed by Apple Health, Google Fit, and clinical/remote-monitoring apps.
GATT structure.
| Service | UUID | Key characteristics |
|---|---|---|
| Health Thermometer | 0x1809 | Temperature Measurement 0x2A1C (Indicate); Intermediate Temperature 0x2A1E (Notify); Temperature Type 0x2A1D; Measurement Interval 0x2A21 |
| Blood Pressure | 0x1810 | BP Measurement 0x2A35 (Indicate); Intermediate Cuff Pressure 0x2A36 (Notify); BP Feature 0x2A49 |
| Glucose | 0x1808 | Glucose Measurement 0x2A18 (Notify); Context 0x2A34; Feature 0x2A51; Record Access Control Point 0x2A52 (Write/Indicate) |
| Pulse Oximeter | 0x1822 | PLX Spot-Check 0x2A5E (Indicate); PLX Continuous 0x2A5F (Notify); PLX Features 0x2A60 |
| Weight Scale | 0x181D | Weight Measurement 0x2A9D (Indicate); Feature 0x2A9E |
| Body Composition | 0x181B | Body Composition Measurement 0x2A9C (Indicate); Feature 0x2A9B |
| Continuous Glucose Monitoring | 0x181F | CGM Measurement 0x2AA7 (Notify); Feature 0x2AA8; Status 0x2AA9; CGM Specific Ops Control Point 0x2AAC |
Data formats. Two patterns distinguish this cluster:
- IEEE-11073 floats — not IEEE-754. SFLOAT (16-bit) = 4-bit signed exponent + 12-bit signed mantissa; FLOAT (32-bit) = 8-bit signed exponent + 24-bit signed mantissa. Reserved mantissa values encode NaN / ±INFINITY / “not at this resolution.” Blood pressure and glucose use SFLOAT; temperature uses FLOAT.
- Stored records + control points — Glucose and CGM are episodic: readings are stored and retrieved via the Record Access Control Point (
0x2A52/ CGM’s0x2AAC), supporting queries like “report all records” or “records since sequence N.” A request/indicate transaction like FTMS’s control point.
Blood Pressure Measurement (0x2A35):
Flags (uint8): bit0 units (0=mmHg,1=kPa), bit1 timestamp present,
bit2 pulse rate present, bit3 user ID present,
bit4 measurement status present
Systolic SFLOAT
Diastolic SFLOAT
Mean Arterial Pressure SFLOAT
[Timestamp][Pulse Rate SFLOAT][User ID][Status] (per flags)
How to use it.
- Pair/bond first — most require an encrypted link before measurements are exposed.
- Live devices (thermometer, BP, pulse oximeter): subscribe to the measurement characteristic and read indications; “Intermediate” notify characteristics stream provisional values during a reading.
- Episodic devices (glucose, CGM): enable indications on the Record Access Control Point, write a query opcode (e.g., Report Stored Records); the device streams matching measurements as notifications and ends with a response indication.
Emulation notes. Low–medium complexity. The two common mistakes are the IEEE-11073 float encoding (write and unit-test a small SFLOAT/FLOAT encoder) and the record-access transaction for glucose/CGM. Pairing is usually required.
Part III — Connectionless beacon protocols
Beacons never open a GATT connection; they broadcast a fixed payload inside the BLE advertising packet,
which any scanner reads passively. Extremely low-power and trivial to emulate (no server, only
advertising data to cycle). Advertising data is a sequence of length-type-value (LTV) structures; the
AD types that matter are Service Data (0x16) — used by Eddystone — and Manufacturer Specific
Data (0xFF) — used by iBeacon and AltBeacon.
Eddystone (0xFEAA)
Purpose. Google’s open beacon format (Apache 2.0). Defines several frame types — an ID, a URL, telemetry, and an encrypted ephemeral ID — selected by the first byte of its service data.
Where it is used. Asset tracking / proximity (UID), tappable-URL signage (URL), fleet health (TLM), privacy-preserving tracking (EID). OS-level Physical Web scanning is largely retired, but the format is still widely produced and read by apps.
GATT structure. None — advertising-only. Uses Service UUID 0xFEAA and a Service Data (0x16)
field whose first byte is the frame type:
| Frame | Type byte | Payload |
|---|---|---|
| Eddystone-UID | 0x00 | Tx power @0m, 10-byte namespace, 6-byte instance, 2 RFU. |
| Eddystone-URL | 0x10 | Tx power @0m, 1-byte scheme prefix, compressed URL. |
| Eddystone-TLM | 0x20 | Version, battery mV, temperature, adv count, uptime. |
| Eddystone-EID | 0x30 | Tx power @0m, 8-byte AES-rotated ephemeral ID. |
Data formats — URL compression. The scheme and common domains compress to single bytes:
| Prefix byte | Scheme | Domain byte | Expansion |
|---|---|---|---|
0x00 | http://www. | 0x00 / 0x07 | .com/ / .com |
0x01 | https://www. | 0x01 / 0x08 | .org/ / .org |
0x02 | http:// | 0x02 / 0x09 | .edu/ / .edu |
0x03 | https:// | 0x03 / 0x0A | .net/ / .net |
(Domain codes continue: 0x04/0x0B .info, 0x05/0x0C .biz, 0x06/0x0D .gov.)
https://www.zephyrproject.org :
[0x10] [TxPwr] [0x01] z e p h y r p r o j e c t [0x08]
| | |
URL frame https://www. .org
TLM frames carry beacon health: battery voltage in mV (uint16), temperature in 8.8 fixed-point °C,
advertising PDU count since boot (uint32), and time since boot in 0.1 s units (uint32).
How to use it.
- Choose the frame(s); multi-frame beacons rotate frame types across advertising slots/intervals.
- Build the Service Data (
0x16) field:0xFEAAUUID, frame-type byte, then the frame payload. - Advertise non-connectable, undirected. The scanner reads the payload with no connection.
Emulation notes. Very low complexity — no GATT server, only advertising bytes to assemble and rotate. Verify with a beacon-scanner app.
iBeacon
Purpose. Apple’s proximity-beacon format. Broadcasts a single identity — a 128-bit UUID plus 16-bit Major and Minor — and a calibrated Tx power for distance estimation. Deliberately minimal: meaning lives in the app’s database, not the packet.
Where it is used. Retail proximity offers, indoor navigation, museum/venue triggers; deep iOS integration via Core Location region monitoring; vendors like Estimote and Kontakt.io.
GATT structure. None — advertising-only, Manufacturer Specific Data (0xFF) with Apple’s company
ID 0x004C. The format is proprietary (Apple license agreement to use the iBeacon trademark), though
the byte layout is openly documented.
Data formats. Two AD structures — Flags, then Manufacturer Specific Data:
AD #1 Flags: 02 01 06
AD #2 Mfr Data: 1A FF 4C 00 02 15 <16-byte UUID> <Major> <Minor> <TxPwr>
| | |---+ | | | | | |
| | Apple | len=0x15 (21) big- big- signed
| | (LE) | endian endian RSSI@1m
| type 0xFF |
len=0x1A(26) 0x02=proximity beacon
Note the byte-order mix: company ID is little-endian (4C 00), but Major and Minor are
big-endian — a common implementation trap. Tx power is a signed byte equal to the measured RSSI at
1 m (e.g., −59 dBm), compared against live RSSI to estimate distance.
How to use it.
- Assign a UUID for your deployment; use Major to group (e.g., per store) and Minor to identify (e.g., per shelf).
- Measure RSSI at 1 m and store it as the Tx power byte.
- Advertise the two AD structures, non-connectable, at a steady interval.
- The consuming app matches UUID/Major/Minor against its own database to derive meaning.
Emulation notes. Very low complexity. Pitfalls: company-ID endianness vs big-endian Major/Minor,
and the two length bytes (0x1A for the AD, 0x15 for the iBeacon body). Apple-proprietary, so it is
“common” rather than “open.”
AltBeacon
Purpose. Radius Networks’ open, vendor-neutral answer to iBeacon. Same kind of identity, not tied to a single company’s ID, with slightly more user payload.
Where it is used. Cross-platform proximity without Apple’s licensing; the Android Beacon Library ecosystem.
GATT structure. None — advertising-only, Manufacturer Specific Data (0xFF) with a configurable
company code. Body: 1-byte beacon code, 20-byte Beacon ID, 1-byte reference RSSI, 1-byte
manufacturer-reserved.
Data formats.
Mfr Data (0xFF) body:
Company ID 2 bytes (the deployer's own SIG company code)
Beacon Code 2 bytes (0xBEAC)
Beacon ID 20 bytes (org UUID 16 + major 2 + minor 2, deployer-defined)
Reference RSSI 1 byte (signed, measured at 1 m)
Mfg Reserved 1 byte
Compared with iBeacon’s 20 bytes of identity, AltBeacon offers ~25 bytes of usable payload and lets the deployer choose the company code (application-specific rather than company-specific identifiers).
How to use it. Identical workflow to iBeacon: pick the Beacon ID scheme, calibrate the reference
RSSI, advertise non-connectable. Scanners parse the 0xBEAC beacon code to recognize the format.
Emulation notes. Very low complexity — same advertising-bytes exercise as iBeacon with a configurable company code.
Part IV — Emulation and validation
Building and testing a BLE peripheral emulator
Hardware and sandbox. Emulation hardware is usually a low-power microcontroller with an integrated BLE stack — Nordic nRF52840 or Espressif ESP32. A practical bench has three pieces:
+------------------------+ +------------------------+
| Emulated Peripheral | | Testing Host |
| (ESP32 / nRF52840) | --( BLE RF )-->| (Smartphone / PC Host) |
+------------------------+ +------------------------+
^ ^
| Serial UART / CLI | App tools:
+------------------------+ | nRF Connect, LightBlue,
| Developer Terminal | | DAWs, Zwift, OS settings
| (Interactive Console) | |
+------------------------+ +-----------------------
- Developer Terminal — a console over UART (or the Nordic UART Service itself) for live logging and interactive state control.
- Emulated Peripheral — the microcontroller hosting the emulated GATT database (e.g., FTMS + Heart Rate + Battery + Device Information together, to mimic a real trainer).
- Testing Host — a phone or PC running diagnostic and real-world apps.
Validation tooling.
- nRF Connect for Mobile / LightBlue — manual GATT discovery, property/permission checks, CCCD subscribe tests, raw value inspection. SIG characteristics auto-decode, revealing scaling/endianness mistakes.
- Protocol-specific clients — Zwift / TrainerRoad for FTMS/CPS control loops; a DAW or MIDI monitor for BLE-MIDI timing; OS Bluetooth settings for HID and Battery; a beacon scanner for Part III.
- Sniffers — nRF Sniffer or Ellisys/Frontline captures to confirm connection intervals (critical for BLE-MIDI and HID) and on-air byte layouts.
A validation checklist that generalizes.
- Advertising: correct service UUIDs and appearance so the host classifies the device.
- Discovery: services, characteristics, properties, descriptors match the spec (CCCD, Report Reference, Number of Digitals, trigger descriptors).
- Serialization: flags select the right optional fields; values use the right type, resolution, endianness.
- Subscriptions: notifications/indications start on CCCD write and stop on disable.
- Control points: transactional opcodes gated correctly, with the right response indications and error codes (FTMS, CPS, glucose/CGM record access).
- Security: pairing/bonding enforced where required (HID, most medical), with the chosen association model.
- Timing: connection interval and notification cadence within spec for latency-sensitive profiles.
Appendix A — Architectural comparison matrix
| Protocol | UUID / Layer | Data structure | Flow | Ecosystem | Emulation | Security |
|---|---|---|---|---|---|---|
| Heart Rate | 0x180D | Flags + uint8/16 BPM, optional RR | Notify | Universal (watches, straps, gyms) | Low | Optional |
| Fitness Machine | 0x1826 | Packed binary + feature flags | Bidirectional | Zwift, TrainerRoad, Rouvy | Med–High (control point) | Recommended |
| Cycling Power | 0x1818 | Flags + power + crank/wheel events | Bidirectional | Power meters, head units | Medium | Link encryption |
| Cycling Speed/Cadence | 0x1816 | Flags + revolution counts/times | Notify | Magnet sensors, head units | Low | Optional |
| Running Speed/Cadence | 0x1814 | Flags + speed/cadence/stride | Notify | Footpods, run watches | Low | Optional |
| Environmental Sensing | 0x181A | Scaled ints + trigger descriptors | Read / Notify | Sensors, weather nodes | Low | Often open |
| Automation IO | 0x1815 | Packed 2-bit digital; uint16 analog | Bidirectional | PLCs, smart-home | Low | Optional pairing |
| HID over GATT | 0x1812 | USB HID report maps | Bidirectional | Native OS input | High (descriptors) | Mandatory pairing+bonding |
| Apple MIDI | 03B8… / 7772… | Split 13-bit timestamps + MIDI | Bidirectional stream | iOS/macOS/Win/Linux DAWs | Medium (timing) | Simple pairing |
| Nordic UART | custom 6E40… | Opaque byte stream | Bidirectional serial | Ubiquitous in DIY/embedded | Low | Optional |
| Battery | 0x180F | uint8 % | Read / Notify | Near-universal companion | Very low | Usually open |
| Device Information | 0x180A | Read-only strings/IDs | Read | Near-universal companion | Very low | Open |
| Current Time | 0x1805 | Packed date/time + reason | Read / Notify | Watches, displays | Very low | Optional |
| Medical cluster | 0x1809/08/10/22/1D/1F | Flags + IEEE-11073 floats; record access | Indicate / Notify | Health apps, clinical | Low–Med | Usually required |
| Eddystone | 0xFEAA | UID/URL/TLM/EID frames | Broadcast | Scanner apps, asset tracking | Very low | EID = AES rotation |
| iBeacon | Mfr 0x004C | UUID + Major + Minor + TxPwr | Broadcast | Deep iOS Core Location | Very low | None (proprietary fmt) |
| AltBeacon | Mfr (custom) | Beacon ID + ref RSSI | Broadcast | Open, Android-friendly | Very low | None (open fmt) |
Appendix B — Master UUID quick reference
Common descriptors: CCCD 0x2902, Characteristic Presentation Format 0x2904, Report Reference
0x2908, Number of Digitals 0x2909.
| Service | UUID | Principal characteristics (UUID) |
|---|---|---|
| Generic Access | 0x1800 | Device Name 2A00, Appearance 2A01, Preferred Conn Params 2A04 |
| Generic Attribute | 0x1801 | Service Changed 2A05, Client Supported Features 2B29, Database Hash 2B2A |
| Heart Rate | 0x180D | Measurement 2A37, Body Sensor Location 2A38, Control Point 2A39 |
| Fitness Machine | 0x1826 | Feature 2ACC, Treadmill 2ACD, Rower 2AD1, Indoor Bike 2AD2, Control Point 2AD9, Status 2ADA |
| Cycling Power | 0x1818 | Measurement 2A63, Feature 2A65, Location 2A5D, Control Point 2A66, Vector 2A64 |
| Cycling Speed/Cadence | 0x1816 | CSC Measurement 2A5B, Feature 2A5C, Location 2A5D, SC Control Point 2A55 |
| Running Speed/Cadence | 0x1814 | RSC Measurement 2A53, Feature 2A54, Location 2A5D, SC Control Point 2A55 |
| Environmental Sensing | 0x181A | Temperature 2A6E, Humidity 2A6F, Pressure 2A6D, Wind Spd 2A70, Wind Dir 2A71 |
| Automation IO | 0x1815 | Digital 2A56, Analog 2A58, Aggregate 2A5A |
| HID over GATT | 0x1812 | HID Info 2A4A, Report Map 2A4B, Report 2A4D, Control Point 2A4C, Protocol Mode 2A4E |
| Apple MIDI | 03B80E5A-…-C4C700 | MIDI I/O 7772E5DB-…-106BF3 |
| Nordic UART | 6E400001-…-CCA9E | RX 6E400002, TX 6E400003 |
| Battery | 0x180F | Battery Level 2A19 |
| Device Information | 0x180A | Mfr 2A29, Model 2A24, Serial 2A25, FW 2A26, HW 2A27, SW 2A28, System ID 2A23, PnP ID 2A50 |
| Current Time | 0x1805 | Current Time 2A2B, Local Time Info 2A0F, Reference Time Info 2A14 |
| Health Thermometer | 0x1809 | Temp Measurement 2A1C, Intermediate Temp 2A1E, Temp Type 2A1D, Interval 2A21 |
| Blood Pressure | 0x1810 | BP Measurement 2A35, Intermediate Cuff 2A36, Feature 2A49 |
| Glucose | 0x1808 | Measurement 2A18, Context 2A34, Feature 2A51, RACP 2A52 |
| Pulse Oximeter | 0x1822 | PLX Spot-Check 2A5E, PLX Continuous 2A5F, Features 2A60 |
| Weight Scale | 0x181D | Weight Measurement 2A9D, Feature 2A9E |
| CGM | 0x181F | Measurement 2AA7, Feature 2AA8, Status 2AA9, Ops Control Point 2AAC |
| Eddystone | 0xFEAA | Service Data frames: UID 0x00, URL 0x10, TLM 0x20, EID 0x30 |
Appendix C — Revisions
- Automation IO — three characteristics. Removed the non-existent Digital Output (
0x2A57) and Analog Output (0x2A59); the service is Digital (0x2A56), Analog (0x2A58), Aggregate (0x2A5A), with Digital/Analog read+write for both directions. - FTMS simulation parameters. Replaced the spurious “Drafting Factor” with the real opcode
0x11fields: Wind Speed (sint16), Grade (sint16), Rolling Resistance Coefficient (uint8), Wind Resistance Coefficient (uint8). - ESS resolutions restored: Pressure 0.1 Pa, Temperature 0.01 °C, Humidity 0.01 %, Wind Direction 0.01°.
- Apple MIDI connection interval restored: ~11.25 ms (iOS) / ~7.5 ms (macOS) floors, peripheral latency 0.
- Formulas restored: simulated road-load equation; Digital-characteristic byte-length
ceil(2n/8). - BLE-MIDI timestamp rollover clarified: 13-bit value rolls every 8192 ms.
- Per-protocol expansion: each protocol has a dedicated section with purpose, real-world usage, GATT structure, data formats, control-flow walkthroughs, and emulation notes; added Foundations, a master UUID reference, and full per-characteristic detail for the medical cluster and HID.
- Review pass: added the mandatory Generic Access (
0x1800) and Generic Attribute (0x1801) services — Device Name, Appearance, Service Changed — which determine how an emulated device is named, classified, and re-discovered; independently re-verified UUIDs, flag-field layouts, and resolutions against primary sources.
Appendix D — Authoritative references
- Bluetooth SIG — Assigned Numbers; and the service specifications for Heart Rate, Fitness Machine, Cycling Power, Cycling/Running Speed and Cadence, Environmental Sensing, Automation IO, HID over GATT, Battery, Device Information, Current Time, and the health profiles (Health Thermometer, Blood Pressure, Glucose, Pulse Oximeter, Weight Scale, CGM).
- MMA / Apple — Specification for MIDI over Bluetooth Low Energy (BLE-MIDI) 1.0; Apple Technical Q&A QA1931 (advertising and connection parameters).
- Google — Eddystone protocol specification (Apache 2.0); Apple — iBeacon documentation; Radius Networks — AltBeacon specification.
- Vendor SDK documentation — Nordic Semiconductor (Nordic UART Service, nRF Connect SDK), Silicon Labs, Espressif (ESP-IDF), and the Nordic bluetooth-numbers-database.