Files
Qris-Soundbox/soundbox-backend-mqtt-spec.md
2026-06-07 01:49:36 +07:00

12 KiB
Executable File

Soundbox Backend MQTT Specification

This document describes the backend contract used by the QF100 soundbox firmware in this project.

Source reference:

  • MQTT payload parser: app/source/MainApp/demo.c
  • Config endpoint constants: app/source/MainApp/globalDefine.h

1. Overview

The device communicates with the backend in two stages:

  1. The device calls the config API to receive MQTT connection settings.
  2. The device connects to MQTT and subscribes to the topic returned by the config API.

The backend then publishes JSON payloads to that topic.

Current firmware mode:

  • SAMPLE_MQTT_DEMO is enabled by default.
  • MQTT_STRICT_TEST_DEMO is disabled unless explicitly enabled in firmware.

2. Device Config API

The device sends a JSON request body to CONFIG_ADDR.

Current configured endpoint:

http://sms.bizone.id/speaker/dev-config

Transport:

  • HTTP is currently configured for the device config API.
  • The firmware calls sdk_http_get() with the http:// config URL.
  • This avoids TLS/certificate validation issues during config fetch.

Request Body

{
  "dev-model": "QF100",
  "item-number": "00",
  "dev-sn": "DEVICE_SN",
  "hardware-config": "0x0F",
  "fw-version": "1.0.0",
  "fw-build": 1,
  "app-config-version": 0,
  "imei": "IMEI",
  "imsi": "IMSI",
  "iccid": "ICCID"
}

Successful Response

{
  "error-code": 0,
  "mqtt": {
    "broker-ip": "broker.example.com",
    "broker-port": 1883,
    "client-id": "soundbox-DEVICE_SN",
    "user-name": "mqtt_user",
    "password": "mqtt_password",
    "subscribe-topic": "soundbox/DEVICE_SN/down",
    "keep-alive": 60
  }
}

Required Fields

Field Type Required Notes
error-code number yes Must be 0 for success.
mqtt object yes Required when error-code is 0.
mqtt.broker-ip string yes Hostname or IP.
mqtt.broker-port number yes MQTT port.
mqtt.client-id string yes MQTT client ID.
mqtt.user-name string yes MQTT username.
mqtt.password string yes MQTT password.
mqtt.subscribe-topic string yes Topic the device will subscribe to.
mqtt.keep-alive number yes Keep-alive interval in seconds.

Important:

  • Numeric fields must be JSON numbers, not strings.
  • If any required MQTT field is missing, the device treats the config as invalid.
  • app-config-version parsing is present but commented out in firmware, so it is not currently applied.

Error Response

{
  "error-code": 1001
}

Any non-zero error-code is treated as config failure by the firmware.

3. MQTT Payment Payload

The backend publishes this payload to mqtt.subscribe-topic.

{
  "header": {
    "category": 1
  },
  "data": {
    "pay-amount": 15000
  }
}

Required Fields

Field Type Required Notes
header object yes Container for message metadata.
header.category number yes Use 1 for payment amount playback.
data object yes Container for message body.
data.pay-amount number yes Must be greater than 0.

Behavior:

  • The device formats pay-amount into a 12-digit string.
  • Example: 15000 becomes 000000015000.
  • The device displays the amount and plays the payment audio.

Do not send pay-amount as a string:

{
  "data": {
    "pay-amount": "15000"
  }
}

Use a number instead:

{
  "data": {
    "pay-amount": 15000
  }
}

3.1 MQTT Transport Security

Current firmware setting:

#define MQTT_TLS_ENABLE (0)

This means MQTT currently connects without TLS.

The SDK and firmware code do support MQTT TLS:

  • sdk_MQTT_connect(..., tls, ...) accepts a TLS flag.
  • The firmware already passes MQTT_TLS_ENABLE into sdk_MQTT_connect().
  • Certificate setup hooks exist in firmware:
    • MQTT_SERVER_CA_CRT
    • MQTT_CLIENT_CA_CRT
    • MQTT_CLIENT_PRIKEY
    • sdk_setx509cer(...)
    • sdk_setx509_own_cer(...)
    • sdk_setx509_prikey(...)

To use MQTTS, firmware must be rebuilt with:

#define MQTT_TLS_ENABLE (1)

Backend recommendation:

  • For current firmware, expose plain MQTT, usually port 1883.
  • For TLS firmware, expose MQTTS, usually port 8883, and provide the correct server CA chain expected by the firmware.
  • Do not assume MQTTS is active unless the firmware was rebuilt with MQTT_TLS_ENABLE (1).

4. MQTT OTA Trigger Payload

The backend can trigger an OTA check by publishing:

{
  "header": {
    "category": 2
  },
  "data": {
    "fw-version": "1.0.1",
    "fw-build": 2
  }
}

Required Fields

Field Type Required Notes
header.category number yes Use 2 for OTA trigger.
data.fw-version string yes Target firmware version.
data.fw-build number yes Target firmware build, must be greater than 0.

Behavior:

  • If fw-version is greater than the current firmware version, the device starts OTA.
  • If fw-version is equal but fw-build is greater, the device starts OTA.
  • Otherwise, the device logs that no update is needed.

5. MQTT Device Heartbeat

The firmware publishes an application-level heartbeat over MQTT after it has connected and subscribed successfully.

Publish topic used by the device:

{mqtt.subscribe-topic}/heartbeat

Example:

soundbox/QF100123456/down/heartbeat

Payload:

{
  "header": {
    "category": 3
  },
  "data": {
    "dev-sn": "QF100123456",
    "client-id": "soundbox-QF100123456",
    "fw-version": "1.0.0",
    "fw-build": 1,
    "time": "20260607123045",
    "battery-level": 80,
    "wifi-ap": {
      "ssid": "testap1",
      "mac": "00:0f:e2:4e:aa:3e",
      "rssi": -13
    },
    "main-cell-info": {
      "mcc": 460,
      "mnc": 0,
      "rssi": 20
    }
  }
}

Timing:

  • The device publishes one heartbeat immediately after MQTT subscribe succeeds.
  • The device then publishes periodically using mqtt.keep-alive seconds from the config response.
  • If mqtt.keep-alive is 0, firmware falls back to 60 seconds.

Backend handling:

  • Subscribe to {mqtt.subscribe-topic}/heartbeat, or a wildcard such as soundbox/+/down/heartbeat.
  • Treat each heartbeat message as the device's last-seen timestamp.
  • This is separate from MQTT protocol PINGREQ/PINGRESP, which is handled by the broker and is not delivered as a subscribed message.

Heartbeat Fields

Field Type Required Notes
header.category number yes Always 3 for device heartbeat.
data.dev-sn string yes Device serial number.
data.client-id string yes MQTT client ID from config response.
data.fw-version string yes Firmware version.
data.fw-build number yes Firmware build number.
data.time string yes Device local time in YYYYMMDDHHMMSS format.
data.battery-level number yes Battery percentage calculated by firmware, 0 to 100.
data.wifi-ap object optional Present only when WiFi data is available.
data.wifi-ap.ssid string optional Current configured WiFi SSID.
data.wifi-ap.mac string optional AP MAC/BSSID, included only when the firmware can match it from WiFi scan result.
data.wifi-ap.rssi number optional Connected WiFi RSSI from SDK.
data.main-cell-info object optional Present only when GPRS/cellular data is available.
data.main-cell-info.mcc number optional Parsed from IMSI when available.
data.main-cell-info.mnc number optional Parsed from IMSI when available.
data.main-cell-info.rssi number optional Cellular signal quality reported by the modem API. Current firmware does not expose LAC/cell-id through available SDK headers.

Important:

  • wifi-ap and main-cell-info are optional. Backend should not reject heartbeat if either object is absent.
  • wifi-ap.mac is optional because the current SDK exposes AP MAC through scan results, not a direct "current BSSID" API.
  • main-cell-info.lac and main-cell-info.cell-id are not sent by this firmware build because no SDK API for those values is available in this repo.

6. Unsupported Categories

For any category other than 1 or 2, the firmware still requires:

{
  "header": {
    "category": 99
  },
  "data": {}
}

The current firmware does not perform any action for unsupported categories.

7. OTA Check API Response

After an OTA trigger, the device calls the update check API configured by UPDATE_ADDR.

The response parsed by firmware is:

{
  "error-code": 0,
  "version": "1.0.1",
  "build": 2,
  "file-length": 123456,
  "download-url": "https://example.com/app_fota.bin"
}

Required Fields

Field Type Required Notes
error-code number yes Must be 0 for update available.
version string yes New firmware version.
build number yes New firmware build.
file-length number yes Must be greater than 0.
download-url string yes Firmware download URL.

Special error codes:

  • 1002: no update needed
  • 1005: download version not found

The firmware treats both as no-update conditions.

8. OTA Result Upload

The device uploads OTA result to RESULT_ADDR.

Request body:

{
  "dev-sn": "DEVICE_SN",
  "result": 0
}

The current firmware sends the request but does not parse the response body.

Use one downlink topic per device:

soundbox/{dev-sn}/down

Example:

soundbox/QF100123456/down

Return the topic in config response:

{
  "error-code": 0,
  "mqtt": {
    "broker-ip": "broker.example.com",
    "broker-port": 1883,
    "client-id": "soundbox-QF100123456",
    "user-name": "soundbox_user",
    "password": "secret",
    "subscribe-topic": "soundbox/QF100123456/down",
    "keep-alive": 60
  }
}

Backend can listen for heartbeat on:

soundbox/{dev-sn}/down/heartbeat

10. End-to-End Example

Config Response

{
  "error-code": 0,
  "mqtt": {
    "broker-ip": "broker.bizone.id",
    "broker-port": 1883,
    "client-id": "soundbox-QF100123456",
    "user-name": "soundbox_user",
    "password": "secret",
    "subscribe-topic": "soundbox/QF100123456/down",
    "keep-alive": 60
  }
}

Payment Publish

Publish to:

soundbox/QF100123456/down

Payload:

{
  "header": {
    "category": 1
  },
  "data": {
    "pay-amount": 25000
  }
}

OTA Trigger Publish

Publish to:

soundbox/QF100123456/down

Payload:

{
  "header": {
    "category": 2
  },
  "data": {
    "fw-version": "1.0.1",
    "fw-build": 2
  }
}

Heartbeat From Device

Subscribe to:

soundbox/QF100123456/down/heartbeat

Payload received:

{
  "header": {
    "category": 3
  },
  "data": {
    "dev-sn": "QF100123456",
    "client-id": "soundbox-QF100123456",
    "fw-version": "1.0.0",
    "fw-build": 1,
    "time": "20260607123045",
    "battery-level": 80
  }
}

11. Notes For Backend Implementation

  • Always publish valid JSON.
  • Always use JSON numbers for numeric fields.
  • Do not use strings for category, pay-amount, fw-build, broker-port, or keep-alive.
  • Keep client-id unique per device.
  • Use the device serial number dev-sn as the main device identifier.
  • The firmware logs MQTT payloads through CATStudio/DIAG, useful for debugging invalid payloads.
  • Use MQTT broker ACLs carefully: the device must be allowed to publish to {subscribe-topic}/heartbeat.