This is a development version of the documentation. Content may change without notice.
Voke Documentation
Concepts

Sub-devices

Sub-devices model the physical components — cabinets, meters, inverters, batteries — that live under a plant. They are never MQTT participants; all their telemetry arrives through the plant.

What is a sub-device?

A sub-device is a logical record under a plant that represents a physical field component: a distribution cabinet, an energy meter, a solar inverter, a battery pack, or any other device whose telemetry arrives via the plant's MQTT connection. Sub-devices never hold MQTT credentials and never connect to the broker directly.

Telemetry for all sub-devices in a site arrives in a single snapshot envelope published by the PLC on the plant's telemetry topic. Voke unpacks the envelope, resolves each entry to a sub-device row, applies the appropriate device template to decode signals, and fans out one telemetry row per signal.


externalId

externalId is a short, operator-assigned identifier that the PLC firmware writes into each snapshot entry. It is:

  • Unique within a plant — enforced by sub_devices_plant_external_id_unique (plantId, externalId). Two plants may have sub-devices with the same externalId.
  • Not globally unique across organizations or plants.
  • Case-sensitive and uppercase onlyR1 and r1 are distinct values. The Zod schema enforces ^[A-Z0-9_-]+$ (1–32 characters).

Conventional naming used in the field:

ComponentConventionExamples
CabinetR + numberR1, R2, R3
MeterE + numberE1, E2
Inverterproject-specificINV1, PV1, M1
Batteryproject-specificBAT1

The PLC firmware dictates these values. Voke treats them as opaque identifiers — there is no validation of the naming convention beyond the character-set and length constraints.


type

type is a typed enum (SubDeviceType from @cpi/shared/types/enums) that identifies the physical kind of the component:

export enum SubDeviceType {
  CABINET  = 'CABINET',
  METER    = 'METER',
  INVERTER = 'INVERTER',
  BATTERY  = 'BATTERY',
}

The type drives the snapshot entry shape. Cabinet entries carry a raw bitmask integer decoded by binary signals. Meter, inverter, and battery entries carry a values map decoded by numeric signals. See Signals for the decode rules.

The type is also used when assigning a device template to a sub-device — the template's type must match the sub-device's type.


Template linkage

Each sub-device may be linked to a DeviceTemplate at SUB_DEVICE level. Like the plant-to-template binding, this is validated on write:

  1. Existence — the template must exist in the global catalogue.
  2. Type match — the template's SubDeviceType must match the sub-device's type. A mismatch returns VALIDATION_ERROR.

Current implementation note: the sub-device service validates existence and type match when assigning templates. Admin UI filters to SUB_DEVICE templates, and direct API callers should do the same.

A sub-device without a template produces no decoded telemetry. The telemetry pipeline simply skips entries with templateId: null.


Auto-discovery

When SubDeviceTelemetryService receives a snapshot that references an externalId not yet present in the database, it creates a placeholder sub-device row automatically (SubDevicesService.upsertForIngest). The created row has:

  • externalId and type taken from the snapshot entry
  • name defaulted to the externalId value
  • status: ONLINE
  • metadata.autoProvisioned: true and metadata.firstSeenAt set to the ingest time
  • templateId: null — no template assigned yet

The sub_device.discovered event is emitted to the plant Socket.IO room as sub-device:discovered, prompting operators to review and attach a template. No firmware change is needed — PLCs can freely evolve the device list they publish, and Voke will track new entries automatically.


Admin surfaces

Plant detail is built around the sub-device model:

  • Overview renders template-fed KPI cards and today's energy summary. The v1 KPI helpers use Solinteg-style external IDs (PV1, GRID, BAT1, BACKUP, INV1) until a formal SubDevice.role field exists.
  • Devices renders a compact, searchable list. Each row shows externalId, name, type badge, status, and up to five featured signals from the assigned template. Row actions handle rename, template change, command dispatch, and delete.
  • Device detail opens as a responsive dialog on desktop and drawer on mobile. It keeps live signal inspection above the fold, with collapsible command and template sections.
  • Live is template-driven. Numeric signals with the same chartGroup render together in one chart panel; chartAxis chooses left or right axis. No React code change is required to add a chart group.

The REST API reference pages are:

  • Sub Devices — plant-scoped CRUD, latest signals, historical telemetry, and sub-device commands
  • Device Templates — global template catalogue

Status

Sub-device status follows SubDeviceStatus:

StatusMeaning
ONLINESeen in at least one recent snapshot.
OFFLINENot seen for longer than the configured staleness threshold.
DEGRADEDSeen but reporting partial or erroneous data.
UNKNOWNDefault at creation (before first telemetry).

The telemetry pipeline stamps status: ONLINE and updates lastSeenAt on every ingest for all sub-devices referenced in the envelope, in a single batch UPDATE.


What sub-devices are NOT

  • Not MQTT clients. A sub-device never holds an MQTT credential or connects to the broker.
  • Not directly addressable on the wire. A PLC does not publish to a per-sub-device topic. All traffic flows on the plant's topic.
  • Commands are routed through the plant. When sending a command to a specific sub-device, the command payload includes a target field set to the sub-device's externalId. The plant receives the full command envelope and routes it to the target component internally. See Commands & alerts.

See also

  • Plants — the MQTT bridge that owns the connection
  • Device templates — the signal catalogue that decodes telemetry
  • Signals — binary vs. numeric decode rules and storage model
  • Commands & alerts — how commands are routed to a specific sub-device

On this page