W
Warsha Everything Mobile
Documentation / BLE Studio / Open BLE Protocols Reference
Browse documentation

Developer Guides

Included with Warsha BLE Studio

v0.9.9

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

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

CharacteristicUUIDPropertiesRole
Device Name0x2A00Read (Write opt.)Name shown in scan lists and OS menus.
Appearance0x2A01Read16-bit category used to pick an icon / device class.
Peripheral Preferred Connection Parameters0x2A04Read (opt.)Suggested interval / latency / timeout.
Central Address Resolution0x2AA6Read (opt.)Whether the device supports privacy address resolution.

GATT structure — Generic Attribute (0x1801).

CharacteristicUUIDPropertiesRole
Service Changed0x2A05IndicateTells bonded clients the table changed; re-discover.
Client Supported Features0x2B29Read, WriteClient opts in to enhanced ATT features.
Database Hash0x2B2AReadLets 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; Keyboard 0x03C1; Mouse 0x03C2; Gamepad 0x03C4.
  • Heart Rate Sensor 0x0340; Generic Cycling 0x0480 (Speed 0x0482, Cadence 0x0483, Power 0x0484).
  • Generic Watch 0x00C0; Thermometer 0x0300; Blood Pressure 0x0380. Full list in Assigned Numbers.

How to use it.

  1. 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.
  2. Set Peripheral Preferred Connection Parameters if the device has timing needs (HID, MIDI).
  3. 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.

CharacteristicUUIDPropertiesRole
Heart Rate Measurement0x2A37NotifyBPM + optional fields; primary stream. Carries CCCD 0x2902.
Body Sensor Location0x2A38Read0 Other, 1 Chest, 2 Wrist, 3 Finger, 4 Hand, 5 Ear Lobe, 6 Foot.
Heart Rate Control Point0x2A39Write0x01 = Reset Energy Expended.

Data formats. 1-octet flags field then conditional fields:

  • Bit 0 — Value format: 0 = BPM is uint8, 1 = BPM is uint16.
  • Bits 1–2 — Sensor Contact: bit 2 = feature supported, bit 1 = skin contact detected.
  • Bit 3 — Energy Expended present: uint16 in kilojoules follows the BPM.
  • Bit 4 — RR-Interval present: one or more uint16 values, 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.

  1. Discover services; find Heart Rate (0x180D).
  2. Read Body Sensor Location (0x2A38) if present.
  3. Write 0x0001 to the Measurement CCCD (0x2902) to enable notifications.
  4. Receive notifications (~1 Hz) and parse per the flags byte.
  5. To reset accumulated energy, write 0x01 to 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.

CharacteristicUUIDPropertiesRole
Fitness Machine Feature0x2ACCReadBitfield: supported metrics and target/control capabilities.
Treadmill Data0x2ACDNotifySpeed, incline, distance, pace.
Rower Data0x2AD1NotifyStroke rate/count, pace, power.
Indoor Bike Data0x2AD2NotifySpeed, cadence, power, distance, energy, HR.
Fitness Machine Control Point0x2AD9Write, IndicateControl opcodes; replies with an indication.
Fitness Machine Status0x2ADANotifyState 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).

OpcodeCommandParametersEffect
0x00Request ControlnoneAcquire exclusive control of the resistance loop.
0x01ResetnoneClear targets, stop the control loop.
0x04Set Target Resistanceuint8 (0–100%)Fixed brake resistance level.
0x05Set Target Powersint16 (1 W)ERG mode: hold a wattage regardless of cadence.
0x07Start or ResumenoneBegin/resume the session.
0x08Stop or Pauseuint8 (1=stop, 2=pause)End or pause the session.
0x11Set Indoor Bike Simulationsint16 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 0x11 has 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.

CharacteristicUUIDPropertiesRole
Cycling Power Measurement0x2A63NotifyPower + optional balance/torque/revolution fields.
Cycling Power Feature0x2A65ReadBitfield of supported features.
Sensor Location0x2A5DReadCrank, pedal, hub, shoe, etc.
Cycling Power Control Point0x2A66Write, IndicateCalibration, crank length, offset compensation.
Cycling Power Vector0x2A64NotifyOptional 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.

CharacteristicUUIDPropertiesRole
CSC Measurement0x2A5BNotifyWheel and/or crank revolution data.
CSC Feature0x2A5CReadWhich of wheel/crank data is supported.
Sensor Location0x2A5DReadSensor placement.
SC Control Point0x2A55Write, IndicateSet 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.

CharacteristicUUIDPropertiesRole
RSC Measurement0x2A53NotifySpeed, cadence, optional stride length / distance.
RSC Feature0x2A54ReadSupported optional fields and walk/run capability.
Sensor Location0x2A5DReadPlacement.
SC Control Point0x2A55Write, IndicateCalibration, 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):

