Skip to main content

BLE Operational Protocol

After provisioning, BLE stays active for direct mobile ↔ device communication — with or without WiFi.

Principle

BLE and MQTT carry identical JSON. Route by payload fields (type, ch), not transport.

MQTT → {workspace_slug}/{device_key}/{stream}
BLE → same JSON object, no topic path

One handler per message type. Switching transport mid-session requires no format change.

Transport comparison

MQTTBLE
RoutingTopic suffix + JSON type/chJSON type/ch only
AuthConnection (username/password)auth message
ConfigFull protocol (gzip, sha256)Simplified (no gzip)
DirectionCloud ↔ deviceMobile ↔ device

Discovery

  1. Scan for service UUID 1d14d6ee-1001-4000-8024-b5a3c0ffee01
  2. Match GAP name to device_key (e.g. MER-C782)

Before provisioning the name is MAC-based (MER-XXXX). See BLE JSON channel.

Session

Connect → negotiate MTU → enable TX notify → auth → operational messages

Non-auth messages before successful auth are rejected.

MTU

uint16_t mtu = iotmer_ble_get_att_mtu() ?: 23;
uint16_t max_payload = mtu > 3 ? mtu - 3 : 0;

Split payloads larger than max_payload into multiple notifications.


Auth (BLE only)

No MQTT equivalent — MQTT auth is at connection level.

Request:

{"type":"auth","token":"dht_abc123..."}

Success:

{"type":"auth.ok","mode":"online"}

mode: "online" (MQTT connected) or "offline".

Failure:

{"type":"auth.err","reason":"invalid_token"}
reasonCause
missing_tokenNo token field
invalid_tokenToken mismatch
not_authenticatedMessage sent before auth

Session timeout: 60 s. Re-auth on timeout. Disconnect clears session.

Mode notification (BLE only)

Device sends when MQTT state changes:

{"type":"mode","mode":"offline"}

Commands

MQTT: publish to …/cmd · BLE: write to RX

{"type":"cmd","ch":"relay_1","cmd":"set","state":true}
FieldDescription
type"cmd"
chChannel ID from device template
cmde.g. "set", "reset_energy"
stateFor set: true = on

Device-wide commands use ch: "device". Firmware OTA is MQTT-triggered ("cmd":"ota"); do not start OTA over BLE. See OTA.


Telemetry

MQTT: publish to …/telemetry · BLE: TX notify

{"type":"telemetry","ch":"ch_1","voltage":22050,"current":1234,"ts":1748000000}
FieldRequiredNotes
typeYes"telemetry"
chPer channelOmit for device-scope metrics
tsRecommendedUnix seconds UTC

Integer scaling is defined per device template in the console — not hard-coded in the transport.

Push

Sent on timer, significant change, or boundary events. MQTT publishes when connected. BLE may mirror on the same schedule or only on demand (product-specific).

Pull: telemetry.get

BLE (after auth, write to RX):

{"type":"telemetry.get","rid":"550e8400-…"}

Device responds with one or more type:"telemetry" notifications. Optional ack: telemetry.get.ok.

MQTT (publish to …/cmd):

{"type":"cmd","ch":"device","cmd":"telemetry.get"}

Device publishes telemetry to …/telemetry. Some products use …/cmd/device with {"cmd":"telemetry.get"} — follow the device template.


State

MQTT: …/state, QoS 1, retain · BLE: TX notify

{"type":"state","ch":"relay_1","state":true,"relay_status":"closed","ts":1748000000}
FieldMeaning
stateLogical: energized
relay_statusPhysical: "closed" / "open"

Event

MQTT: …/event, QoS 1 · BLE: TX notify

{"type":"event","key":"fault","value":3,"fault_bits":{"over_current":true},"ts":1748000000}

Common fault_bits: over_current, over_voltage, under_voltage, energy_low, reset_overflow.

This is live notify. On-device history is event.get (command ack), not this topic. See Event logging.


Config (BLE)

Get

No MQTT equivalent for this direction — MQTT config flows cloud → device. See MQTT Config Protocol.

// request
{"type":"config.get","rid":"abc123"}
// response
{"type":"config.resp","rid":"abc123","data":{"protection":{"overcurrent_a":63}}}

Large responses use chunked config.resp.part:

{"type":"config.resp.part","rid":"abc123","i":0,"n":3,"chunk":"{\"protection\":…"}

Reassemble: group by rid, sort by i, concatenate chunk strings, parse JSON. Timeout: 5 s. Cap concurrent transfers (2–4).

If config not loaded: "data": null, "note": "config_not_loaded".

Set

Applies to MCU immediately. Does not persist to cloud.

{"type":"config.set","rid":"abc124","data":{"protection":{"over_voltage_v":260}}}
{"type":"config.ok","rid":"abc124"}

When MQTT reconnects, cloud config overwrites local values. To persist, update config in the Control Plane.


SDK integration

Register one handler per command — called identically from MQTT or BLE:

iotmer_ble_ops_register_cmd(type, ch, handler);

Outbound — build JSON once, send on both channels:

iotmer_ble_ops_notify(payload); /* BLE TX */
iotmer_publish(client, payload); /* MQTT */