← Developers · Türkçe

Specification · Release 1.0

ATB Open Biosignal Profile (ATB-OBP) 1.0

Open, vendor-neutral, royalty-free Bluetooth LE GATT profile for multi-channel EEG and biosignal devices.

Status: Release 1.0 · Date: 4 November 2026 · Owner: HMS Health Mobile Software / Auto Train Brain Inc. · Contact: info@healthmobilesoftware.com

ATB-OBP is an open, vendor-neutral Bluetooth Low Energy (BLE) GATT profile for multi-channel EEG and related biosignal devices. A device that implements this profile connects to ATB Desktop (Windows and macOS) — and later to the ATB mobile apps — without any vendor driver or SDK. The application discovers the device, reads a self-describing JSON record, starts the stream and immediately uses the device for live EEG monitoring, recordings, QEEG measurements and EEG-based brain exercise sessions in clinical EEG software.

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are used as in RFC 2119.


1. Scope and design goals

  • One profile, any hardware: 1–32 channels, 128–4096 Hz, dry / dry-active / gel / saline electrodes.
  • Self-describing: channel labels, sample rates, scaling and features are read from the device; the app needs no per-model code.
  • Simple firmware: fixed little-endian binary packets; one notification per data packet; no compression.
  • Robust: every packet carries a sequence number and a sample index, so the app detects and repairs loss exactly.
  • Extensible: unknown JSON keys are ignored; vendor features use the x- prefix; optional characteristics may be absent.

