MQTT Interface
Documentation
Rotoclear exposes 67 canonical commands for imaging, recording, streaming, AI and GPIO. All commands support correlated JSON requests through execute_v1; eight also provide a direct topic form. set_parameter contains 42 setting keys and configure_backup contains three policy keys. These are parameters, not additional command names.
Connection, topics and request contract
Use the device's configured embedded broker or external broker. The device MQTT client must be running and connected to that broker. Embedded credentials/ACLs are synchronized from eligible device users; external brokers must enforce the same per-user topic ownership. Configure broker listeners, TLS/trust and client settings through the device management interface.
The embedded internal listener uses 127.0.0.1:1883. This loopback listener is not a LAN endpoint. Use the configured external listener (TLS port 8883 by default) for remote clients.
The embedded broker uses the device’s HTTPS certificate and private key by default and follows renewal of device-managed HTTPS material. An uploaded custom MQTT server certificate is preserved until you explicitly reset it to the HTTPS certificate. Connect using a hostname or IP in the certificate’s Subject Alternative Name (SAN), and configure clients with the appropriate trusted issuer chain. /media/data/mqtt/ca.crt can contain a certificate rather than a complete issuing-CA chain; do not assume that exporting this file alone establishes normal CA-chain trust. Keep hostname and certificate verification enabled. See Certificates and TLS for provisioning.
Enable the MQTT module in Settings → Modules and configure its MQTT section on that same page. An active mqtt license is required to run the service and execute commands; configuration can still be read and saved while inactive. Choose the embedded or external broker, credentials, topic prefix and device ID, save, then start the device client. Embedded mode manages the local broker as part of startup. Confirm that the client reports connected. Configure application users and their API permissions before connecting an integration client.
| Purpose | Topic when device IDs are enabled |
|---|---|
| Versioned request | <prefix>/<device_id>/cmd/users/<username>/execute_v1 |
| Correlated response | <prefix>/<device_id>/response/users/<username>/execute_v1 |
| Direct request/response | Same paths, replacing execute_v1 with one of the eight retained direct commands |
| Per-user capability discovery | <prefix>/<device_id>/response/users/<username>/capabilities |
| Private automation state | <prefix>/<device_id>/response/users/<username>/automation_state |
| Existing shared events | <prefix>/<device_id>/events/# |
| Existing device status | <prefix>/<device_id>/camera/status |
Omit / when configured with include_device_id=false. Subscribe to replies before publishing. Always publish requests non-retained. All 67 operations accept a versioned request through execute_v1; only the eight commands below also have direct-topic handlers. Each is one operation with two transport forms, not two command names.
Replace the illustrative expiry with device-compatible Unix milliseconds strictly in the future and at most 60 seconds ahead. Calculate expiry from the current time when sending each new request; keep client and device clocks synchronized. Give every intentional action a unique request ID. Request IDs are 1–64 bytes; requests are limited to 4096 bytes. Required fields are version, request_id, command, expires_at_ms; params defaults to {} where allowed. Unknown fields/types/commands are rejected. User identity comes from the ACL-protected topic, never a payload owner field.
"version": 1,
"request_id": "image-001",
"command": "set_parameter",
"params": {
"name": "brightness",
"value": 2
},
"expires_at_ms": 1791806400000
}
Responses contain version, request_id, command, timestamp_ms, phase, success, data, and error fields when applicable. Phases are rejected, failed, accepted, applied, and completed. applied normally confirms API configuration readback; accepted reports asynchronous intent, not physical completion. GPIO direct output additionally confirms driver readback. Read-only queries use completed.
Structured mutating requests use duplicate protection scoped to the topic user. Identical ID, command, parameters and expiry replay the cached result while the request remains valid; conflicting mutation IDs are rejected. Entries expire at expires_at_ms, with a five-minute monotonic upper bound, so the normal retry window is at most 60 seconds. get_state and status always read fresh state and do not use the cache. Replies are non-retained QoS 1 and limited to 16 KiB. Asynchronous actions report progress through state queries rather than later correlated completion callbacks.
Certificates and TLS
Use the certificate controls in Settings → Modules → MQTT. Server identity, trust for incoming client certificates and trust for an external broker are separate settings. Certificate uploads and removals automatically reload or restart the affected connection; a separate reload button is not required. After a change, reconnect integration clients and check the device connection status. A successful upload reports installation, not a completed TLS handshake.
| Purpose | Provisioning |
|---|---|
| Embedded server certificate | Upload a PEM server certificate/chain and matching private key. Put the leaf certificate first, followed by issuing intermediates. An optional passphrase unlocks an encrypted uploaded key. Reset selects the device HTTPS certificate again. |
| Incoming client certificates (mutual TLS) | Upload the trusted client CA or CA bundle, then enable tls.require_client_cert. The certificate display lists every CA in the bundle. The switch only enforces client certificates when a client CA file exists; removing that CA removes this enforcement. Application username/password and topic permissions still apply. |
| External broker | Upload its trusted CA bundle. If the broker requires device client authentication, also upload a matching client certificate/key pair and enable tls.require_client_cert. Configure the external endpoint, TLS and broker credentials separately. |
Uploads check PEM readability and certificate/key matching. They do not establish that an external client trusts the chain or that the configured hostname matches its SAN. Use a correctly issued chain for normal CA validation, or an explicitly supported certificate-pinning configuration. The device skips TLS for broker hosts literally named localhost or 127.0.0.1, including external mode; use a certificate-matching named endpoint when TLS is required.
For automation, send authenticated POST /api/mqtt/certificates?kind= with multipart/form-data and NetworkSettings_rw permission. The whole request body is limited to 256 KiB. PEM fields are certificate, key, ca, and optional passphrase. Transfer private keys only over HTTPS.
| kind | Fields / effect |
|---|---|
server |
Required certificate and key; optional passphrase. Installs a custom embedded server identity. |
server_reset |
No PEM fields. Restores the device HTTPS identity. |
client_ca |
Required ca. Installs/replaces the incoming client trust bundle. |
client_ca_remove |
No PEM fields. Removes incoming client trust. |
external |
Required ca; optional certificate and key together, plus passphrase. A CA-only upload does not remove an already installed client pair. |
external_remove |
No PEM fields. Removes external CA and device client credentials. |
Success returns HTTP 200 with {"ok":true}; installation/validation errors return HTTP 400 with {"ok":false,"error":"…"}. Missing permission returns 403 and oversized uploads return 413. Inspect mqtt_certificates for certificate source, identity and CA-bundle details.
Configuration, sessions and recovery
Read mqtt_config, write configuration with NetworkSettings_rw, and inspect mqtt_config_validation_result before starting. Include mode (embedded or external) in configuration writes. Saved configuration lives in /media/data/config/mqtt.json. The management HTTP endpoints are GET /api/mqtt/config, POST /api/mqtt/config and GET /api/mqtt/status.
Use mqtt_action with an action value of start_client, stop_client, restart_client, restart_broker, reload_acl or reload_certificates. The HTTP POST /api/mqtt/actions interface accepts the four restart/reload actions, not start/stop. Starting applies saved configuration; observe client/broker state after asynchronous transitions. Module activation, including activation during application startup, starts MQTT from saved settings. The exposed startup.auto_start field is not used to gate this activation path; do not rely on it to keep an active module stopped after reboot.
connection.session_persistent defaults to false (clean session); existing saved values are preserved. Enabling it lets the broker retain subscriptions and queue eligible messages during disconnection. Queued direct commands have no expiry and can execute after reconnection. Prefer expiring execute_v1 requests for controls. This setting is distinct from the application result cache and from durable broker storage. The outgoing in-memory queue is bounded by connection.publish_queue_limit and drops its oldest item when full.
For an external broker, the application derives the caller from the topic username; it cannot inspect the publishing client’s authenticated broker identity. Configure broker ACLs so each client can publish only its own command topics and read only its own private responses. Only the device service identity may publish responses/capabilities. TLS alone does not enforce these ownership rules.
Direct-topic commands
These names are also valid inside an execute_v1 envelope. New clients should use the envelope for client-chosen IDs, expiry and duplicate protection.
| Commands | Direct payload | Envelope params and behavior |
|---|---|---|
take_picture |
Ignored | {}; asynchronous capture through the shared API |
video_start |
Empty or {} |
{}; normal recording only |
video_stop |
Ignored | {}; generic stop for the current recording/pending start |
set_rpm0, set_rpm1 |
Raw JSON integer | {"value":1500}; target RPM for the selected head, not measured RPM |
rtsp_on, rtsp_off |
Ignored | {}; RTSP service intent |
status |
Ignored | {}; derived summary with MQTT uptime, device ID, software version and recording intent |
RPM values must be nonnegative signed-32-bit integers. This is a representation bound, not a hardware-certified speed range. The existing API motor behavior remains in effect. Direct deliveries get generated IDs and have no duplicate protection. status replies on the user's response topic and, for direct requests, preserves a limited shared events/status snapshot. It is distinct from get_state, which reads selected raw API fields.
Retired duplicate names and migration
Breaking change: the following 23 names no longer execute, on either direct topics or execute_v1. They return COMMAND_REPLACED with a replacement description and are absent from capability discovery. Publish replacement requests through execute_v1; do not merely rename the old direct topic. Replies then arrive on the execute_v1 response topic and use the result envelope's data object.
| Retired names | Canonical replacement | Reason |
|---|---|---|
light_0_on, light_0_off, light_1_on, light_1_off |
light0_on, light0_off, light1_on, light1_off |
Four identical boolean writes |
camera_head_0, camera_head_1 |
select_head0, select_head1 |
Identical head-selection writes |
stream_freeze, stream_unfreeze |
freeze_stream_on, freeze_stream_off |
Identical freeze writes |
backup_start, backup_stop |
visual_backup_on, visual_backup_off |
Identical backup-intent writes |
set_light_0_brightness, set_light_1_brightness |
set_parameter, name light0Brightness / light1Brightness, with value |
Already covered by the typed allowlist |
set_focus_cam0_sensor0, set_focus_cam0_sensor1, set_focus_cam1_sensor0, set_focus_cam1_sensor1 |
set_parameter, matching focusCam0Sensor0 / focusCam0Sensor1 / focusCam1Sensor0 / focusCam1Sensor1, with value |
Already covered by the typed allowlist |
select_sensor_cam0, select_sensor_cam1 |
head0_sensor0 / head0_sensor1, or head1_sensor0 / head1_sensor1 |
Choose the fixed command matching the former index |
camera_settings |
get_state with the seven image keys listed below |
Same readable values, explicit key selection |
stream |
get_state with the six stream keys listed below |
Same readable values, explicit key selection |
system |
get_state with systemDescription, systemPerformance |
Both fields are now in MQTT's read allowlist |
autofocus_center |
set_parameter, name:"tapToFocusView", value:{"x":0.5,"y":0.5} |
Preset of an existing typed control |
zoom_reset |
set_parameter, name:"zoomState", value:{"zoom":1} |
Preset of an existing typed control |
Former camera_settings keys: exposure, brightness, contrast, whiteBalance, noiseSuppression, sharpness, saturation.
Former stream keys: resolution, fps, streamingResolution, streamingFps, streamingBitrate, freeze.
For system, former response labels description and performance become raw state keys systemDescription and systemPerformance in data. There is no separate grouped-query handler or duplicated top-level result data.
The optional video_start.mode input was also removed. Use video_start, timelapse_start, or slowmotion_start, each with {}. Even mode:"normal" is rejected; empty parameters give one explicit command per recording mode. slowmo is not a separate alias.
video_stop, timelapse_stop, and slowmotion_stop remain distinct: the latter two require the matching active mode, while video_stop is generic. Opposite on/off actions and different head/sensor targets remain separate because they request different state changes. Shared status-event topics remain available; removing grouped query commands does not remove those event topics.
Fixed operational commands
Head 0 and head 1 commands address the numbered camera, light or disc independently. select_head0/1 selects the active view; headN_sensorM selects that head’s sensor. Layout commands select single view, the named picture-in-picture corner, or side-by-side. Automatic exposure, white balance and noise suppression toggle the corresponding image setting; auto-rotate and disc controls address the named head. Stream/record time/date commands toggle those overlays. Audio commands toggle recording or backup audio, subject to configured audio input. visual_backup_on/off sets backup intent for the configured backup storage.
These names use empty {} parameters. Three also appear in the direct-topic table above and are counted once. All others require execute_v1.
| Area | Commands |
|---|---|
| Capture/recording | take_picture, video_start, video_stop, timelapse_start, timelapse_stop, slowmotion_start, slowmotion_stop |
| Lights/freeze | light0_on, light0_off, light1_on, light1_off, freeze_stream_on, freeze_stream_off |
| Head/sensor | select_head0, select_head1, head0_sensor0, head0_sensor1, head1_sensor0, head1_sensor1 |
| Layout | layout_single, layout_pip_bottom_left, layout_pip_top_left, layout_pip_top_right, layout_pip_bottom_right, layout_side_by_side |
| Automatic image modes | auto_exposure_on, auto_exposure_off, auto_white_balance_on, auto_white_balance_off, noise_suppression_on, noise_suppression_off |
| Orientation | head0_auto_rotate_on, head0_auto_rotate_off, head1_auto_rotate_on, head1_auto_rotate_off |
| Disc motors | head0_disc_on, head0_disc_off, head1_disc_on, head1_disc_off |
| Backup | visual_backup_on, visual_backup_off |
| Overlays | stream_time_on, stream_time_off, stream_date_on, stream_date_off, record_time_on, record_time_off, record_date_on, record_date_off |
| Audio | record_audio_on, record_audio_off, backup_audio_on, backup_audio_off |
Head/sensor/focus/layout availability is checked. Shared API recording services enforce camera/storage and resource restrictions. timelapse_stop and slowmotion_stop require the matching active mode. MQTT video_stop remains a generic stop for legacy compatibility.
| Command | Parameters | Purpose |
|---|---|---|
set_parameter |
{"name":"brightness","value":2} |
One of the 42 validated settings below |
configure_backup |
{"name":"backupNumberOfSavedHours","value":24} |
Separate storage-policy operation |
get_state |
{"keys":["kiControl","gpioInput","gpioOutput"]} |
1–16 allowlisted state keys; read permission required for every key |
| Group | Accepted keys |
|---|---|
| Image | brightness, saturation, expo, gain, contrast, sharpness, redGain, blueGain |
| Illumination/focus | light0Brightness, light1Brightness, focusCam0Sensor0, focusCam0Sensor1, focusCam1Sensor0, focusCam1Sensor1 |
| Resolution/FPS | fotoResolution, resolution, streamingResolution, fps, streamingFps |
| Quality/codec | pictureQuality, streamingQuality, recordingQuality, slowmoQuality, streamingFormat, recordingFormat |
| Special recording rates | highSpeedMode, timelapseInputFps, timelapseOutputFps |
| Framing | tapToFocusView, zoomState |
| Bitrate | streamingBitrate, streamingMaxBitrate, recordingMaxBitrate |
| Overlay style | streamingTimestampSetting, recordingTimestampSetting |
| Naming | changeVideoName, changeImageName, changePostName, changeBackupPostName |
| Orientation/pre-roll | alignmentStream0, alignmentStream1, prerollSettings |
Typed parameters
set_parameter requires exactly params.name and params.value. Only these names are exposed:
| Names | JSON type and accepted values |
|---|---|
streamingBitrate |
Integer 0…100,000,000 bits/s; 0 restores quality-based selection; effective encoder output is still limited by the configured cap |
streamingMaxBitrate, recordingMaxBitrate |
Integer 10,000,000…100,000,000 bits/s |
streamingTimestampSetting, recordingTimestampSetting |
Nonempty object patch: x, y numbers 0…1; scaleValue number 0.4…2.2; drawBackdrop boolean; backdropColor, textColor integers 0…4,294,967,295 in the API's packed color format |
prerollSettings |
Nonempty object patch: enabled boolean and/or durationSec integer 15…30 |
changeVideoName, changeImageName |
String filename prefix, 0…17 bytes; reserved value Backup rejected |
changePostName, changeBackupPostName |
String filename suffix, 0…55 bytes |
alignmentStream0, alignmentStream1 |
String "0" for normal orientation or "1" for the alternate hardware orientation; corresponding head must exist; not an angle in degrees |
brightness, saturation |
Integer −7…7 |
expo |
Number 0.01…1000, in existing API exposure units |
gain |
Integer 0…42 |
contrast |
Number 1…2.5 |
sharpness |
Integer 0…15 |
redGain, blueGain |
Integer −128…127 |
light0Brightness, light1Brightness |
Integer 0…100; connected, dimmable head required |
focusCam0Sensor0, focusCam0Sensor1, focusCam1Sensor0, focusCam1Sensor1 |
Integer 0…1023; corresponding focus motor required |
pictureQuality |
Integer 10…90 |
streamingQuality, recordingQuality, slowmoQuality |
Integer 1…100 |
fotoResolution, resolution, streamingResolution |
String HD, FullHD, 4K |
fps, streamingFps |
Integer 1, 5, 10, 15, 24, 30, 60 |
highSpeedMode |
Integer 120, 240, 480, 600, 720, 960 |
timelapseInputFps |
Number 0.01…1 |
timelapseOutputFps |
Number 1…60 |
streamingFormat |
String h264, hevc |
recordingFormat |
String h264, hevc, mjpg |
tapToFocusView |
Object with both x and y, numbers 0…1 in the current view |
zoomState |
Nonempty object: zoom number 1…4, or up to 7 for a head with tele-sensor focus support; pan uses both left and top, numbers 0…1 |
For example:
Recording/backup setting restrictions from the API still apply. A rejected setter or readback mismatch returns failed. Zoom and autofocus return accepted because hardware motion is asynchronous. These controls do not introduce new guarantees beyond the API's hardware behavior.
"version": 1,
"request_id": "plc-004",
"command": "set_parameter",
"params": {
"name": "tapToFocusView",
"value": {
"x": 0.4,
"y": 0.6
}
},
"expires_at_ms": 1791806400000
}
Examples for structured settings
For overlays and pre-roll, omitted fields retain their previous values. Unknown fields, empty objects and string-encoded objects are rejected. The result's data.value contains the full accepted object; numeric integer/float representation differences do not cause false failures. prerollSettings describes configuration; read prerollStatus to check active buffering, duration and capacity. Special recording modes can suspend buffering even when configured enabled.
Filename components accept ASCII letters, digits, hyphen, underscore and dot. Empty strings clear the component and restore normal API naming behavior. Spaces, non-ASCII text, directory separators, NUL, . and .. are rejected before invoking the API, so names are not silently sanitized or truncated. Naming changes affect subsequent captures/files, not names of existing recordings. The 17/55-byte limits match the current API setter's inclusive substring limits.
Bitrate caps use the existing API's 10–100 Mbit/s bounds. The target bitrate's 100 Mbit/s ceiling is an command contract limit; it is not a claim that all codecs or modes can achieve it. Existing recording resolution/FPS changes may recompute the recording cap. Query accepted settings after such changes.
"version": 1,
"request_id": "overlay-001",
"command": "set_parameter",
"params": {
"name": "streamingTimestampSetting",
"value": {
"x": 0.05,
"y": 0.9,
"scaleValue": 1.2,
"drawBackdrop": true
}
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "preroll-001",
"command": "set_parameter",
"params": {
"name": "prerollSettings",
"value": {
"enabled": true,
"durationSec": 30
}
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "name-001",
"command": "set_parameter",
"params": {
"name": "changePostName",
"value": "Part_123"
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "bitrate-001",
"command": "set_parameter",
"params": {
"name": "streamingMaxBitrate",
"value": 20000000
},
"expires_at_ms": 1791806400000
}
Backup retention policy
configure_backup invokes only the following three API setters with storage-policy permission; it cannot change storage credentials or select a storage device.
params.name |
params.value |
|---|---|
backupUseCompleteSpace |
Boolean; choose use-all-space versus configured retention capacity |
backupNumberOfSavedHours |
Integer 1…8760 hours; backup storage must be available |
backupOnReachingRecordingTimeCapacityAction |
String stopRecording or reduceMaxTime |
The 8760-hour ceiling is a deliberate command limit, not a storage-capacity guarantee. The API may reduce the requested hours to fit available storage. A successful retention result includes requested, value, configured_hours and adjusted. For example, requested:24, value:12, adjusted:true means the API selected 12 hours. The exposed write key is backupNumberOfSavedHours; backupNumberOfSavedHoursSet is read-only and reports effective configuration.
These settings use existing backup behavior, including capacity notifications, stopping/reducing retention at capacity, and automatic cleanup of eligible backup media. Changes apply as individual operations, not as an atomic multi-setting transaction. Read the policy settings and remaining capacity after configuring them.
"version": 1,
"request_id": "retention-001",
"command": "configure_backup",
"params": {
"name": "backupNumberOfSavedHours",
"value": 24
},
"expires_at_ms": 1791806400000
}
AI commands
| Command | Parameters | Required API write permission |
|---|---|---|
ai_control |
Nonempty patch of active boolean, project enum, confidence number 0–1 |
RecordingSettings_rw or Recording_rw |
ai_model |
project, model string up to 128 bytes; empty model clears selection |
RecordingSettings_rw |
ai_runtime |
project, nonempty settings object |
RecordingSettings_rw or SystemSettings_rw |
Projects: humandetect, spandetekt, motiondetect. Starting AI requires an active license, connected camera, available processing resources and a selected installed model where the project requires one. motiondetect does not use a selectable model. Stop remains possible when the license is inactive. Stop AI and wait for process exit before changing project/confidence; an active project's model cannot be changed.
Runtime settings accept displayMode (all, or relevant for spandetekt), showTool, profile, autoTag, deletable, window (0.3–60), integer triggerTime/gap/cooldown (0–3600), classes, and ampel. Profiles must be advertised by the selected model. Class maps allow at most 16 entries; class keys have at most 64 bytes. Numeric class/traffic-light thresholds are 0–10000; positive warn must not exceed positive crit. ampel requires window and classes. These are transport bounds and count-based thresholds, not necessarily confidence probabilities.
Omitted top-level settings are preserved. Supplied nested class/traffic-light objects replace those nested values. Null reset, arbitrary runtime keys, model upload/delete and producer ingestion are not exposed. AI start/stop results report accepted; use kiControl, kiProjects, and processAmpel for actual process/decision state. Stored runtime settings still require producer support. These services are shared with OPC but execute with the MQTT caller's permissions.
"version": 1,
"request_id": "ai-start-001",
"command": "ai_control",
"params": {
"project": "motiondetect",
"confidence": 0.35,
"active": true
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "ai-model-001",
"command": "ai_model",
"params": {
"project": "humandetect",
"model": "installed-model.hef"
},
"expires_at_ms": 1791806400000
}
Runtime settings accept displayMode (all, or relevant for spandetekt), showTool, profile, autoTag, deletable, window (0.3–60), integer triggerTime/gap/cooldown (0–3600), classes, and ampel. Profiles must be advertised by the selected model. Class maps allow at most 16 entries; class keys have at most 64 bytes. Numeric class/traffic-light thresholds are 0–10000; positive warn must not exceed positive crit. ampel requires window and classes. These are transport bounds and count-based thresholds, not necessarily confidence probabilities.
{
"version": 1,
"request_id": "ai-runtime-001",
"command": "ai_runtime",
"params": {
"project": "spandetekt",
"settings": {
"autoTag": true,
"window": 2,
"ampel": {
"window": 2,
"classes": {
"0": {
"warn": 0.25,
"crit": 0.5
}
}
}
}
},
"expires_at_ms": 1791806400000
}
GPIO commands
All three require StreamSettings_rw and enabled/licensed GPIO. Command and returned single-binding pin numbers are 1–4; raw binding arrays from get_state retain API indexes 0–3. Input/output state objects use pin1…pin4 string levels "0"/"1".
| Command | Parameters | Behavior |
|---|---|---|
gpio_output |
pin, boolean value |
Checked driver write and readback; rejects an output controlled by a binding or any active legacy output task |
gpio_input_binding |
pin, action |
Persistent input-edge action: light0, light1, or recording; caller must also have the target action's write permission |
gpio_output_binding |
pin, source, source-specific fields; optional active_high boolean (default true) |
Persistent source-driven output; caller must be allowed to read that source |
| Either binding command | Positive id, delete:true only |
Delete an existing binding owned by this MQTT user |
| Output source | Additional fields |
|---|---|
vbActive, light0, light1, internet, reconstruction3d |
None |
recording |
when: start or stop |
kiAmpel |
instance: supported AI project; when: green, orange, red, or gray |
"version": 1,
"request_id": "gpio-001",
"command": "gpio_output",
"params": {
"pin": 1,
"value": true
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "gpio-input-001",
"command": "gpio_input_binding",
"params": {
"pin": 1,
"action": "recording"
},
"expires_at_ms": 1791806400000
}
{
"version": 1,
"request_id": "gpio-ai-001",
"command": "gpio_output_binding",
"params": {
"pin": 2,
"source": "kiAmpel",
"instance": "spandetekt",
"when": "red",
"active_high": true
},
"expires_at_ms": 1791806400000
}
The supported MQTT input actions remain light0, light1 and recording. The wider API gpioActions/gpioSources catalogs may contain additional entries; use this protocol’s command tables to determine accepted values. For example, freeze is not an accepted input-binding action.
For supported input actions, HIGH selects on/start and LOW selects off/stop. The current level is applied once after binding creation and GPIO initialization, then after subsequent level changes, following a 300 ms settling interval. A failed action or a later UI change is not continuously retried at the same level; another input transition is required. Duplicate pin/action combinations are rejected; input bindings are capped at 32 total and eight per MQTT user. One output binding occupies each pin. MQTT can replace its own output binding, but cannot replace or delete another user’s or an OPC/API-owned binding. Driver readback does not verify external wiring; binding creation confirms stored configuration.
MQTT binding ownership is server-assigned deviceRole:"mqtt_user" plus createdByUser:, persisted across restart. Input actions recheck GPIO and target-action permissions; output evaluations recheck GPIO and source-read permissions. Deleting a user removes that user’s MQTT input and output bindings, so recreating the same username does not inherit them. Permission revocation prevents further authorized actions/evaluations but does not automatically reset an output. Removing a binding leaves its last output level unchanged. No missing owner falls back to admin for MQTT bindings; naming a user opc-pubsub does not grant the OPC role.
Bindings keep running independently of broker connectivity while their owner's permissions allow it. The existing privileged API/OPC output-binding administration can still replace or remove output bindings. Output source inversion can make a missing/inactive source HIGH. Backup activity is requested intent; reconstruction state does not start reconstruction. Direct output levels are not persisted across reboot. General network tasks, arbitrary state writes, pulses and schedules remain outside this command interface.
AI/GPIO state and telemetry
Device availability uses separate /camera/status/online and /camera/status/lwt topics. Connection publishes retained online:true on the online topic; the Last Will publishes online:false on the LWT topic after unexpected disconnection. Do not treat a retained online message alone as proof of a current connection, or the Last Will timestamp as the exact disconnection time. Grouped state events under /events/# are retained QoS 1 snapshots. The /events/status/picture event (payload field picture_taken) is non-retained; old retained photo events are cleared when the device connects. Private request results are non-retained.
get_state includes kiControl, kiModels, kiRuntimeSettings, kiProjects, processAmpel, processGraph, reconstruction3dActive, gpioInput, gpioOutput, gpioInputBindings, gpioOutputBindings, gpioSources, and gpioActions, alongside the operational allowlist. Each read is permission-checked. Producer inputs kiSample/kiEvent remain unexposed.
The private automation_state topic publishes version, timestamp_ms, a state object and omitted_keys. It includes readable values from kiControl, kiProjects, processAmpel, gpioInput, and gpioOutput; it is sent on connect and on changes, coalesced to at most four snapshots per second. It uses non-retained QoS 1 and rechecks each user's read permissions on every send. Large fields are omitted to preserve result-size bounds; query individual fields instead. Existing shared event topics are not expanded to broadcast private AI/GPIO state. Snapshots are not correlated completion events.
State queries
get_state requires params.keys, an array of 1–16 allowed strings. Duplicate keys are returned once. Reads return current API state, not a hardware-synchronous snapshot. Every requested key requires read permission. MQTT also supports systemDescription and systemPerformance. The shared readable keys are:
cameraSwitch, cam0Exists, cam1Exists, cam0SelectedSensor, cam1SelectedSensor, headIsSlowMoCompatible, light0, light1, light0Brightness, light1Brightness, light0Dim, light1Dim, dictRotation0, dictRotation1, RPM0, RPM1, exposure, whiteBalance, noiseSuppression, brightness, expo, gain, contrast, sharpness, saturation, redGain, blueGain, pip, autoRotate0, autoRotate1, freeze, zoomState, focusView, focusCam0Sensor0, focusCam0Sensor1, focusCam1Sensor0, focusCam1Sensor1, takePicture, record, recordingMode, recordTime, recordingFilename, fotoFilename, backupActive, backupFilename, backupRemainingCapacitySeconds, fotoResolution, resolution, fps, streamingResolution, streamingFps, pictureQuality, streamingQuality, recordingQuality, slowmoQuality, streamingFormat, recordingFormat, timelapseInputFps, timelapseOutputFps, highSpeedMode, streamingWithTimeStamp, streamingWithDateStamp, recordingWithTimeStamp, recordingWithDateStamp, recordingAudioActive, backupAudioActive, streamingIsBusy, streamingBitrate, streamingMaxBitrate, recordingMaxBitrate, streamingTimestampSetting, recordingTimestampSetting, prerollSettings, prerollStatus, changeVideoName, changeImageName, changePostName, changeBackupPostName, alignmentStream0, alignmentStream1, backupUseCompleteSpace, backupNumberOfSavedHours, backupNumberOfSavedHoursSet, backupOnReachingRecordingTimeCapacityAction, kiControl, kiModels, kiRuntimeSettings, kiProjects, processAmpel, processGraph, reconstruction3dActive, gpioInput, gpioOutput, gpioInputBindings, gpioOutputBindings, gpioSources, gpioActions, fpgaTemp, oct0, oct1, psi0, psi1, swVersion, fmVersion.
Permissions and capability discovery
The broker authenticates the client and enforces topic ownership; the application separately checks the corresponding API permission for each command. Users must only publish to their own cmd/users//+ topics and subscribe to their own response/users//#. External broker administrators must enforce this separation, including protection against publishing forged responses. The device’s internal client needs access to incoming user commands and outgoing replies/telemetry.
Subscribe to your retained per-user capabilities topic for permitted commands, resolved reply paths, typed settings and envelope limits. Capabilities refresh on connection and permission changes; deleting a user clears its retained capabilities. execute_v1 describes the dispatcher and is not an extra permitted operation. Availability still depends on connected heads, storage, licenses and configuration. Every write and requested state key is permission-checked, including cached retries. Values requiring read permission are omitted when the caller lacks it.
Results, retries and errors
| Phase | Meaning |
|---|---|
rejected |
Validation, authorization, expiry, retention or queue-capacity checks prevented execution |
failed |
Execution failed or the accepted state differs from the request; read affected state before retrying |
accepted |
Asynchronous action accepted; observe state for progress |
applied |
Configuration readback matches the requested setting, or adjusted backup retention is reported |
completed |
State query returned its data |
"version": 1,
"request_id": "image-001",
"command": "set_parameter",
"timestamp_ms": 1791806395000,
"phase": "applied",
"success": true,
"data": {
"name": "brightness",
"requested": 2,
"value": 2
}
}
| Error code | Resolution |
|---|---|
INVALID_ARGUMENT, INVALID_TOPIC |
Correct the JSON types, fields, ranges or user topic |
UNKNOWN_COMMAND, COMMAND_REPLACED |
Use the canonical command table and migration mapping |
EXPIRED |
Synchronize clocks; create a new intentional request with a fresh expiry |
RETAINED_COMMAND |
Remove retained command messages and publish without retention |
PERMISSION_DENIED |
Grant the relevant application permission and broker ACL |
UNAVAILABLE |
Check camera, storage, module and resource requirements |
REQUEST_ID_CONFLICT |
An ID already identifies different content; use a unique ID for new work |
QUEUE_FULL |
The mutation cache reached 64 entries for this user or 1024 globally; wait for request expiry before retrying |
NOT_APPLIED, EXECUTION_ERROR |
Inspect error_message and read current state before another action |
RESULT_TOO_LARGE |
Request fewer or smaller state fields |
The mutation cache holds at most 64 entries per user and 1024 globally. When either limit is reached, new structured mutations return QUEUE_FULL until entries expire; read-only queries do not consume slots. Cache state survives broker reconnects in the same application process, but not application restart. Retry a missing mutation reply with the identical envelope, including expiry, only while that expiry is valid. After expiry, query current state before deciding whether a new action is needed. Direct commands have neither expiry nor duplicate protection. The receive channel holds 100 events; overflow and requests over 4096 bytes can be dropped without a correlated reply. Receive-queue drops increment mqtt_status_update.statistics.commands_dropped. A broker delivery acknowledgement does not confirm execution.
For capture, compare takePicture and fotoFilename with their previous values. For recording, observe record, recordingMode, recordingFilename and recordTime. For AI, observe kiControl, kiProjects and processAmpel. A filename alone does not establish that writing has finished. Other clients can change the same state.
Client example
Install the Python paho-mqtt client in your integration environment. Set MQTT_HOST, MQTT_USER, MQTT_PASSWORD, MQTT_CA, and MQTT_BASE for your configuration. This example uses TLS, subscribes before publishing, and makes a read-only state request. The example requires Paho MQTT 2.x.
import paho.mqtt.client as mqtt
base = os.environ["MQTT_BASE"] # prefix/device_id, or prefix only
user = os.environ["MQTT_USER"]
request_topic = f"{base}/cmd/users/{user}/execute_v1"
reply_topic = f"{base}/response/users/{user}/execute_v1"
request_id = str(uuid.uuid4())
done = threading.Event()
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print("Connection rejected:", reason_code)
done.set()
return
client.subscribe(reply_topic, qos=1)
def on_subscribe(client, userdata, mid, reason_codes, properties):
if any(code.is_failure for code in reason_codes):
print("Subscription rejected")
done.set()
return
request = {"version": 1, "request_id": request_id,
"command": "get_state", "params": {"keys": ["cam0Exists", "cam1Exists"]},
"expires_at_ms": int(time.time() * 1000) + 30000}
client.publish(request_topic, json.dumps(request), qos=1, retain=False)
def on_message(client, userdata, message):
result = json.loads(message.payload)
if result.get("request_id") == request_id:
print(json.dumps(result, indent=2))
done.set()
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
client.username_pw_set(user, os.environ["MQTT_PASSWORD"])
client.tls_set(ca_certs=os.environ["MQTT_CA"])
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.connect(os.environ["MQTT_HOST"], int(os.getenv("MQTT_PORT", "8883")))
client.loop_start()
if not done.wait(35):
print("No correlated result received; check connection and device state")
client.disconnect()
client.loop_stop()