OPC UA PubSub Interface
Documentation

Rotoclear provides 64 commands through brokerless UDP UADP PubSub: 55 fixed commands, eight typed write groups and one state query. Use this manual to configure a PLC or other PubSub peer and control imaging, recording, AI and GPIO. This interface uses PubSub datasets rather than remotely callable OPC UA Client/Server methods.

  • Http API
  • WebSocket API
  • Camera stream
  • State Description
  • AI module
  • GPIO module
Setting Command Legacy status Structured result
Sender Peer Device Device
Destination opc.udp://<device>:4840/ opc.udp://<publisher_host>:4843/ Same as status
PublisherId, encoded as UInt16 2234 2235 2235
WriterGroupId 100 101 102
DataSetWriterId 62541 62542 62543
Fields One scalar String Command One scalar String Status One scalar String Result
Example light0_on or JSON below ok JSON result below

Use transport profile http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp and Variant field encoding. Include publisher ID, group header, writer group ID and payload header. Configure a separate DataSetReader for Result; its metadata has one scalar String field. Results have their own WriterGroup so the legacy status dataset remains unchanged.

Set publisher_host to the reachable address of the peer receiving status and results. An empty value may be saved during setup, but worker startup rejects it. The destination is configured explicitly and is not discovered from incoming commands. The device binds to 0.0.0.0 by default. Dataset IDs must match the peer; result writer-group and dataset-writer IDs must differ from the corresponding status IDs. Both outgoing groups use the same configured destination.

Both outgoing groups publish every 500 ms and also publish when their values change through command processing. Until a first result exists, the Result string is empty. Periodic repeats are not new operations: correlate by request_id.

The internal namespace defaults to urn:rotoclear:opc:pubsub, object rotoclear. Remote clients do not need node IDs. Internal NoOfCommandsReceived is not a published dataset field.

Security and enabling commands

Configure both peers with the same security_mode (None, Sign, SignAndEncrypt), security_policy (Aes128Ctr, Aes256Ctr), token ID and key bytes. Defaults are SignAndEncrypt, Aes128Ctr, token 1. Status and result use the same policy. Token ID alone is not an enforced authorization boundary: messages with matching keys can be accepted with a different token ID. Protect and replace the actual keys when revoking access.

The device automatically generates cryptographically random keys at /media/data/config/certs/pubsub_keys.bin when secured OPC UA is first started or its keys are first downloaded. Keys persist across application restarts and firmware updates; existing keys are retained. The binary file contains the signing key, encryption key and key nonce, in that order:

Network configuration

Policy Signing key Encryption key Nonce Total
Aes128Ctr 32 bytes 16 bytes 4 bytes 52 bytes
Aes256Ctr 32 bytes 32 bytes 4 bytes 68 bytes

In Settings → Modules → OPC UA, save the security configuration, then use Security Status → Download to obtain pubsub_keys.bin. No manual key generation is needed. The download requires an active OPC UA module and NetworkSettings_rw permission. Use HTTPS and keep the file private: possession permits publishing commands as well as decoding messages. Configure the peer with the downloaded bytes, matching policy and token ID.

The download endpoint is GET /opc/pubsub-keys, using the same authenticated session as the settings UI. Responses are attachments with Cache-Control: no-store. While the worker is running, downloads return its startup keys; restart after changing settings to use the newly saved configuration. When stopped, downloads use the saved configuration and generate missing default keys. A missing custom key path or invalid key length returns an error without replacing existing keys. Changing between AES policies requires a matching key file; automatic generation does not rotate an existing installation's keys. Custom files can still be provisioned by an administrator. Factory resets that remove the persistent configuration require peers to be provisioned again.

Custom pubsub.keys_file paths must be normalized absolute paths inside /media/data/config/certs/; symbolic-link key files are rejected. Keys are read into the worker at startup. Changing the file or saved security settings does not rotate an active worker’s keys: stop, provision matching keys for both peers and restart. There is no automatic key rotation.

None requires no keys. PubSub security authenticates possession of group keys; it does not provide a separate application user account for each publisher.

Enable and configure OPC UA in Settings → Modules → OPC UA. The module/license identifier is opcua; runtime startup requires an active license. Configuration remains readable and writable with the appropriate network permissions while the module is inactive.

Settings persist in /media/data/config/opc.json. The authenticated configuration interface requires NetworkSettings_rw for writes:

1. Write opc_config with addresses, security, dataset IDs, and the desired canonical command names in methods. 2. Check opc_config_validation_result.

3. Write opc_action value {"action":"stop_opc_server"}, then {"action":"start_opc_server"}.

4. Check opc_status_update.server.state is "started".

