Topics
Full MQTT topic reference — topic tree, QoS, ACL scoping, and connection behaviour for PLC devices.
Every plant communicates with Voke over four MQTT topics. All topics sit under the cpi/{plantId}/ prefix, where {plantId} is the plant's UUID as provisioned in Voke admin.
Topic tree
| Topic | Direction | QoS | Published by |
|---|---|---|---|
cpi/{plantId}/telemetry | plant → Voke | 1 | plant |
cpi/{plantId}/command | Voke → plant | 1 | Voke |
cpi/{plantId}/ack | plant → Voke | 1 | plant |
cpi/{plantId}/alarm | plant → Voke | 1 | plant |
Additional topics used by the protocol but not covered on this page:
| Topic | Direction | Notes |
|---|---|---|
cpi/{plantId}/status | plant → Voke | Plant connectivity / heartbeat |
cpi/{plantId}/info | plant → Voke | Device capabilities, published retained |
cpi/{plantId}/config | Voke → plant | Configuration push from Voke |
QoS 1 guarantees at-least-once delivery. PLCs must handle duplicate messages gracefully — Voke uses the nonce (n) field in each envelope for deduplication.
Message shapes
Telemetry (cpi/{plantId}/telemetry) carries either the PLC telemetry payload (flat power/SOC/grid fields) or a sub-device snapshot envelope (the devices array). Voke distinguishes between the two by checking for a top-level devices key. See Snapshot envelope for the full sub-device wire shape.
Command (cpi/{plantId}/command) carries a PlcCommand envelope from Voke. Fields: cmdId, ts, type, p (parameters), sig. The plant must subscribe to this topic at connect time — Voke does not publish commands as retained messages, so a plant that is offline when a command is sent will miss it.
Ack (cpi/{plantId}/ack) is the plant's reply to a command. Fields: cmdId, st (status), ts, n, err?, msg?, sig?. See Commands (receipt) for the full ack contract.
Alarm (cpi/{plantId}/alarm) carries a PlcAlarm envelope. Fields: ts, n, ev (RAISE | RESOLVE), alarmId, code, sev, msg?, detail?, sig?. See Alarms upstream.
Commands are not retained. A plant that reconnects after a command was dispatched will not automatically receive it. If your use case requires command delivery to transiently offline devices, poll GET /api/v1/plants/{plantId}/commands over REST instead.
ACL scoping per listener
Each plant credential is tightly scoped to its own plant topics. No plant can read or write another plant's data regardless of auth method.
mTLS and JWT listeners (ports 8883 / 8884 local, 8886 / 8887 prod)
ACL file: docker/mosquitto/config/acl
# Pattern rules — %u is replaced with the plant UUID at auth time
pattern write cpi/%u/telemetry
pattern write cpi/%u/status
pattern write cpi/%u/ack
pattern read cpi/%u/commandsThe ACL for mTLS/JWT uses commands (plural) for backward compatibility with older firmware. New firmware should use the singular command topic — both are accepted by the broker for now, but new Voke API features (sub-device commands, template-based actions) are published on cpi/{plantId}/command. If your plant connects via mTLS or JWT, subscribe to both command and commands during the migration window.
PASSWORD listener (port 8885)
ACL file: docker/mosquitto/config/acl-devices (dynamically managed)
Per-plant entries are written programmatically by MqttPasswordService during provisioning. Each entry grants:
- write
cpi/{plantId}/telemetry - write
cpi/{plantId}/status - write
cpi/{plantId}/ack - write
cpi/{plantId}/alarm - read
cpi/{plantId}/command
No wildcards. No cross-plant access.
API internal listener (port 1883)
The Voke API connects here with the cpi_api credential, which has readwrite cpi/# — full access to all plant topics. This is the listener used for publishing commands and subscribing to all plant telemetry streams.
Connection and keepalive
- TLS: All listeners enforce TLS. PLCs must trust the Voke CA root (
ca.crt). - Keepalive: PLCs should use a keepalive of
60seconds (shown in all example clients). The broker enforces 1.5× keepalive as the session timeout; a 60-second keepalive gives a 90-second disconnect detection window. - Clean session: Use
clean = true. Voke does not rely on persistent sessions. - Reconnect: Implement exponential backoff starting at ~5 seconds. The broker is configured for up to 500 simultaneous connections.
- LWT (Last Will and Testament): Voke does not configure a server-side will on the plant connection. Plants may optionally publish their own LWT to
cpi/{plantId}/statusto signal a clean or ungraceful disconnect, but Voke detects disconnects via broker-side CONNACK monitoring rather than relying on a will.
Cross-links
- Auth methods — which listener to use and why
- Snapshot envelope — telemetry topic wire format for sub-device data
- Sub-device contract — how DeviceTemplate signals map to snapshot fields
- Commands (receipt) — command + ack envelope details
- Alarms upstream — alarm envelope details
- HMAC signing — signature algorithm shared across all message types