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:
- The device calls the config API to receive MQTT connection settings.
- 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_DEMOis enabled by default.MQTT_STRICT_TEST_DEMOis 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 thehttp://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-versionparsing 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-amountinto a 12-digit string. - Example:
15000becomes000000015000. - 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_ENABLEintosdk_MQTT_connect(). - Certificate setup hooks exist in firmware:
MQTT_SERVER_CA_CRTMQTT_CLIENT_CA_CRTMQTT_CLIENT_PRIKEYsdk_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-versionis greater than the current firmware version, the device starts OTA. - If
fw-versionis equal butfw-buildis 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-aliveseconds from the config response. - If
mqtt.keep-aliveis0, firmware falls back to60seconds.
Backend handling:
- Subscribe to
{mqtt.subscribe-topic}/heartbeat, or a wildcard such assoundbox/+/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-apandmain-cell-infoare optional. Backend should not reject heartbeat if either object is absent.wifi-ap.macis optional because the current SDK exposes AP MAC through scan results, not a direct "current BSSID" API.main-cell-info.lacandmain-cell-info.cell-idare 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 needed1005: 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.
9. Recommended Topic Design
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, orkeep-alive. - Keep
client-idunique per device. - Use the device serial number
dev-snas 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.