An explicit start_opc_server action with an active license saves autostart:true; stop_opc_server saves autostart:false. With autostart enabled, module activation/application startup attempts to start the worker from saved settings. This records requested startup, not proof of a successful bind: inspect opc_status_update and its error details. Correct an invalid destination or occupied port before retrying start. License deactivation stops runtime execution.

The persisted allowlist is called enabled_methods; the configuration observable calls it methods. Existing saved allowlists are retained. An installation that enabled only the original 13 commands must explicitly add the desired new commands and restart. Defaults for a new configuration and the shipped template include all 64 command names. The running worker uses a snapshot of settings until restarted.

commands maps canonical names to custom legacy strings through the configuration API; the Command Strings editor is hidden in the settings UI. Use the documented default strings unless your installation explicitly provisions alternatives. Mappings must be unique, nonempty, at most 63 UTF-8 bytes, without surrounding whitespace or NUL, and must not begin with {. Matching is exact and case-sensitive. Structured requests use canonical names and the same method allowlist.

set_parameter accepts 42 setting names; configure_backup accepts three policy names. AI and GPIO use six independently enabled command groups.

The adapter exposes only the controls and read keys listed below. It does not expose arbitrary observables, user administration, network reconfiguration, firmware updates, reboot, factory reset, file transfer or media deletion.

Requests

Do not send raw JSON in a UDP datagram: encode the JSON text as the scalar String field inside a UADP DataSetMessage. OPC requests do not accept MQTT’s expires_at_ms field.

Legacy clients can continue sending one fixed command string, for example video_start. The legacy ok means the request entered the state-thread queue; it is not execution success. Read Result to learn whether validation and execution succeeded. Legacy requests receive generated IDs and have no duplicate protection: repeated publishes repeat actions where the API permits them.

Legacy strings remain accepted in secured modes as well as None. Incoming command values are not debounced or filtered merely because the same value repeats. A cyclic PLC publisher must not send a legacy action every scan unless repeated execution is intended. Use structured requests with stable IDs for retries. Signing/encryption does not add an application-level expiry or durable replay history; the in-memory mutation cache is limited to five minutes.

For correlation, parameters and duplicate protection, put this JSON text in the same Command String:

Requests contain 1–1024 bytes without NUL. version must be integer 1; request_id must be a nonempty string of at most 64 bytes. Unknown fields, wrong JSON types, unsupported values and unexpected fixed-command parameters are rejected. params is optional only for fixed commands. Send numbers/objects as JSON numbers/objects, not strings containing them.

"version": 1,
  "request_id": "plc-001",
  "command": "select_head1"
}
{
  "version": 1,
  "request_id": "plc-002",
  "command": "set_parameter",
  "params": {
    "name": "light1Brightness",
    "value": 60
  }
}
{
  "version": 1,
  "request_id": "plc-read-003",
  "command": "get_state",
  "params": {
    "keys": [
      "record",
      "recordingMode",
      "recordingFilename",
      "recordTime"
    ]
  }
}

Fixed commands

Capture, recording, illumination and freeze controls:

Commands Action
take_picture Request a JPEG capture
video_start, video_stop Start/stop normal manual recording
timelapse_start, timelapse_stop Start/stop timelapse recording
slowmotion_start, slowmotion_stop Start/stop slow-motion recording
light0_on, light0_off, light1_on, light1_off Switch the numbered head’s light
freeze_stream_on, freeze_stream_off Set stream freeze

Capture and recording use the shared API lifecycle, storage checks, hardware arbitration, configured filenames and media registration. Starts require a connected camera and suitable recording storage; slow motion also requires a compatible head. Stop commands must match the active recording mode. OPC and API clients share recording state.

Head selection, image settings and operational controls:

Commands API setting/action
select_head0, select_head1 cameraSwitch false/true; requested head must exist
head0_sensor0, head0_sensor1, head1_sensor0, head1_sensor1 Selected sensor for the specified head; sensor must exist
layout_single pip=0
layout_pip_bottom_left, layout_pip_top_left, layout_pip_top_right, layout_pip_bottom_right pip=1,2,3,4 respectively; both heads required
layout_side_by_side pip=5; both heads required
auto_exposure_on, auto_exposure_off exposure
auto_white_balance_on, auto_white_balance_off whiteBalance
noise_suppression_on, noise_suppression_off noiseSuppression
head0_auto_rotate_on, head0_auto_rotate_off, head1_auto_rotate_on, head1_auto_rotate_off autoRotate0/1
head0_disc_on, head0_disc_off, head1_disc_on, head1_disc_off dictRotation0/1; existing API motor behavior
visual_backup_on, visual_backup_off backupActive, the desired backup setting
autofocus_center tapToFocusView={"x":0.5,"y":0.5}; selected sensor needs focus support
zoom_reset zoomState={"zoom":1.0}
stream_time_on, stream_time_off, stream_date_on, stream_date_off Stream timestamp/date overlay
record_time_on, record_time_off, record_date_on, record_date_off Recording timestamp/date overlay
record_audio_on, record_audio_off recordingAudioActive
backup_audio_on, backup_audio_off backupAudioActive

