Skip to main content
Version: 3.0 (next)

PI System Trigger Node

Overview​

The PI System Trigger Node starts a MaestroHub pipeline when an AVEVA PI Data Archive produces new values, streamed through the MaestroHub PI Agent. Unlike the PI System read nodes, which read inside an already-running pipeline, this trigger starts a new execution as values arrive.

The trigger is backed by a Subscribe to Tag Values function (pisystem.subscribe) authored on a PI System connection.

One event carries many values

This is the single most important difference from every other industrial trigger, including PI Web API against the same historian.

PI can deliver values far faster than one pipeline execution per value would allow, so the connector emits one event per batch — up to 8,000 values. Your downstream nodes must iterate $trigger.result.values; reading "the value" from the event will only ever see the first one.

This trigger needs the PI Agent

The PI System connector reaches PI through the MaestroHub PI Agent, a Windows service you install inside the plant network. Download it from https://portal.maestrohub.com/downloads/plugins and see the installation guide.


Core functionality​

1. Event-driven pipeline execution Start pipelines as values arrive, with no polling loop of your own.

2. Batched delivery Each event carries a batch of values with their paths, timestamps and quality. Batch size follows the stream — a quiet tag set produces small events, a busy one produces large ones.

3. Flexible tag selection Subscribe by explicit tag list, by name wildcard, by AF element subtree, or any combination. The union is de-duplicated.

4. Automatic subscription lifecycle The subscription is created when the pipeline becomes enabled and torn down when it is disabled. After a connection or archive outage the agent recovers missed values and delivers them marked as backfill.

5. No connector-side deduplication Every value carries its own timestamp, so consecutive batches differ even when a reading repeats. Like the OPC UA, Ignition and PI Web API triggers, this one does not hash-dedup, and there is intentionally no onChange toggle — one that could never suppress anything would mislead.


How PI System triggering works​

AspectRead nodesSubscribe (trigger)
ModelRequest–response (pull)Streaming (push)
ExecutionOne read per callContinuous — one execution per batch of values
LifecycleStatelessStateful subscription held by the agent
Payloadresult.values for the paths you asked forresult.values for whatever changed

When you enable a pipeline with a PI System Trigger:

  1. MaestroHub resolves the Subscribe function's selectors — tag paths, name filter, AF element path — into a concrete tag set, and reports how many matched.
  2. The agent registers those tags with PI and begins streaming.
  3. Values are decoded and grouped into batches; each batch becomes one pipeline execution.
  4. The pipeline runs with the batch available as $trigger.result.values and the event's context as $trigger._metadata.

Reconnection and recovery​

ScenarioBehaviour
Transient network errorThe connector reconnects and re-establishes the subscription automatically.
PI archive outageThe agent keeps running and retries. Once PI returns, values recorded during the gap are recovered and delivered marked backfill.
Agent restartSubscriptions are re-established when the session reconnects. Values the agent had not yet delivered do not survive its restart.
MaestroHub restartTriggers for enabled pipelines are restored on startup.
Telling recovered data from live data

Backfilled values arrive with $trigger._metadata.backfill set to "true" and frameType of backfill. If a downstream calculation should treat recovered history differently from live readings — or skip it entirely — branch on that field.

You can also turn recovery off entirely with the function's Include Recovered Values toggle, which discards those values instead of delivering them.


Configuration options​

Basic information​

FieldTypeDescription
Node LabelString (Required)Display name on the pipeline canvas. Must be non-empty.
DescriptionString (Optional)Explains what this trigger initiates.

Configuration​

ParameterTypeDefaultRequiredDescription
PI System ConnectionConnection ID""YesThe PI System connection profile to subscribe through. Filtered to PI System connections.
Subscribe FunctionFunction ID""YesThe pisystem.subscribe function that defines which tags to stream. Filtered to subscribe functions on the selected connection.
Enable TriggerBooleantrueNoWhen disabled, the subscription is not created even if the pipeline is enabled.
Function requirement

The selected function must be a Subscribe to Tag Values function (pisystem.subscribe). Read and write functions cannot drive this trigger.

If the connection has no subscribe functions yet, use the Edit connection link in the picker to jump to its Functions tab and author one.

