# 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: ```text 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 ```json { "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 ```json { "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 ```json { "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`. ```json { "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: ```json { "data": { "pay-amount": "15000" } } ``` Use a number instead: ```json { "data": { "pay-amount": 15000 } } ``` ## 3.1 MQTT Transport Security Current firmware setting: ```c #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: ```c #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: ```json { "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. Unsupported Categories For any category other than `1` or `2`, the firmware still requires: ```json { "header": { "category": 99 }, "data": {} } ``` The current firmware does not perform any action for unsupported categories. ## 6. 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: ```json { "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. ## 7. OTA Result Upload The device uploads OTA result to `RESULT_ADDR`. Request body: ```json { "dev-sn": "DEVICE_SN", "result": 0 } ``` The current firmware sends the request but does not parse the response body. ## 8. Recommended Topic Design Use one downlink topic per device: ```text soundbox/{dev-sn}/down ``` Example: ```text soundbox/QF100123456/down ``` Return the topic in config response: ```json { "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 } } ``` ## 9. End-to-End Example ### Config Response ```json { "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: ```text soundbox/QF100123456/down ``` Payload: ```json { "header": { "category": 1 }, "data": { "pay-amount": 25000 } } ``` ### OTA Trigger Publish Publish to: ```text soundbox/QF100123456/down ``` Payload: ```json { "header": { "category": 2 }, "data": { "fw-version": "1.0.1", "fw-build": 2 } } ``` ## 10. 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.