Settings apply through the API setters. applied confirms application state, not motor speed, exposure convergence, audible audio or actual backup writing. Backup requires suitable configured storage; audio still depends on the device's audio configuration.

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
    }
  }
}

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 OPC 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
    }
  }
}
{
  "version": 1,
  "request_id": "preroll-001",
  "command": "set_parameter",
  "params": {
    "name": "prerollSettings",
    "value": {
      "enabled": true,
      "durationSec": 30
    }
  }
}
{
  "version": 1,
  "request_id": "name-001",
  "command": "set_parameter",
  "params": {
    "name": "changePostName",
    "value": "Part_123"
  }
}
{
  "version": 1,
  "request_id": "bitrate-001",
  "command": "set_parameter",
  "params": {
    "name": "streamingMaxBitrate",
    "value": 20000000
  }
}

Backup retention policy

Enable configure_backup explicitly in a saved installation's opc_config.methods and restart the worker to use this command. A saved allowlist containing set_parameter alone does not authorize backup-policy writes. 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
"version": 1,
  "request_id": "retention-001",
  "command": "configure_backup",
  "params": {
    "name": "backupNumberOfSavedHours",
    "value": 24
  }
}

The 8760-hour ceiling is a deliberate OPC 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.

AI commands

Enable the required names in opc_config.methods, then restart the OPC worker: ai_control, ai_model, ai_runtime. Existing saved allowlists are not expanded automatically. These are separate operations, not extra names accepted by set_parameter.

Command params Behavior
ai_control Nonempty object with optional active boolean, project string, confidence number 0…1 Start/stop intent or configure project/confidence
ai_model project and model strings Select an installed model; empty model clears selection
ai_runtime project string and nonempty settings object Merge validated per-project runtime configuration

Projects are humandetect, spandetekt, and motiondetect. The last does not use a selectable model. Model/profile names must match installed model metadata, not filesystem paths. Read kiModels to discover names and advertised profiles.

AI activation requires an active AI license, a connected camera, no exclusive recording conflict, and an installed selected model for projects that require one. Model and runtime changes also require the license. Stop AI and wait for it to exit before changing the running project's model or changing control project/confidence. Reasserting active:true on an already-running local process does not request a restart. Project/confidence can be configured while inactive; changing these fields does not itself activate AI.

Use an actually installed model name instead of the example detector.hef. Start/stop results are accepted; process startup, shutdown and inference are asynchronous. Follow kiControl.status/error, kiProjects, processAmpel and processGraph through get_state. The process reporting running does not prove that a first inference result exists. No per-request completion event is emitted.

"version": 1,
  "request_id": "ai-model-001",
  "command": "ai_model",
  "params": {
    "project": "humandetect",
    "model": "detector.hef"
  }
}
{
  "version": 1,
  "request_id": "ai-start-001",
  "command": "ai_control",
  "params": {
    "project": "humandetect",
    "confidence": 0.35,
    "active": true
  }
}
{
  "version": 1,
  "request_id": "ai-stop-001",
  "command": "ai_control",
  "params": {
    "active": false
  }
}

Supported ai_runtime.settings fields:

Field Type and bounds
displayMode all or relevant; relevant is exposed only for spandetekt
showTool, autoTag, deletable Boolean
profile Nonempty string ≤64 bytes; must be advertised by the selected model
window Number 0.3…60 seconds, matching sample-window limits
triggerTime, gap, cooldown Integer 0…3600 seconds; the upper bound is an OPC contract limit
classes Object of up to 16 class names/IDs to thresholds 0…10,000
ampel Object with both window (0.3…60 seconds) and classes; each class has warn and crit numbers 0…10,000

Class keys are nonempty strings ≤64 bytes without NUL and must correspond to the producer/model's class identifiers to affect evaluation. Thresholds operate on class-count averages, not necessarily confidence probabilities. Zero disables the corresponding traffic-light threshold; where both are positive, warn must not exceed crit. The 16-class and 10,000-threshold limits are OPC bounds. The 1024-byte request limit still applies.

Top-level settings omitted from the patch are preserved. Supplied classes and ampel objects replace those complete nested values, matching the API's shallow merge. Null resets, arbitrary runtime keys, producer ingestion (kiSample/kiEvent), model upload and model deletion are not exposed by these commands. applied means stored configuration; display/runtime behavior still depends on producer support. Auto-tag settings require suitable model class configuration and backup storage to produce usable tags.