Subscribe function configuration​

The selected function controls which tags are streamed and how they are batched. These are set on the function, not on the node:

SettingDefaultDescription
Tag Paths—Tags to subscribe explicitly
Name Filter—Every tag matching this pattern; * and ? are wildcards
AF Element Path—Every PI point beneath this Asset Framework element
Maximum Tags10000Refuse the subscription if the selectors resolve to more tags than this (1–200,000)
Maximum Values Per Event0Split large batches into events of at most this many values. 0 keeps each batch whole.
Include Recovered ValuestrueDeliver values the agent recovers after an outage

The three selectors combine, and their union is de-duplicated. At least one must resolve to something — if all three select nothing, the subscription is refused with a message naming all three.

See Subscribe to Tag Values for the full reference.

Subscription fields are not templatable

The subscribe function's fields do not accept ((parameter)) placeholders. A subscription is resolved once when the pipeline is enabled, so there is no execution to take parameter values from.

Sample payload​

The Parameters tab also holds a Sample Payload editor and a Sample Quality picker. When you run the node with Test Node in Sandbox mode, the trigger emits the sample payload as result instead of waiting for a real event. Sample Quality sets the quality the sample reports in $trigger._metadata.quality (good, uncertain or bad). The live subscription and the node's Fire Trigger action do not use the sample. When the editor is empty, a built-in sample batch is used. Write your own sample in the same shape — a batch, not a single reading:

{
"values": [
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:00:00Z", "v": 42.5, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:00:00Z", "v": 118.2, "q": "good" }
]
}

Keep the sample payload in this shape. A mock carrying one flat value would let you build downstream mappings that break on the first real event.

Settings​

SettingOptionsDefaultDescription
On ErrorPipeline Default / Stop Pipeline / Continue ExecutionPipeline DefaultBehaviour when the node fails.

Output data structure​

Each event produces two top-level keys: _metadata and result.

Output format​

{
"_metadata": {
"type": "pisystem_trigger",
"protocol": "pisystem",
"connectionId": "bf29be94-fc0a-4dc4-8e5c-092f1b74eb4b",
"functionId": "aef374c3-aa2b-454e-aabc-5657faac5950",
"valueCount": "3",
"frameType": "live",
"quality": "good"
},
"result": {
"values": [
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:00:00Z", "v": 42.5, "q": "good", "uom": "m" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:00:00.250Z", "v": 118.2, "q": "good" },
{ "path": "\\\\PISRV01\\VALVE01", "t": "2026-09-01T08:00:00.500Z", "v": "Open", "q": "good", "ordinal": 1, "digital": true }
]
}
}

result.values[] fields​

FieldTypeDescription
pathstringThe PI point path
tstringThe PI source timestamp, ISO 8601 UTC
vnumber | string | boolean | nullThe value
qstringQuality — good, uncertain or bad
uomstringEngineering units. Present only when the point defines them.
ordinalnumberPresent only for a digital point — the underlying state number
digitalbooleanPresent and true only for a digital point
systemStatenumberPresent only when v is null — the PI system-state code explaining why
Digital points stream as names, but read back as numbers

On a subscription, a digital point's value arrives as its state name ("Open"), with the raw ordinal alongside it and digital: true.

A read of the same point returns the ordinal number instead, because a read result is not scoped to the streaming dictionary that carries the state names. If a pipeline both streams and reads the same digital tag, handle the two shapes explicitly.

_metadata fields​

FieldTypeDescription
typestringAlways "pisystem_trigger"
protocolstringAlways "pisystem"
connectionIdstringThe PI System connection profile ID
functionIdstringThe subscribe function ID
valueCountstringHow many values are in this batch. A string, not a number.
frameTypestringlive for streamed values, backfill for recovered ones
qualitystringWorst-of quality across the batch — good, uncertain or bad. Per-value quality stays in result.values[].q.
backfillstringPresent and "true" only when the batch is recovered data
outOfOrderstringPresent and "true" when values in the batch may not be in timestamp order
Metadata values are strings

