Overview
How PLCs connect to Voke — transport, topics, auth listeners, and the template-driven decode model.
What lives where
Plant — the MQTT bridge. One plant row in Voke = one MQTT client credential. The PLC
connects to Mosquitto as this plant identity and publishes all telemetry under
the cpi/{plantId}/ topic prefix.
Sub-devices — cabinets, meters, inverters, and batteries that sit logically underneath the plant. Sub-devices never open their own MQTT connections. The PLC collects their readings and bundles everything into a single snapshot envelope published on the plant's telemetry topic.
DeviceTemplates — signal catalogues configured in Voke admin. Each template maps either
bit positions (binary signals for cabinets) or field names (numeric signals for meters, inverters, and batteries)
to human-readable metric names. Template-driven decode happens entirely on the API side.
The PLC emits raw bitmasks and numeric field dictionaries; Voke's SubDeviceTelemetryService
resolves the template for each externalId in the snapshot and decodes signals there.
The PLC firmware has no awareness of signal names or storage schemas.
Integration surface
| Dimension | Detail |
|---|---|
| Transport | MQTT 5 over TLS |
| Listeners | mTLS (8886 prod / 8883 dev), JWT (8887 prod / 8884 dev), PASSWORD (8885 both) |
| Topic prefix | cpi/{plantId}/ |
| Subtopics | telemetry, command, ack, alarm — all singular |
| Payload format | JSON (compact, UTF-8) |
| Message size limit | 8 KB (Mosquitto message_size_limit) |
| HMAC signing | Required for mTLS and JWT devices; optional for PASSWORD devices per ACL policy |
Topic summary
| Direction | Topic | Description |
|---|---|---|
| PLC → Voke | cpi/{plantId}/telemetry | Snapshot envelope with sub-device readings |
| Voke → PLC | cpi/{plantId}/command | Command envelope from Voke |
| PLC → Voke | cpi/{plantId}/ack | Command acknowledgement |
| PLC → Voke | cpi/{plantId}/alarm | Device-originated alarm event |
Auth listeners
Three separate Mosquitto listeners handle different credential types. Pick one per plant:
| Listener | Port (prod) | Port (dev) | Credential |
|---|---|---|---|
| mTLS | 8886 | 8883 | Client certificate (Voke PKI) |
| JWT | 8887 | 8884 | RS256 JWT; username = plantId |
| PASSWORD | 8885 | 8885 | username = plantId, password via Voke admin |
All three listeners use server-side TLS. mTLS additionally requires a client certificate signed by the Voke CA. See Auth methods for a full comparison.
Architecture
The API ingestion path:
- PLC publishes a snapshot to
cpi/{plantId}/telemetry. - Mosquitto forwards it to
MqttServiceinside the API container over the internal listener (port 1883). SubDeviceTelemetryServicereceives the message, batch-resolves allexternalIdvalues indevices[]toSubDevicerows, and batch-preloads theirDeviceTemplaterecords.- Each sub-device's payload is decoded using its template (bit positions for cabinets, field names for meters, inverters, and batteries).
- Decoded readings are written to TimescaleDB as individual
telemetryrows. - Decoded signals are re-broadcast over Socket.IO
sub-device:telemetryto theplant:{plantId}room for live dashboard updates. - For plants enrolled in ESM trading, the API forwards aggregated readings to the partner's AMQP queue.
Contract in three bullets
- Publish snapshots to
cpi/{plantId}/telemetry. Flat envelope:{ts, n, sig, timestamp, devices: [...]}. - Subscribe to
cpi/{plantId}/command. Execute the command. Ack oncpi/{plantId}/ack. - Raise device-originated fault events on
cpi/{plantId}/alarm.
Next steps
- Auth methods — choose and set up a listener.
- Snapshot envelope — full field reference for the telemetry payload.
- Topics — topic tree, QoS, and retention rules.