"version": 1,
  "request_id": "ai-runtime-001",
  "command": "ai_runtime",
  "params": {
    "project": "spandetekt",
    "settings": {
      "autoTag": true,
      "window": 2,
      "triggerTime": 10,
      "gap": 10,
      "cooldown": 20,
      "ampel": {
        "window": 2,
        "classes": {
          "0": {
            "warn": 0.25,
            "crit": 0.5
          }
        }
      }
    }
  }
}

GPIO commands and AI-to-GPIO bindings

Enable the required names independently: gpio_output, gpio_input_binding, gpio_output_binding. The GPIO module must be enabled/licensed. Requests cannot set an owner, account, permissions, device role, arbitrary API key, URL or network task.

OPC commands and returned single-binding objects use physical pin numbers 1–4. The existing raw API binding arrays returned by get_state retain their original zero-based pin values 0–3. gpioInput/gpioOutput use pin1…pin4 with string levels "0"/"1".

Set an output level

value must be a JSON boolean. The adapter checks the driver write and reads back the driver's level before returning applied with driver_readback:true; this does not verify external wiring or a connected actuator. Writes are rejected if that output has a binding or if any legacy output task is active, including delayed tasks. A driver/readback error can occur after the write, so a failure is not proof that the level stayed unchanged. This command does not persist a desired level across reboot and does not implement pulses, scheduling or repeated waveforms.

"version": 1,
  "request_id": "gpio-level-001",
  "command": "gpio_output",
  "params": {
    "pin": 1,
    "value": true
  }
}

Bind an input to a device action

Actions are light0, light1, and recording. HIGH selects on/start and LOW selects off/stop. After creation or GPIO initialization, the current input level is applied once; later changes are applied after a 300 ms settling interval. Failed actions or later UI changes are not continuously retried while the level stays unchanged. Duplicate pin/action bindings are rejected, with 32 input bindings maximum. Different actions may share an input pin. The wider API action catalog does not expand the protocol allowlist; for example, freeze is not accepted by this command.

"version": 1,
  "request_id": "gpio-input-001",
  "command": "gpio_input_binding",
  "params": {
    "pin": 1,
    "action": "recording"
  }
}

OPC-created input bindings have a server-assigned deviceRole:"opc_pubsub" and display owner opc-pubsub. The device role is distinct from an account username and persists with the binding. Its action execution is limited to head-light and recording permissions; it never inherits the API handler's admin fallback. Clients cannot supply either ownership field. Existing API-owned bindings retain their account-based behavior.

Bind an output to device or AI state

Source Additional arguments
vbActive, light0, light1, internet, reconstruction3d None; boolean source
recording when: start or stop
kiAmpel instance: one supported AI project; when: green, orange, red or gray
"version": 1,
  "request_id": "gpio-ai-001",
  "command": "gpio_output_binding",
  "params": {
    "pin": 2,
    "source": "kiAmpel",
    "instance": "spandetekt",
    "when": "red",
    "active_high": true
  }
}

active_high is optional and defaults to true. The output follows the selected source condition, inverted when false. Missing source state is treated as inactive, so inversion can make the output HIGH in that case. vbActive reflects requested backup enablement, not proof of actual file writing. reconstruction3d is producer-reported state; binding it does not start reconstruction.

There is one output binding per pin. Creating one replaces that pin's previous binding and evaluates the new source immediately; creation is rejected while any legacy output task is active. applied confirms persistent binding configuration, not physical output readback. The shared API binding evaluator controls later output updates.

Read and remove bindings

Read gpioInputBindings, gpioOutputBindings, gpioSources, gpioActions, gpioInput and gpioOutput through get_state. Creation results include the binding ID. Remove one with its direction-specific command:

Deletion requires an existing positive integer ID. OPC input deletion is restricted to OPC-owned bindings; output deletion can remove an existing output binding. Delete requests cannot include creation fields. Removing an output binding leaves its last level unchanged; use gpio_output afterward if a particular level is required.

Bindings persist across application restarts and continue independently of the OPC worker. Removing a command from the OPC allowlist or stopping the worker prevents further configuration requests but does not remove existing bindings. Delete unwanted bindings explicitly. General HTTP/TCP/UDP input tasks, arbitrary state-write tasks, daily schedules and output pulse sequences remain on the existing API and are not part of this integration.

"version": 1,
  "request_id": "gpio-remove-001",
  "command": "gpio_input_binding",
  "params": {
    "id": 1,
    "delete": true
  }
}

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. The whitelist is:

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.