CharacteristicUUIDTypeResolution
Temperature0x2A6Esint160.01 °C
Humidity0x2A6Fuint160.01 %
Pressure0x2A6Duint320.1 Pa
True Wind Speed0x2A70uint160.01 m/s
True Wind Direction0x2A71uint160.01°, clockwise from north
Elevation0x2A6Csint240.01 m

Descriptors.

DescriptorUUIDPurpose
CCCD0x2902Enable notify/indicate.
Characteristic Presentation Format0x2904Units and exponent metadata.
ES Measurement0x290CSampling function, measurement period, update interval.
ES Trigger Setting0x290DCondition that causes a notification (delta or interval).
ES Configuration0x290BCombine multiple triggers with AND/OR logic.
Valid Range0x2906Lower/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):

CharacteristicUUIDPropertiesRole
Digital0x2A56Read, Write, (Notify)Packed 2-bit states for n digital signals.
Analog0x2A58Read, Write, (Notify)A uint16 analog value (one instance per channel).
Aggregate0x2A5ARead, (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.

  1. Read Number of Digitals (0x2909) to learn how many digital signals exist.
  2. Read Digital (0x2A56) or Analog (0x2A58) to sample inputs.
  3. Write to the same characteristic to drive outputs (e.g., write 0x01 into a pin’s 2-bit field to set it high).
  4. 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.

CharacteristicUUIDPropertiesRole
HID Information0x2A4AReadHID spec version, country code, flags (e.g., remote wake).
Report Map0x2A4BReadUSB HID report descriptor: report layout and usages.
Report0x2A4DRead, Write, NotifyInput/output/feature reports; repeats per report.
HID Control Point0x2A4CWrite No RespSuspend / exit-suspend signaling.
Protocol Mode0x2A4ERead, Write No RespBoot vs Report protocol selection.
Boot Keyboard Input / Output0x2A22 / 0x2A32Read/Write/NotifyFixed-format boot keyboard reports.
Boot Mouse Input0x2A33Read/NotifyFixed-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:

  1. Advertise with the HID appearance and 0x1812 so the OS recognizes an input device.
  2. On connection the host reads HID Information and the Report Map, enables notifications on input Report(s) via their CCCDs (0x2902).
  3. If the host accesses a protected Report before bonding, the server returns Insufficient Authentication; the OS runs pairing (Just Works / Passkey / Numeric Comparison).
  4. 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.

ElementUUIDProperties
MIDI Service03B80E5A-EDE8-4B33-A751-6CE34EC4C700
MIDI I/O Characteristic7772E5DB-3868-4112-A1A9-F2669D106BF3Read, 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.

  1. Advertise the MIDI service UUID; the OS surfaces the device in its Bluetooth-MIDI settings.
  2. On connection, negotiate the MTU and the lowest mutually supported interval.
  3. Subscribe (CCCD) for inbound MIDI.
  4. To send, build a packet (header byte, then per event a timestamp byte + MIDI bytes) and write Without Response.
  5. 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.

ElementUUIDProperties
NUS Service6E400001-B5A3-F393-E0A9-E50E24DCCA9E
RX Characteristic6E400002-B5A3-F393-E0A9-E50E24DCCA9EWrite / Write Without Response
TX Characteristic6E400003-B5A3-F393-E0A9-E50E24DCCA9ENotify

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.

  1. Central enables notifications on TX (…0003) via its CCCD.
  2. Central writes a chunk to RX (…0002); the peripheral forwards it to its UART / command parser.
  3. Peripheral emits data as TX notifications.
  4. 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.

CharacteristicUUIDPropertiesRole
Battery Level0x2A19Read, Notifyuint8 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.

CharacteristicUUIDType
Manufacturer Name String0x2A29UTF-8 string
Model Number String0x2A24UTF-8 string
Serial Number String0x2A25UTF-8 string
Hardware Revision String0x2A27UTF-8 string
Firmware Revision String0x2A26UTF-8 string
Software Revision String0x2A28UTF-8 string
System ID0x2A238-byte (manufacturer + OUI)
PnP ID0x2A50Vendor 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.

CharacteristicUUIDPropertiesRole
Current Time0x2A2BRead, Notify, (Write)Date/time, day of week, fractions, adjust reason.
Local Time Information0x2A0FRead, (Write)Time zone and DST offset.
Reference Time Information0x2A14ReadTime 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.

ServiceUUIDKey characteristics
Health Thermometer0x1809Temperature Measurement 0x2A1C (Indicate); Intermediate Temperature 0x2A1E (Notify); Temperature Type 0x2A1D; Measurement Interval 0x2A21
Blood Pressure0x1810BP Measurement 0x2A35 (Indicate); Intermediate Cuff Pressure 0x2A36 (Notify); BP Feature 0x2A49
Glucose0x1808Glucose Measurement 0x2A18 (Notify); Context 0x2A34; Feature 0x2A51; Record Access Control Point 0x2A52 (Write/Indicate)
Pulse Oximeter0x1822PLX Spot-Check 0x2A5E (Indicate); PLX Continuous 0x2A5F (Notify); PLX Features 0x2A60
Weight Scale0x181DWeight Measurement 0x2A9D (Indicate); Feature 0x2A9E
Body Composition0x181BBody Composition Measurement 0x2A9C (Indicate); Feature 0x2A9B
Continuous Glucose Monitoring0x181FCGM 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’s 0x2AAC), 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.

  1. Pair/bond first — most require an encrypted link before measurements are exposed.
  2. Live devices (thermometer, BP, pulse oximeter): subscribe to the measurement characteristic and read indications; “Intermediate” notify characteristics stream provisional values during a reading.
  3. 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:

FrameType bytePayload
Eddystone-UID0x00Tx power @0m, 10-byte namespace, 6-byte instance, 2 RFU.
Eddystone-URL0x10Tx power @0m, 1-byte scheme prefix, compressed URL.
Eddystone-TLM0x20Version, battery mV, temperature, adv count, uptime.
Eddystone-EID0x30Tx power @0m, 8-byte AES-rotated ephemeral ID.

Data formats — URL compression. The scheme and common domains compress to single bytes:

Prefix byteSchemeDomain byteExpansion
0x00http://www.0x00 / 0x07.com/ / .com
0x01https://www.0x01 / 0x08.org/ / .org
0x02http://0x02 / 0x09.edu/ / .edu
0x03https://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.

  1. Choose the frame(s); multi-frame beacons rotate frame types across advertising slots/intervals.
  2. Build the Service Data (0x16) field: 0xFEAA UUID, frame-type byte, then the frame payload.
  3. 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.

  1. Assign a UUID for your deployment; use Major to group (e.g., per store) and Minor to identify (e.g., per shelf).
  2. Measure RSSI at 1 m and store it as the Tx power byte.
  3. Advertise the two AD structures, non-connectable, at a steady interval.
  4. 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) |                            |
 +------------------------+                            +-----------------------
  1. Developer Terminal — a console over UART (or the Nordic UART Service itself) for live logging and interactive state control.
  2. Emulated Peripheral — the microcontroller hosting the emulated GATT database (e.g., FTMS + Heart Rate + Battery + Device Information together, to mimic a real trainer).
  3. 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.

  1. Advertising: correct service UUIDs and appearance so the host classifies the device.
  2. Discovery: services, characteristics, properties, descriptors match the spec (CCCD, Report Reference, Number of Digitals, trigger descriptors).
  3. Serialization: flags select the right optional fields; values use the right type, resolution, endianness.
  4. Subscriptions: notifications/indications start on CCCD write and stop on disable.
  5. Control points: transactional opcodes gated correctly, with the right response indications and error codes (FTMS, CPS, glucose/CGM record access).
  6. Security: pairing/bonding enforced where required (HID, most medical), with the chosen association model.
  7. Timing: connection interval and notification cadence within spec for latency-sensitive profiles.

Appendix A — Architectural comparison matrix

ProtocolUUID / LayerData structureFlowEcosystemEmulationSecurity
Heart Rate0x180DFlags + uint8/16 BPM, optional RRNotifyUniversal (watches, straps, gyms)LowOptional
Fitness Machine0x1826Packed binary + feature flagsBidirectionalZwift, TrainerRoad, RouvyMed–High (control point)Recommended
Cycling Power0x1818Flags + power + crank/wheel eventsBidirectionalPower meters, head unitsMediumLink encryption
Cycling Speed/Cadence0x1816Flags + revolution counts/timesNotifyMagnet sensors, head unitsLowOptional
Running Speed/Cadence0x1814Flags + speed/cadence/strideNotifyFootpods, run watchesLowOptional
Environmental Sensing0x181AScaled ints + trigger descriptorsRead / NotifySensors, weather nodesLowOften open
Automation IO0x1815Packed 2-bit digital; uint16 analogBidirectionalPLCs, smart-homeLowOptional pairing
HID over GATT0x1812USB HID report mapsBidirectionalNative OS inputHigh (descriptors)Mandatory pairing+bonding
Apple MIDI03B8… / 7772…Split 13-bit timestamps + MIDIBidirectional streamiOS/macOS/Win/Linux DAWsMedium (timing)Simple pairing
Nordic UARTcustom 6E40…Opaque byte streamBidirectional serialUbiquitous in DIY/embeddedLowOptional
Battery0x180Fuint8 %Read / NotifyNear-universal companionVery lowUsually open
Device Information0x180ARead-only strings/IDsReadNear-universal companionVery lowOpen
Current Time0x1805Packed date/time + reasonRead / NotifyWatches, displaysVery lowOptional
Medical cluster0x1809/08/10/22/1D/1FFlags + IEEE-11073 floats; record accessIndicate / NotifyHealth apps, clinicalLow–MedUsually required
Eddystone0xFEAAUID/URL/TLM/EID framesBroadcastScanner apps, asset trackingVery lowEID = AES rotation
iBeaconMfr 0x004CUUID + Major + Minor + TxPwrBroadcastDeep iOS Core LocationVery lowNone (proprietary fmt)
AltBeaconMfr (custom)Beacon ID + ref RSSIBroadcastOpen, Android-friendlyVery lowNone (open fmt)