valueCount, backfill and outOfOrder are strings — "3", "true" — because the event metadata is a string map. Compare them as strings ({{ $trigger._metadata.backfill === "true" }}), or convert before doing arithmetic.

Referencing in downstream nodes​

  • $trigger.result.values — the batch (an array)
  • $trigger.result.values[0].v — the first value only
  • $trigger._metadata.valueCount — how many values arrived
  • $trigger._metadata.quality — worst-of quality for the batch
  • $trigger._metadata.backfill — "true" when this is recovered data
  • $trigger._metadata.connectionId / $trigger._metadata.functionId — which connection and function produced it

A JavaScript node is the usual way to work with the batch:

// Keep only good live readings, and reshape for a downstream sink.
const values = $trigger.result.values;
return values
.filter((row) => row.q === "good")
.map((row) => ({
tag: row.path,
value: row.v,
at: row.t,
}));
PI can deliver out of order

PI data pipes do not guarantee timestamp order, and the agent deliberately does not sort — ordering costs time, and only your pipeline knows whether it needs it. When $trigger._metadata.outOfOrder is "true", sort by t yourself if order matters.


Validation rules​

Node Label

  • Must not be empty or whitespace-only

PI System Connection

  • Must be provided and non-empty

Subscribe Function

  • Must be provided and non-empty
  • Must be a pisystem.subscribe function belonging to the selected connection

Enabled Flag

  • Must be a boolean if provided

Usage examples​

Stream a production line into the UNS​

Key configuration

  • Label: Line 1 Tag Stream
  • Connection: Production PI System
  • Function: Subscribe with Name Filter LINE1.*
  • Enabled: true

Downstream usage: iterate $trigger.result.values in a JavaScript node to map each row to a UNS topic, then publish to the Unified Namespace. Because each event carries a batch, one execution publishes many tags.

Quality-gated calculation​

Key configuration

  • Label: Reactor Calculation
  • Connection: Production PI System
  • Function: Subscribe with an explicit Tag Paths list

Downstream usage: branch on $trigger._metadata.quality with a Condition node to skip a batch containing bad readings entirely, or filter per value on row.q inside a JavaScript node when partial data is still useful.

Live-only processing​

Key configuration

  • Label: Live Alarm Watch
  • Function: Subscribe with Include Recovered Values left on

Downstream usage: gate on {{ $trigger._metadata.backfill !== "true" }} so recovered history does not re-trigger alarms that already fired. Leaving recovery on but filtering downstream keeps the data available for other branches.


Best practices​

Subscription design​

  • Subscribe to what you act on. Maximum Tags exists to stop a wildcard quietly claiming the whole archive; when it binds, the error names how many matched.
  • Leave Maximum Values Per Event at 0 unless a downstream node genuinely needs smaller payloads. Whole batches are the efficient shape.
  • Prefer a name filter or AF path over a long explicit list when the tag set changes — the selectors are expanded when the subscription starts.

Designing for reliability​

PracticeRationale
Iterate result.values, never index [0] aloneBatch size varies with the stream; a mapping built on one value silently drops the rest
Check _metadata.quality or per-value qA batch can mix good and bad readings
Handle outOfOrder if sequence mattersThe agent does not sort, by design
Watch the connection's Health tabsubscriptionsNotStreaming and tagsNotStreaming name tags PI refuses to stream — future-data points are the common case
Use one connection per agentThe agent serves a single session; a second MaestroHub instance would displace the first

Error handling​

For critical workflows: set On Error to Stop Pipeline and alert on stopped pipelines.

For best-effort processing: set On Error to Continue Execution and make sure downstream nodes tolerate partial batches.

Enable vs. disable​

  • Use Enable Trigger to pause streaming without changing pipeline state.
  • Disable during maintenance windows to avoid processing recovered data you do not want.
  • Record why a trigger is disabled in the node's Description.
Some tags will never stream

A subscription can be established successfully while specific tags deliver nothing — PI refuses to sign future-data points up to a snapshot pipe, and on a real archive those can be several percent of all points.

This is reported rather than hidden. The connection's Health tab shows streaming.subscriptionsNotStreaming as a count and connector.tagsNotStreaming as a list of paths with the reason for each. Check it before concluding a tag is missing.