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

Plants

A plant is the MQTT bridge between Voke and field equipment. Everything else — sub-devices, telemetry, commands — flows through it.

What is a plant?

A plant is the primary field-side entity in Voke. It represents the MQTT client credential — exactly one MQTT connection, one set of authentication secrets, and one MQTT topic namespace. Physical components hanging off a plant (cabinets, meters, inverters) are modelled as sub-devices and never connect to MQTT directly. All their telemetry arrives through the plant.

One plant = one MQTT client = one credential.


Identifiers

FieldTypeNotes
idUUIDVoke-assigned primary key. Also used as the MQTT client ID (mqttUsername) for mTLS and TOKEN plants.
serialNumberstringOperator-supplied. Unique within an organization — a UQ_plants_org_serial constraint enforces (organizationId, serialNumber) uniqueness. Two plants in different organizations may share a serial number.
externalPlantIdstring | nullOptional free-form identifier used by ESM (energy-trading) partners to link a Voke plant to an ESM-tracked plant. Populated by the trading integration; irrelevant to PLC firmware.
templateIdstring | nullFK to a DeviceTemplate at PLANT level. Optional — a plant without a template still forwards sub-device telemetry; the template is only needed if the plant itself produces signals decoded by Voke (rather than via sub-devices).

Template linkage

A plant may reference at most one device template. Template assignment is validated at write-time against two conditions:

  1. Existence — the template ID must exist in the global catalogue.
  2. Level — the template must be DeviceTemplateLevel.PLANT. Assigning a SUB_DEVICE-level template to a plant returns DEVICE_TEMPLATE_WRONG_LEVEL.

Pass templateId: null to remove the assignment without deleting the template.

// Validation path in PlantsService.update (apps/api/src/modules/plants/plants.service.ts)
const template = await this.templateRepo.findOne({
  where: { id: templateId },
  select: { id: true, level: true },
});
if (!template) throw DEVICE_TEMPLATE_NOT_FOUND;
if (template.level !== DeviceTemplateLevel.PLANT) throw DEVICE_TEMPLATE_WRONG_LEVEL;

Authentication methods

Plants authenticate to Mosquitto using one of three methods, set at creation:

authMethodDescription
MTLSMutual TLS — Voke-issued client certificate + HMAC shared secret.
TOKENJWT-based MQTT authentication + HMAC shared secret. No certificate needed.
PASSWORDUsername + password on the password-authenticated MQTT listener.

The method is fixed at creation. Credentials are issued once via POST /api/v1/plants/:id/provision and never stored in plaintext after issuance.


Lifecycle

Plants progress through PlantLifecycleStatus states:

PENDING → ACTIVE → SUSPENDED → ACTIVE (reactivation)
                → DECOMMISSIONED (revocation, terminal)
StatusMeaning
PENDINGCreated but not yet provisioned. No credentials issued.
ACTIVEProvisioned and operational. Telemetry and commands flow normally.
SUSPENDEDTemporarily disabled. MQTT credentials remain valid at the broker level; Voke rejects telemetry.
DECOMMISSIONEDRevoked. Credentials cleared. Terminal — cannot be reactivated.

Suspending a plant does not disconnect its MQTT client or tear down the credential at the broker level. The PLC may still publish; Voke will silently discard the messages. Re-activating (POST /api/v1/plants/:id/reactivate) resumes normal forwarding with the existing credentials. Decommissioning (POST /api/v1/plants/:id/revoke) clears all credentials — the plant cannot reconnect until re-provisioned as a new entity.


HMAC signing

All telemetry and command-acknowledgement payloads are HMAC-SHA256-signed. The shared secret (hmacSecret) is generated during provisioning and stored encrypted. Voke validates every inbound message before passing it to the telemetry pipeline.

See Device security for the secret-rotation flow and the 1-hour grace period.


Sub-devices under a plant

A plant may have zero or many sub-devices. Sub-devices share the plant's MQTT connection and are addressed by their short externalId (R1, GRID, INV1, BAT1, …) inside the snapshot envelope. The plant itself is transparent to sub-device telemetry — SubDeviceTelemetryService uses the plant ID as the scoping key, resolves every entry to a sub-device row, and writes decoded signal rows into the shared telemetry hypertable.

GET /api/v1/plants/:id/energy/today derives the plant's today summary from sub-device telemetry. The first implementation is Solinteg-convention based: it sums PV sub-devices whose externalId starts with PV and grid readings from GRID, using UTC day boundaries.


See also

On this page