Appendix B — Master UUID quick reference

Common descriptors: CCCD 0x2902, Characteristic Presentation Format 0x2904, Report Reference 0x2908, Number of Digitals 0x2909.

ServiceUUIDPrincipal characteristics (UUID)
Generic Access0x1800Device Name 2A00, Appearance 2A01, Preferred Conn Params 2A04
Generic Attribute0x1801Service Changed 2A05, Client Supported Features 2B29, Database Hash 2B2A
Heart Rate0x180DMeasurement 2A37, Body Sensor Location 2A38, Control Point 2A39
Fitness Machine0x1826Feature 2ACC, Treadmill 2ACD, Rower 2AD1, Indoor Bike 2AD2, Control Point 2AD9, Status 2ADA
Cycling Power0x1818Measurement 2A63, Feature 2A65, Location 2A5D, Control Point 2A66, Vector 2A64
Cycling Speed/Cadence0x1816CSC Measurement 2A5B, Feature 2A5C, Location 2A5D, SC Control Point 2A55
Running Speed/Cadence0x1814RSC Measurement 2A53, Feature 2A54, Location 2A5D, SC Control Point 2A55
Environmental Sensing0x181ATemperature 2A6E, Humidity 2A6F, Pressure 2A6D, Wind Spd 2A70, Wind Dir 2A71
Automation IO0x1815Digital 2A56, Analog 2A58, Aggregate 2A5A
HID over GATT0x1812HID Info 2A4A, Report Map 2A4B, Report 2A4D, Control Point 2A4C, Protocol Mode 2A4E
Apple MIDI03B80E5A-…-C4C700MIDI I/O 7772E5DB-…-106BF3
Nordic UART6E400001-…-CCA9ERX 6E400002, TX 6E400003
Battery0x180FBattery Level 2A19
Device Information0x180AMfr 2A29, Model 2A24, Serial 2A25, FW 2A26, HW 2A27, SW 2A28, System ID 2A23, PnP ID 2A50
Current Time0x1805Current Time 2A2B, Local Time Info 2A0F, Reference Time Info 2A14
Health Thermometer0x1809Temp Measurement 2A1C, Intermediate Temp 2A1E, Temp Type 2A1D, Interval 2A21
Blood Pressure0x1810BP Measurement 2A35, Intermediate Cuff 2A36, Feature 2A49
Glucose0x1808Measurement 2A18, Context 2A34, Feature 2A51, RACP 2A52
Pulse Oximeter0x1822PLX Spot-Check 2A5E, PLX Continuous 2A5F, Features 2A60
Weight Scale0x181DWeight Measurement 2A9D, Feature 2A9E
CGM0x181FMeasurement 2AA7, Feature 2AA8, Status 2AA9, Ops Control Point 2AAC
Eddystone0xFEAAService Data frames: UID 0x00, URL 0x10, TLM 0x20, EID 0x30

Appendix C — Revisions

  1. 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.
  2. FTMS simulation parameters. Replaced the spurious “Drafting Factor” with the real opcode 0x11 fields: Wind Speed (sint16), Grade (sint16), Rolling Resistance Coefficient (uint8), Wind Resistance Coefficient (uint8).
  3. ESS resolutions restored: Pressure 0.1 Pa, Temperature 0.01 °C, Humidity 0.01 %, Wind Direction 0.01°.
  4. Apple MIDI connection interval restored: ~11.25 ms (iOS) / ~7.5 ms (macOS) floors, peripheral latency 0.
  5. Formulas restored: simulated road-load equation; Digital-characteristic byte-length ceil(2n/8).
  6. BLE-MIDI timestamp rollover clarified: 13-bit value rolls every 8192 ms.
  7. 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.
  8. 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.