Skip to content

Alarms & Commands

A device type declares two behavioural surfaces alongside its tags: the conditions that should raise an alarm, and the commands an operator can issue against an instance. Both are plain YAML inside the type package — no code — so they version, review, and ship with the type itself. See The Type System for how a package is laid out and Tags for the tag model these definitions bind against.

consystence.pump.centrifugal/
├── manifest.yaml
├── simulation.yaml
├── alarms/
│ └── alarms.yaml # ← alarm definitions
├── commands/
│ └── commands.yaml # ← command definitions
└── components/

alarms.yaml is a list of alarm definitions. Each one names a condition over the instance’s tag values — its live data points; when the condition holds, an active alarm is raised against the instance.

FieldRequiredNotes
idyesStable identifier, unique within the type.
nameyesHuman-readable label.
conditionyesExpression over bindings; raises the alarm when true.
priorityyesOne of Low, Medium, High, Critical.
categoryyesFree-form grouping string. The bundled types use equipment, process, instrumentation, and safety.
messageyesTemplate shown to operators; supports bindings.
delaySecondsnoDeclared debounce — the intent is that the condition must stay true this long before the alarm activates. Not yet enforced: today’s evaluators raise the alarm on the first scan where the condition is true. Also accepted as delay: with an ISO-8601 duration (for example PT10S) — the form exported packages use.

Conditions and messages use the same binding syntax as the rest of the type package. The evaluated namespaces are {tags.X} for a tag value, {config.X} for instance configuration, {instance.X} for instance metadata ({instance.name} for the label), and — inside commands — {params.X}. Conditions support comparisons and boolean logic (>, &&, ==, !, abs(...)).

Here are real definitions from consystence.pump.centrifugal — note the categories, the priority ladder, and the declared delaySeconds debounce on the noisy process and bearing readings:

alarms/alarms.yaml
- id: fault
name: Pump Fault
condition: "{tags.Fault}"
priority: High
category: equipment
message: "{instance.name}: Fault detected (Code: {tags.FaultCode})"
- id: high-bearing-temp
name: High Bearing Temperature
condition: "{tags.BearingTemp} > 85"
priority: High
category: equipment
message: "{instance.name}: Bearing temperature {tags.BearingTemp}°C exceeds limit"
delaySeconds: 10
- id: low-flow
name: Low Flow
condition: "{tags.Running} && {tags.FlowRate} < 5"
priority: Medium
category: process
message: "{instance.name}: Flow rate {tags.FlowRate} m3/h below minimum"
delaySeconds: 60
- id: interlock-active
name: Safety Interlock Active
condition: "{tags.Interlock}"
priority: Low
category: safety
message: "{instance.name}: Safety interlock preventing operation"

priority is an ordered enum: LowMediumHighCritical. Reserve Critical for conditions that demand immediate intervention. The consystence.tank.storage type uses it for the high-high and low-low trips:

- id: high-high-level
name: Tank High-High Level
condition: "{tags.HighHighLevel} == true"
priority: Critical
category: process
message: "{instance.name}: High-high level alarm - overflow risk, shut down inlets"
delaySeconds: 2

An alarm is active while its condition holds and clears automatically when it stops holding — there is no latching. Activation and clearance both surface in the instance’s event log.

Operators can acknowledge an active alarm; the acknowledging user and timestamp are recorded on the alarm (AcknowledgedBy, AcknowledgedAt). Alarm and event timestamps are recorded and displayed in UTC. Acknowledgement is an operator action, never an AI action — the AI may explain an alarm, but it cannot acknowledge it.

commands.yaml is a list of command definitions. A command is a named, guard-railed action that, when executed, writes one or more tags on the instance.

FieldRequiredNotes
idyesStable identifier, unique within the type.
nameyesHuman-readable label.
descriptionnoWhat the command does.
confirmnoIf true, renderers prompt the operator before execution. Honoured by the default (wire) web UI and the CLI; the legacy HTML shell (being removed) does not prompt — see Execution flow and audit.
confirmMessagenoCustom confirmation prompt (supports bindings).
parametersnoOperator-supplied inputs (see below).
preconditionsnoGates that must all pass before the writes are applied.
tagWritesyesThe tag writes performed on execution.
auditnoIf true, execution is recorded to the command audit log.

Each precondition pairs a condition expression with the message shown when it fails. All preconditions must hold before any tag write is attempted; the first failing one short-circuits with its message.

Precondition evaluation is fail-closed on tag quality: every {tags.X} reference must resolve to a present, non-null, good-quality value taken from one atomic snapshot of the instance’s authoritative tag values. If any referenced tag is missing, or its value is uncertain, cached, stale or bad, the command is rejected as precondition data unavailable — manifest defaults are never accepted as evidence, and degraded data never lets a guarded command through. Simulated devices attest good-quality provenance for the values they produce, so guarded commands work normally in simulation. These are server-enforced operability gates (“already running”, “in local mode”), surfaced to give the operator a clear reason — they are still not a safety function, and not a substitute for controller-side interlocking.

parameters declares operator inputs. Each has a name, a dataType (Bool, Int, DInt, Real, String), optional min/max/unit, and a description. Reference a parameter inside a tag write with {params.<name>}.

