Skip to content

BLE Architecture

BLUETOOTH LOW ENERGY · GATT · NUS

This chapter describes how MeshCore communicates with companion devices via Bluetooth Low Energy (BLE), and provides an analysis of the BLE stack and GATT services.

BLE vs. Classic Bluetooth

Since Bluetooth 4.0 (2010) there are two separate radio systems within the Bluetooth standard — they are different radio protocols that do not communicate directly with each other.

Property Classic Bluetooth (BR/EDR) BLE
Power consumption High Very low
Data rate High Low
Kanalen 79 × 1 MHz 40 × 2 MHz
Typical use Audio, files Sensors, IoT, MeshCore

The T1000-E is a BLE-only device. Your smartphone is dual-mode: it talks to your Bluetooth headphones (Classic) and to your MeshCore radio (BLE).

The BLE Stack (7 layers)

BLE Stack SVG

Diagram 1 bij ble-architectuur

GATT — Generic Attribute Profile

GATT is the structure through which BLE devices offer their data. Think of it as a digital notice board:

Service (categorie)
  └── Characteristic (specifiek datapunt)
        └── Descriptor (extra configuratie)

NUS — Nordic UART Service

NUS is a standard BLE service from Nordic Semiconductor that simulates a serial port (UART) over Bluetooth. MeshCore uses NUS for all communication with companion devices.

Nordic UART Service (NUS)
  ├── RX Characteristic → data van radio naar computer
  └── TX Characteristic → data van computer naar radio

UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e

[!NOTE] NUS is not a protocol — it is a service specification. GATT is the protocol; NUS is a service offered via GATT.

Official vs. Custom Services

Type UUID Wie mag maken? Voorbeeld
Officieel (SIG) 16-bit Alleen Bluetooth SIG Heart Rate (0x180D)
Custom 128-bit Iedereen NUS, MeshCore Companion

Well-known official services

Service UUID Application
Battery Service 0x180F Battery level
Device Information 0x180A Manufacturer, model, firmware
Heart Rate 0x180D Heart rate monitors
Environmental Sensing 0x181A Temperature, humidity, pressure
HID over GATT 0x1812 Keyboards, mice
Generic Access 0x1800 Required — device name

Notify vs. Read

Method How it works When
Read You actively request data One-time values (e.g. battery status)
Notify Device sends automatically when new data arrives Continuous data stream (e.g. messages)

For MeshCore you use Notify — you want to know when a message arrives. The CCCD (Client Characteristic Configuration Descriptor) is the on/off switch for Notify.

[!WARNING] Crucial: Only one client at a time can activate Notify. A second client will receive the error Notify acquired.

Pairing, Bonding, and Trust

Step What happens Analogy
Pairing Exchange cryptographic keys Exchange phone numbers
Bonding Permanently store keys Save number in contacts
Trust Automatically trust device Add someone to favourites
# Controleer in Linux:
bluetoothctl info AA:BB:CC:DD:EE:FF | egrep -i "Paired|Bonded|Trusted"

# Verwacht:
Paired: yes
Bonded: yes
Trusted: yes

BLE Channel Layout

The 2.4 GHz ISM band (2400–2483.5 MHz) is divided into 40 channels of 2 MHz each:

Type Channels Function
Advertising 3 (nrs. 37, 38, 39) Finding devices, initiating connections
Data 37 (nrs. 0–36) Actual communication

Advertising channels are strategically chosen to avoid Wi-Fi interference — they sit between Wi-Fi channels 1, 6, and 11. Communication uses frequency hopping: one channel at a time, constantly changing.

Serial vs. Structured

NUS is a serial service — it simulates a UART port with unstructured bytes. Official SIG services are structured with fixed fields.

Aspect Serial (NUS) Structured (SIG)
Data format Free, self-defined Fixed, by specification
Parsing Build custom parser Standard parser possible
Interoperability Own software only Any conforming app/device
Flexibility Maximum Limited to spec

Why MeshCore chooses NUS

  • Flexibility — The Companion Protocol needs its own framing
  • No suitable SIG service — There is no "Mesh Radio Service" standard
  • Bidirectional — NUS offers both RX and TX characteristics
  • Simplicity — No complex SIG specification to implement

Ownership — The core problem

Ownership indicates which client holds the active GATT session with Notify. This sits at OSI layer 5 (session). Only one listener may be connected at a time.

Typical "owner" Problem Solution
GNOME Bluetooth GUI Notify acquired Close GUI
bluetoothctl connect Tool fails on notify Always disconnect first
Phone Bluetooth on Phone claims connection Turn BT off on phone
Multiple scripts First wins, rest fails One tool at a time

BLE Pairing on Headless Linux

When developing MeshCore applications on a desktop Linux system (Ubuntu, Fedora with GNOME or KDE), BLE PIN pairing typically works without issue. The desktop environment automatically registers a Bluetooth pairing agent via D-Bus, which handles PIN requests in the background.

On headless systems — such as a Raspberry Pi, server, or embedded Linux without a graphical environment — there is no default pairing agent active. The BlueZ bluetoothd daemon does not handle pairing itself: it delegates every PIN request via D-Bus to a registered agent. Without an agent, PIN requests go unanswered and the BLE connection fails.

This makes the problem hard to detect during development: on a desktop everything works, but when deploying to the headless target system — precisely the most common use case for meshcore_py — the connection fails.

Overview per environment

Environment Standaard agent? PIN-pairing werkt?
Ubuntu/Fedora Desktop (GNOME/KDE) ✅ Desktop BT agent ✅ Yes — agent handles PIN silently
Raspberry Pi OS (headless) ❌ No agent ❌ No — PIN requests unanswered
Raspberry Pi OS (with desktop) ✅ Desktop BT agent ✅ Probably yes
Fedora/Debian server (headless) ❌ No agent ❌ No — same problem

Oplossing

The fix is simple: register a custom D-Bus pairing agent before connecting. This works on all Linux environments — with or without a desktop. See the source reference below for a detailed technical description and sample code.

Conclusion

MeshCore BLE companion works correctly on Linux. The only requirement is: exactly one active BLE client per radio. The GATT/NUS architecture ensures reliable communication between node and companion device. On headless systems, a custom D-Bus pairing agent is additionally required for successful PIN verification.

What travels over this link — the frames, the commands and the responses — is described in The three transports.

The section on BLE pairing on headless Linux is based on a contribution by PE1HVH to meshcore_py issue #33 ↗.

Translated from Dutch by Anthropic Claude