DRAFT — Preliminary version, do not share. · Version préliminaire, ne pas partager.

MQTT topics and message formats

This page lists the MQTT broker's connection parameters, how a device authenticates, and every topic a device can publish or subscribe to, with its payload. Use it when you write or debug the MQTT layer of your firmware.

Two supported paths. Your firmware can talk to the broker directly with any MQTT 5 client, following this page. Or it can use the Linux daemon or the MCU library, which implement this protocol for you, including key handling, heartbeats, actions and firmware updates. Some topics are configurable per registry: read them from the registry rather than hard-coding them.

Connection parameters

Parameter Value
Host mqtts.fundamentum-iot.com
Port 8883 (MQTT over TLS)
Protocol MQTT 5
Client certificate Not required
Server Name Indication (SNI) Required: send the broker hostname in the TLS handshake
Username Not used. Set it to unused
Password A device JWT, see Authentication
Client ID Identifies the device, see Client ID

Always connect to the host and port returned by provisioning (data.mqtt.host and data.mqtt.port), see Devices API v3.

Embedded TLS stacks must trust the root certificate authority of the broker's chain. Inspect the chain with:

openssl s_client -showcerts -verify 5 -connect mqtts.fundamentum-iot.com:8883

Authentication

The device signs a JWT with its private key, the one whose public key it registered at provisioning. The broker verifies it against that public key. No shared secret travels on the wire.

Claim Value
aud The project ID
iat Issue time (Unix seconds)
exp Expiry time (Unix seconds)

Sign with RS256. The broker refuses the connection if the signature is invalid, the token is expired, or aud does not match the project in the client ID.

Client ID

Long form:

projects/<project-id>/regions/<region-id>/registries/<registry-id>/devices/<serial-number>

Short form, for devices where length matters:

<project-id>.<region-id>.<registry-id>.<serial-number>
Part Description
project-id Integer. Your project
region-id Integer. 1 unless told otherwise
registry-id Integer. The registry the device was provisioned into
serial-number String. Unique in its registry. The broker lowercases it

Topics

Every topic starts with the device's own prefix:

registries/<registry-id>/devices/<serial-number>/

A device may only publish and subscribe under its own prefix. There is no device-to-device traffic: your systems receive device data through connectors.

Device to Fundamentum

Topic (after the prefix) Payload Purpose
events Any format Telemetry, sent to the project's default telemetry topic
events/<name> Any format Telemetry sent to the registry topic configured under <name>
subdevices/<sub-serial-number>/events Any format Telemetry on behalf of a sub-device. Only events is allowed under subdevices/
states Any format; Fundamentum JSON recommended Device states and beacon readings
heartbeat JSON, or protobuf with v: 1 Proof the device is alive
commands/<device-command-id> JSON Outcome of a command
actions/<action-id>/response Protobuf Outcome of an action
pfwd/rx Binary Reserved for the port-forwarding agent

Fundamentum to device

Topic (after the prefix) Payload QoS Retained Purpose
config JSON — — Device configuration, sent once when the device subscribes
commands JSON array 0 Yes Pending commands. Retained, so a device that reconnects receives them
actions Protobuf 2 No Actions to execute
custom_actions Binary 0 No Your own application messages
custom_actions/<name> Binary 0 No Your own application messages on a named channel
pfwd/tx Binary 2 No Reserved for the port-forwarding agent

Messages on custom_actions are delivered only while the device is subscribed. Nothing is sent back to the cloud for them.

Payloads

Telemetry: events

Any bytes. Fundamentum passes telemetry through as is, so you choose your own format. Decode a proprietary format with a cloud function.

States: states

Any payload reaches the registry's states topic. When you use the Fundamentum JSON format, the platform also stores the states (history and latest values) and extracts beacon readings:

{
  "timestamp": 1789167823,
  "states": {
    "battery_level": 87,
    "temperature": 21.4
  },
  "beacons": [
    {
      "identification": "beacon-abc123",
      "states": { "rssi": -67, "battery_level": 44 }
    }
  ]
}
Field Description
timestamp Unix time in seconds
states Free-form object: any name, any value
beacons[].identification Identifies the beacon, which is treated as a device of its own
beacons[].states The beacon's states. rssi, when present, is recorded as a scan

Heartbeat: heartbeat

The MQTT 5 user property v selects the format.

Without v, or with v: 0, send JSON with the seconds since boot:

{ "uptime": 86400 }

With v: 1, send a protobuf Heartbeat message. It can also carry the sub-devices a gateway speaks for, each with its serial number and uptime. See Gateways and sub-devices.

The protobuf schemas for heartbeats and actions are published in the fundamentum-iot-mqtt-proto crate on crates.io.

Commands: commands and commands/<device-command-id>

The device receives a JSON array of pending commands, each shaped like this:

{
  "device_command_id": 1,
  "command_short_name": "command-name",
  "parameters": {}
}
command_short_name Parameters
reboot device_id
firmware-update target_device, and version with configuration_file_url (download URLs), configuration (free-form, passed through), identifier, name
custom-configuration-update deep_update (apply the whole configuration when true, only the difference when false), retry_timeout (seconds), target_device.actual_configuration, target_device.new_configuration

Example firmware update:

{
  "device_command_id": 790,
  "command_short_name": "firmware-update",
  "parameters": {
    "device_id": 296,
    "command_id": 8,
    "version_id": 29,
    "target_device": { "serial_number": "<serial-number>", "id": 296 },
    "version": {
      "configuration_file_url": ["https://<download-url>"],
      "configuration": {},
      "identifier": "base-firmware-1-4-2",
      "name": "base-firmware-version",
      "id": 29
    }
  }
}

command_id and version.id are internal identifiers that the device can ignore.

Report progress on commands/<device-command-id>:

{ "status": "success", "message": "firmware updated to 1.4.2" }
Field Values
status ongoing, success or failure
message Free text describing the change of status

See Firmware updates (OTA).

Actions: actions and actions/<action-id>/response

Actions are protobuf messages. The device reports the outcome on actions/<action-id>/response, also in protobuf. Actions are defined per device type and translated for each firmware version by encoders. See Actions and custom encoders.

Configuration: config

The device's stored configuration, as JSON. It has no fixed shape: it is whatever you stored for the device, for example with POST .../configuration on the Devices API.

What's next