# commands/commands.yaml — consystence.pump.centrifugal
- id: start
name: Start Pump
description: Starts the pump at current speed setpoint
confirm: true
confirmMessage: "Start {instance.name}?"
audit: true
preconditions:
- condition: "!{tags.Running}"
message: Pump is already running
- condition: "!{tags.Fault}"
message: Clear fault before starting
- condition: "!{tags.Interlock}"
message: Safety interlock is active
- condition: "!{tags.LocalMode}"
message: Pump is in local mode
tagWrites:
- tagName: Running
value: "true"
- id: set-speed
name: Set Speed
description: Sets the speed setpoint
confirm: false
audit: true
parameters:
- name: speed
dataType: Real
min: 0
max: 3600
unit: rpm
description: Target speed in RPM
preconditions:
- condition: "!{tags.LocalMode}"
message: Pump is in local mode
tagWrites:
- tagName: SpeedSetpoint
value: "{params.speed}"

Each tagWrites entry names a tagName and a value. The value is a literal ("true", "0", "100") or a binding ("{params.speed}", "{tags.Position}"). Command tagWrites are exempt from the tag access check: a command defines its own writes and may deliberately target a tag that is read-only for direct writes (the start command above writes Running, a Read feedback tag). The access gate applies to direct tag writes, not to command execution — see Tags for access and physicalRole.

Add pulseSeconds (also accepted as pulse: with an ISO-8601 duration such as PT1S — the form exported packages use) to hold a value for a fixed duration, after which the tag resets to its inactive value — false for booleans, 0 for numerics — the right shape for momentary resets and jogs. The consystence.sensor.flow type uses it for a one-second totaliser reset pulse:

- id: reset-totalizer
name: Reset Totalizer
description: Resets the accumulated flow totalizer to zero
confirm: true
confirmMessage: "Reset totalizer for {instance.name}? This cannot be undone."
audit: true
tagWrites:
- tagName: TotalizerReset
value: "true"
pulseSeconds: 1

Commands execute over the server-driven UI session, not a public HTTP endpoint. When an operator executes a command against live hardware, parameter values are bound and validated first — missing, unknown, wrong-type, and out-of-range values are rejected before anything else runs; on the local/sim path parameter values are applied as declared (see the caution above). Preconditions are then evaluated against one atomic snapshot of good-quality tag values; if any precondition fails, execution stops with that precondition’s message, and if any referenced tag value is missing or degraded the command is rejected as precondition data unavailable — the evaluation fails closed.

What happens next depends on where the device’s tags live. For a local or simulated device, the tagWrites are applied in-process (parameter bindings resolved, pulse tags scheduled to reset). For live hardware, the command is dispatched from the site to the edge over the authenticated command channel with an expiry: a command that expires before dispatch is rejected, and a dispatched command that is not acknowledged by its expiry is marked timed out — the outcome on the PLC is unknown, and it is surfaced that way rather than assumed. When audit: true, the execution — caller, parameters, and the individual tag writes with their outcome and data source — is recorded to the command audit log (readable via csy instance commands / csy site commands).

flowchart TD
  A[Operator issues command<br/>via the UI session] --> V{Parameters bind?<br/>type + range — edge dispatch}
  V -->|no| R[Reject with the failing<br/>parameter, precondition<br/>or data-unavailable message]
  V -->|yes| B{All preconditions true<br/>on one good-quality<br/>tag snapshot?}
  B -->|no| R
  B -->|yes| D{Live hardware?}
  D -->|local / sim| E[Apply tagWrites in-process<br/>literals and params bindings]
  D -->|live| X[Dispatch to edge command<br/>channel with expiry]
  X --> K{Acknowledged<br/>before expiry?}
  K -->|yes| E2[Edge applies writes<br/>to the controller]
  K -->|no| T[Timed out —<br/>PLC outcome unknown]
  E --> F[Pulse tags reset<br/>after pulseSeconds]
  E2 --> F
  E --> G[Write audit log<br/>subject id + tag writes + outcome]
  E2 --> G
  T --> G

The audited caller is the authenticated principal’s subject identifier. For a human operating through the CIAM Bearer flow that is the pairwise per-application sub claim — not the stable Entra oid, so correlating audit entries against an Entra directory by oid does not work today (a known limitation); for automation using a tenant X-Api-Key (prefix csk_) it is that key’s id. Email is a verified attribute, not the identity, and never the audit key. See The MultiAuth Scheme and Authentication for how principals are resolved, and Roles for org membership (owner, admin, member). Executing a command requires an authenticated principal with command authority: org membership on the cloud surface, or the equivalent site role on a site server — site users authorise on their site role, and org membership is not required on-site.

  • The Type System — package structure and IDs.
  • Creating Types — authoring a new type package.
  • Tags — the data model commands write and alarms read.
  • PLC Communication — how a tag write reaches the field.
  • API Reference — the rendered OpenAPI. It carries read-only operational endpoints — live tag values, trends, active alarms, and the command audit log — but no command execution endpoint: commands run over the server-driven UI session.