Out of scope for 1.0: on-device signal processing results, stimulation output, firmware update (use the vendor's own DFU service), more than 32 channels (planned for 2.0).


2. Conformance levels

Level Requirement
Core (REQUIRED) Service, Device Info (§5), Control Point with START_STREAM / STOP_STREAM (§6), EEG Data (§7), timing (§9), at least one sample rate ≥ 128 Hz.
Recommended Status (§8.2), impedance or contact quality (§8.1), SYNC_TIME, IDENTIFY, standard Battery Service 0x180F, Device Information Service 0x180A, LE Secure Connections.
Optional Aux (IMU / PPG, §8.4), Events / markers (§8.3), SET_RATE, SET_CHANNEL_MASK, MARKER.

A device is "ATB-OBP 1.0 Core compatible" when it passes every Core check of the in-app conformance test (§11).


3. GATT layout

3.1 UUIDs

All ATB-OBP UUIDs share one 128-bit base. Replace xxxx with the 16-bit short code:

a7b1xxxx-0b1e-4e0e-8a7b-41544200b1e0        ("415442" = ASCII "ATB")
Short UUID Name Properties Level
0000 a7b10000-0b1e-4e0e-8a7b-41544200b1e0 ATB-OBP Service (primary) — Core
0001 a7b10001-0b1e-4e0e-8a7b-41544200b1e0 Device Info Read Core
0002 a7b10002-0b1e-4e0e-8a7b-41544200b1e0 Control Point Write (with response), Indicate Core
0003 a7b10003-0b1e-4e0e-8a7b-41544200b1e0 EEG Data Notify Core
0004 a7b10004-0b1e-4e0e-8a7b-41544200b1e0 Impedance / Contact Notify Recommended
0005 a7b10005-0b1e-4e0e-8a7b-41544200b1e0 Status Read, Notify Recommended
0006 a7b10006-0b1e-4e0e-8a7b-41544200b1e0 Aux (IMU / PPG) Notify Optional
0007 a7b10007-0b1e-4e0e-8a7b-41544200b1e0 Events / Markers Notify Optional

Standard SIG services that MAY be added alongside: Battery Service 0x180F (Battery Level 0x2A19), Device Information Service 0x180A.

3.2 Advertising

  • The device MUST include the ATB-OBP Service UUID (128-bit) in its advertising data or scan response, so the app can filter devices by service.
  • The device SHOULD advertise a human-readable Complete Local Name (e.g. AcmeEEG-8 1234; the last 4 characters of the serial help identify one of several units).
  • Advertising interval while waiting for a connection: 100–250 ms recommended.

3.3 Byte order

All multi-byte integer and float fields are little-endian. Signed integers are two's complement.


4. Connection sequence (what the app does)

  1. Scan with filter services = [ATB-OBP Service]; the user picks the device in the app.
  2. Connect; exchange MTU (the app's BLE stack requests the maximum; see §9.1).
  3. Read Device Info (§5), parse and validate it. Validation errors abort the connection and are shown in the conformance test.
  4. Enable indications on Control Point; enable notifications on Status, Impedance and Events if present. Read Status once.
  5. Enable notifications on EEG Data.
  6. If sync_time is listed in features: send SYNC_TIME.
  7. Send START_STREAM(defaultRate, mask = all eeg+eog channels, format); wait for the response indication (timeout 3 s).
  8. Stream. On user disconnect: send STOP_STREAM, wait ≤ 1.5 s for the response, then disconnect.

Firmware MUST tolerate steps 4–5 in any order and MUST NOT start streaming before START_STREAM.


5. Device Info characteristic (0001)

A UTF-8 encoded JSON object (no BOM; trailing NUL bytes allowed). The value MAY be longer than the ATT MTU — the client uses ATT Read Blob (long read) automatically. Maximum length 2048 bytes. The value MUST NOT change while connected.

5.1 Fields

Key Type Req. Meaning
obp string "major.minor" yes Profile version implemented. For this spec "1.0".
vendor string ≤ 64 yes Manufacturer name.
model string ≤ 64 yes Model name.
serial string ≤ 64 recommended Unit serial number (no personal data).
firmware string ≤ 32 recommended Firmware version.
hardware string ≤ 32 no Hardware revision.
electrodes "dry" | "dry-active" | "gel" | "saline" recommended Selects the app's signal-quality and artifact profile.
rates number[] yes Supported sample rates in Hz (64–4096). At least one MUST be ≥ 128. 250, 256, 500 or 512 recommended.
defaultRate number recommended Rate used by the app; MUST be in rates.
formats string[] yes Sample formats supported: "int16", "int24", "float32".
format string recommended Preferred format (MUST be in formats).
lsb_uV number yes for int formats Microvolts per LSB at the device output (after gain). µV = raw × lsb_uV.
range_uV number no ± input range in µV.
reference string no Reference electrode / montage, e.g. "Cz", "linked-ears", "A1".
ground string no Ground / bias electrode, e.g. "Fpz".
channels object[] (1–32) yes One entry per hardware channel, in channel-index order. See §5.2.
maxFramesPerPacket number 1–255 recommended Largest nFrames the device sends (§7).
features string[] recommended Capability list, §5.3.
filters object no { "hp_Hz": 0.5, "lp_Hz": 100, "notch_Hz": null } — analog/digital filtering applied by the device.

Unknown keys MUST be ignored by the app; vendors MAY add keys prefixed x- (e.g. "x-acme-gain": 24).

5.2 Channel entries

{ "label": "Fp1", "type": "eeg" }
  • label: electrode name in the international 10-20 / 10-10 system (e.g. Fp1, F3, Cz, O2, TP9). Legacy names T3/T4/T5/T6 are accepted and mapped to T7/T8/P7/P8. Labels MUST be unique.
  • type: eeg (default) · ref · eog · emg · ecg · aux. The app streams eeg and eog channels by default; at least one eeg channel is required.
  • The channel's index (0-based position in the array) is its bit in the channel mask and its column in data packets.

5.3 Features

Feature Meaning
impedance Impedance in kΩ via IMPEDANCE_START (§8.1, format 1).
contact_quality Contact quality 0–100 % instead of kΩ (§8.1, format 2) — typical for dry electrodes.
impedance_during_stream EEG keeps streaming while impedance is measured.
status Status characteristic present.
events Events characteristic present.
aux.accel / aux.gyro / aux.ppg Aux characteristic streams these types.
sync_time / marker / identify / set_rate / channel_mask Control Point opcodes supported.
battery_service / dis SIG services 0x180F / 0x180A present.
x-… Vendor specific.

5.4 Example

{
  "obp": "1.0",
  "vendor": "Acme Neuro",
  "model": "AN-8",
  "serial": "AN8-000123",
  "firmware": "1.2.0",
  "hardware": "rev C",
  "electrodes": "dry",
  "rates": [250, 500],
  "defaultRate": 250,
  "formats": ["int24"],
  "format": "int24",
  "lsb_uV": 0.02235,
  "range_uV": 187500,
  "reference": "linked-ears",
  "ground": "Fpz",
  "channels": [
    { "label": "Fp1", "type": "eeg" }, { "label": "Fp2", "type": "eeg" },
    { "label": "C3",  "type": "eeg" }, { "label": "C4",  "type": "eeg" },
    { "label": "P3",  "type": "eeg" }, { "label": "P4",  "type": "eeg" },
    { "label": "O1",  "type": "eeg" }, { "label": "O2",  "type": "eeg" }
  ],
  "maxFramesPerPacket": 9,
  "features": ["contact_quality", "status", "sync_time", "identify", "battery_service"],
  "filters": { "hp_Hz": 0.5, "lp_Hz": 100, "notch_Hz": null }
}

6. Control Point characteristic (0002)

The app writes a command (Write with response). The device answers every command with one indication on the same characteristic.

6.1 Command format

Byte Field
0 Opcode (u8)
1… Parameters (little-endian)
Opcode Name Parameters Level
0x01 START_STREAM rate_Hz u16 · channelMask u32 · format u8 (0 = device default, 1 = int16, 2 = int24, 3 = float32) Core
0x02 STOP_STREAM — Core
0x03 IMPEDANCE_START — Recommended
0x04 IMPEDANCE_STOP — Recommended
0x05 SET_RATE rate_Hz u16 (only while not streaming) Optional
0x06 SET_CHANNEL_MASK channelMask u32 (only while not streaming) Optional
0x07 SYNC_TIME unixTime_ms u64 Recommended
0x08 MARKER code u16 Optional
0x09 IDENTIFY seconds u8 (blink LED / vibrate) Recommended
0x0A GET_STATUS — Recommended

channelMask: bit i selects channels[i]. Bits ≥ channel count MUST be ignored. A mask of 0 is INVALID_PARAM.

6.2 Response indication

Byte Field
0 0x80 (response code)
1 Request opcode
2 Result (u8)
3… Optional response data
Result Name When
0x00 SUCCESS Command executed.
0x01 NOT_SUPPORTED Unknown or unsupported opcode.
0x02 INVALID_PARAM Rate not in rates, mask 0, unsupported format…
0x03 INVALID_STATE e.g. SET_RATE while streaming, START_STREAM while already streaming with different parameters.
0x04 FAILED Hardware error (also set Status error code).

Response data:

  • SYNC_TIME → sampleIndex u32 of the device at reception (lets the app align device sample clock to wall time).
  • GET_STATUS → the 8-byte Status payload (§8.2).
  • START_STREAM → MAY return rate_Hz u16 · channelMask u32 · format u8 actually applied.

Timing: the response SHOULD be indicated within 200 ms (MUST within 3 s). START_STREAM while already streaming with identical parameters SHOULD return SUCCESS (idempotent). After START_STREAM the first data packet MUST start with firstSampleIndex = 0.


7. EEG Data characteristic (0003)

One notification = one packet = a 10-byte header + nFrames frames. A frame is one sample of every channel in the active mask, in ascending channel-index order.

7.1 Packet layout

Offset Size Field Notes
0 u8 format Low nibble: 0x01 int16 · 0x02 int24 · 0x03 float32 (value already in µV). High nibble reserved = 0.
1 u16 seq Packet counter, +1 per packet, wraps 65535 → 0. Resets to 0 on START_STREAM.
3 u32 firstSampleIndex Index of the first frame in this packet since START_STREAM (sample clock), wraps at 2³².
7 u8 nFrames Frames in this packet (1–255, ≤ maxFramesPerPacket).
8 u8 nChannels Channels per frame = popcount(active mask).
9 u8 flags bit0 LEAD_OFF (any channel off) · bit1 CLIPPING · bit2 MARKER (an Events record refers to this packet) · bit3 LOW_BATTERY · bits4–7 reserved = 0.
10 n samples nFrames × nChannels × bytesPerSample, frame-major (F0C0, F0C1 … F0Cn, F1C0 …).

bytesPerSample: int16 = 2, int24 = 3 (little-endian, sign-extended from bit 23), float32 = 4 (IEEE-754).

Scaling: µV = raw × lsb_uV for int formats; float32 values are µV directly.

7.2 Sizing packets

The ATT notification payload is MTU − 3 bytes. The firmware MUST choose nFrames so that 10 + nFrames × nChannels × bytesPerSample ≤ MTU − 3.

MTU Channels Format Max frames / packet Packets/s at 250 Hz
247 8 int24 9 ~28
247 8 int16 14 ~18
247 4 int24 19 ~13
247 16 int24 4 ~63
185 8 int24 7 ~36
23 (default) 2 int16 2 125

Firmware SHOULD send a packet at least every 40 ms even if it is not full (latency, §9.2).

7.3 Golden packets (for firmware unit tests)

int16, seq = 0x0102, firstSampleIndex = 1000, 2 frames × 2 channels, samples [100, −100], [32767, −32768]:

01 02 01 E8 03 00 00 02 02 00 64 00 9C FF FF 7F 00 80

int24, seq = 0, index 0, LEAD_OFF set, 1 frame × 3 channels, samples [1, −1, −8388608]:

02 00 00 00 00 00 00 01 03 01 01 00 00 FF FF FF 00 00 80

7.4 Loss handling (app side, informative)

  • seq gap → packets lost; firstSampleIndex gap → exact number of frames lost.
  • Gaps ≤ 1 s are bridged by linear interpolation so filters and spectra stay continuous; the loss is counted and reported (loss %).
  • Gaps > 1 s break the stream (discontinuity); recordings mark the break.
  • Packets with an older firstSampleIndex (duplicates / reordered) are dropped.
  • The device MUST NOT renumber samples after a buffer overrun: if frames are dropped on the device, skip their indices (so the app sees the gap) and increment the Status droppedPackets counter.

8. Optional characteristics

8.1 Impedance / Contact (0004, Notify)

Sent while impedance mode is active (IMPEDANCE_START … IMPEDANCE_STOP), at least once per second.

Offset Size Field
0 u8 format: 0x01 kΩ (u16 per channel) · 0x02 contact quality 0–100 % (u8 per channel)
1 u8 firstChannel (index into channels)
2 u8 count
3 … values: format 1 → u16 kΩ, 0xFFFE = open / lead-off, 0xFFFF = unknown; format 2 → u8 0–100, 0xFF = unknown

Large channel counts MAY be split across several notifications using firstChannel. Unless impedance_during_stream is listed, the device MAY pause EEG data during impedance mode and MUST resume it on IMPEDANCE_STOP (continuing seq/firstSampleIndex numbering, which the app treats as a gap).

App colour coding: kΩ ≤ 20 green, ≤ 50 amber, > 50 red; quality ≥ 70 green, ≥ 40 amber.

8.2 Status (0005, Read + Notify)

8 bytes. Notify on change and at least every 10 s while connected.

Offset Size Field
0 u8 Battery % (0–100, 0xFF unknown)
1 u8 Flags: bit0 CHARGING · bit1 STREAMING · bit2 IMPEDANCE · bit3 LOW_BATTERY · bit4 ERROR
2 u8 State: 0 idle · 1 streaming · 2 impedance · 3 error
3 u8 Error code (0 = none; vendor-defined otherwise)
4 u32 Dropped packets (device-side overruns since START_STREAM)

Safety recommendation: devices SHOULD NOT stream EEG while charging from mains (mains noise and electrical safety); report CHARGING so the app can warn.

8.3 Events / Markers (0007, Notify)

7 bytes per event.

Offset Size Field
0 u8 Type: 1 marker echo (from MARKER) · 2 button press · 3 lead-off change · 4 hardware trigger input
1 u32 sampleIndex (on the EEG sample clock)
5 u16 Code (marker code, button id, channel index, trigger value)

8.4 Aux — IMU / PPG (0006, Notify)

Offset Size Field
0 u8 Type: 0x10 accelerometer (mg) · 0x11 gyroscope (0.01 °/s) · 0x12 PPG (raw)
1 u16 seq
3 u32 sampleIndex on the EEG sample clock (for alignment)
7 u8 nFrames
8 u8 nAxes (accel/gyro 3, PPG 1–3)
9 … int16 samples, frame-major

ATB Desktop 1.11 ignores Aux data; it is reserved for motion-artifact rejection and HRV in a later release.


9. Timing and performance

9.1 MTU and connection parameters

  • Support ATT MTU exchange up to 247 (BLE 4.2 Data Length Extension). The device MUST still work at the default MTU 23 by sending smaller packets.
  • Connection interval 7.5–30 ms recommended (15 ms typical); slave latency 0 while streaming; supervision timeout 2–4 s.
  • 2M PHY SHOULD be supported for ≥ 16 channels or ≥ 500 Hz.

9.2 Sampling and latency

Item Requirement
Sample rate ≥ 128 Hz (Core); 250 / 256 / 500 Hz recommended
Rate accuracy Effective rate within ±2 % of nominal (crystal-derived sample clock; ±50 ppm recommended)
Packet loss ≤ 0.5 % frames over 10 min at 1 m line of sight
Latency Sample-to-notification ≤ 100 ms (Core: max gap between packets ≤ 100 ms); ≤ 50 ms recommended
Jitter Inter-packet arrival standard deviation ≤ 20 ms recommended
Channels All channels sampled simultaneously (or skew ≤ 1 sample)
Data rate check 8 ch × 250 Hz × int24 = 6 kB/s payload — comfortably within BLE 1M PHY

9.3 Signal quality (recommendations)

  • Input-referred noise ≤ 1 µV RMS (0.5–40 Hz); CMRR ≥ 100 dB; resolution ≤ 0.1 µV/LSB.
  • Do not apply a mains notch filter on the device unless reported in filters; the app applies 50/60 Hz notch by region.
  • High-pass ≤ 0.5 Hz; anti-alias low-pass < 0.45 × sample rate.

10. Security and privacy

  • LE Secure Connections pairing (Just Works or Passkey) is RECOMMENDED; the profile also works unpaired for evaluation units.
  • The device MUST NOT store or transmit personal or health information about the wearer. serial identifies the hardware only.
  • The Control Point MUST reject malformed commands with INVALID_PARAM and never crash or reboot.
  • Firmware updates are out of scope; use the vendor's own DFU service (it MUST NOT be reachable through ATB-OBP characteristics).

11. Conformance checklist (in-app test)

ATB Desktop → Clinic → device card "ATB-OBP Bluetooth device" → 🧪 Conformance test runs these checks live and exports a JSON report (Save report). Send the report to info@healthmobilesoftware.com to be listed as compatible.

ID Level Check Pass criterion
C1 Core Device Info JSON Valid JSON, obp major = 1, required fields present, rates/format/lsb consistent, unique labels, ≥ 1 EEG channel. Warnings (e.g. non 10-10 label) do not fail.
C2 Core START_STREAM Response SUCCESS within 3 s.
C3 Core Sample rate Effective rate within ±2 % of defaultRate (warn ±5 %), ≥ 10 s streaming.
C4 Core Frame loss ≤ 0.5 % (warn ≤ 2 %).
C5 Core Packet gap Longest gap ≤ 100 ms (warn ≤ 250 ms).
C6 Core STOP_STREAM Response SUCCESS on disconnect.
C7 Core 10-10 labels All EEG labels map to 10-10 names (warning only).
O1 Optional Status Status read/notify decodes.
O2 Optional Impedance / contact At least one impedance/quality notification received.

Firmware-side self test: reproduce the golden packets of §7.3 byte-for-byte.


12. Versioning

  • obp = "major.minor". Minor versions only add optional fields, opcodes, characteristics or flag bits; a 1.x app accepts any 1.y device (unknown parts ignored, a newer minor is reported as a warning).
  • A major version change (2.0) may change packet layouts; it will use a new service UUID short code so 1.x and 2.x can coexist on one device.
  • Reserved bits and bytes MUST be sent as 0 and ignored on receipt.

13. Reference material

File Purpose
atb_obp.h C header: UUIDs, opcodes, packed structs, packet builder for embedded firmware.
reference/atb_obp_sim.py Reference BLE peripheral (Python, bless) that simulates an 8-channel device — test the app without hardware.
ATB Desktop src/shared/obp/protocol.ts The app's decoder and validator (normative behaviour of §5–§7 on the client side).

Appendix A — Minimal firmware state machine

           ┌────────────── IMPEDANCE_STOP ───────────────┐
           ▼                                              │
  IDLE ──START_STREAM──► STREAMING ──IMPEDANCE_START──► IMPEDANCE
   ▲  ▲                     │                             │
   │  └──── STOP_STREAM ────┘                             │
   └────────────── STOP_STREAM / disconnect ──────────────┘
  • On disconnect: stop streaming, keep advertising.
  • On START_STREAM: reset seq and firstSampleIndex to 0, apply rate/mask/format, respond SUCCESS, then start notifications.

Appendix B — Example timeline

App  → Read 0001                         ← {"obp":"1.0",...}
App  → CCCD 0002 = indicate, 0003/0004/0005 = notify
App  → 0002: 07 <unix ms u64>            ← 80 07 00 <sampleIndex u32>        (SYNC_TIME)
App  → 0002: 01 FA 00 FF 00 00 00 02     ← 80 01 00                          (START 250 Hz, 8 ch, int24)
     ← 0003: 02 00 00 00 00 00 00 09 08 00 <216 bytes>   seq 0, idx 0
     ← 0003: 02 01 00 09 00 00 00 09 08 00 <216 bytes>   seq 1, idx 9
     …
App  → 0002: 02                          ← 80 02 00                          (STOP)