AVEVA PI System Integration Guide
Stream and query an AVEVA PI Data Archive at high throughput through the MaestroHub PI Agent — a Windows service you run inside the plant network. MaestroHub connects out to the agent over a single WebSocket; the agent talks to PI on your behalf.
This guide covers installing and connecting the agent, every connection and function field, the tag model, worked examples with real inputs and outputs, and the connector's limitations.
Overview
The PI System connector provides:
- High-throughput streaming of PI point values with quality and timestamps
- Subscriptions by explicit tag list, name wildcard, or AF element subtree
- Four read modes — current, recorded (raw archive), interpolated (fixed grid), and summary (aggregates)
- Batch writes with a per-item outcome, so bad paths fail individually rather than failing the batch
- Automatic gap backfill after a connection or archive outage
- Tag browsing — search PI points by name, or walk the Asset Framework hierarchy
- TLS with your own PKI, including private certificate authorities
This connector does not talk to PI directly. It requires the MaestroHub PI Agent running on a Windows machine that can reach your PI Data Archive.
Download it from https://portal.maestrohub.com/downloads/plugins.
The agent is a self-contained Windows x64 build — there is no .NET runtime to install and nothing to compile. MaestroHub then connects out to it, so the agent needs no access to MaestroHub; MaestroHub needs access to the agent.
For step-by-step download, configuration and startup instructions, see the PI Agent Installation Guide.
Which PI connector should I use?
MaestroHub has two connectors that reach a PI System. They are siblings, not duplicates:
| PI System (this connector) | PI Web API | |
|---|---|---|
| How it reaches PI | The MaestroHub PI Agent, installed on a Windows host | PI Web API's REST interface, over HTTPS |
| Anything to install? | Yes — the agent | No |
| Best for | Throughput; streaming many tags at high rates | Convenience; when you cannot install software |
| AF attributes | Not supported — PI points only | Supported |
| Trigger delivers | A batch of values per event | One value per event |
Use PI System when throughput matters. Use PI Web API when the agent cannot be installed, or when you need Asset Framework attribute data.
How the connection works
MaestroHub ──── WebSocket (TCP 45283) ───► PI Agent ──── PI protocol ───► PI Data Archive
(Windows) (TCP 5450/5457)
One WebSocket carries two planes: JSON for requests and subscription management, and a compact binary format for value data. The binary plane is what makes high value rates reachable.
Three consequences worth knowing before you start:
- MaestroHub dials the agent, not the other way round. The agent listens on TCP
45283; that port must be reachable from the MaestroHub host. - One MaestroHub session per agent. The agent serves a single session at a time and deliberately prefers the newest arrival — a second connection displaces the first. This is why the connector's scaling is fixed at one replica (see Scaling).
- The agent keeps running when PI is down. It retries with backoff and reports the problem through its health endpoint, rather than exiting.
Connection configuration
Navigate to Connect → New Connection → AVEVA PI System. The form has seven tabs.
| Tab | What it holds | Available before saving? |
|---|---|---|
| Connection | Profile name, description, labels; agent host and port | Yes |
| Security | API token, TLS, CA certificate, skip-verify | Yes |
| Advanced | Timeouts, framing, flow control | Yes |
| Functions | This connection's functions | Yes, but empty until saved |
| Tag Browser | Browse the server and build functions from a selection | No — save first |
| Scaling | Replica settings | No — save first |
| Health | Live agent and PI counters | No — save first |
The last three need a live connection to call the agent, so they stay disabled until the connection is saved. Each carries the reason in its tooltip. A tab showing a red badge has that many validation errors inside it.
1. Profile information
| Field | Default | Description |
|---|---|---|
| Profile Name | — | A descriptive name for this connection (required, max 100 characters). Must be unique across all connections. |
| Description | — | Optional description for this connection |
| Labels | — | Key-value pairs to categorize the connection (max 10 labels) |
2. Connection
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Agent Host | Text | Yes | — | Hostname or IP of the machine running the PI Agent, e.g. pi-agent.plant.local. With TLS on, this must match the certificate's DNS name. |
| Agent Port | Number | No | 45283 | Port the PI Agent listens on (1–65535) |
3. Security
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| API Token | Text (secret) | Yes | — | The shared token the agent expects — the ApiToken value from the agent's appsettings.json, character for character. Stored encrypted. When editing an existing connection, leave it empty to keep the stored token. |
| Use TLS | Toggle | No | true | Encrypt the connection. Must match the agent's own EnableTLS setting. |
| CA Certificate (PEM) | Text | No | — | The certificate authority that signed the agent's certificate, PEM-encoded. Needed when the agent uses a private PKI the MaestroHub host does not already trust — the normal shape of a plant deployment. Leave empty to use the host's own trust store. |
| Skip Certificate Verification | Toggle | No | false | Accept any certificate the agent presents. Lab rigs only. |
- The API token is sent with no
Bearerprefix. The agent rejects a prefixed token on purpose, so a mistake fails loudly at connect time instead of appearing to work. Do not add a prefix, and watch for a trailing space when pasting. - Use TLS must agree with the agent. If they disagree, the dial fails during the handshake and the error reads like a network problem.
- CA Certificate and Skip Certificate Verification are mutually exclusive. Setting both is refused when you save — skipping verification ignores the CA entirely, so the CA you pasted would be silently unused. Pick one.
This WebSocket carries both your process data and the API token. Over plaintext the token is readable and replayable by anyone on the network path. Turn TLS off only for a loopback connection (agent and MaestroHub on the same machine) or an isolated lab network.
4. Advanced
Every field here has a working default. Leave all six alone on a first run.
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
| Connection Timeout (seconds) | Number | 30 | 1–300 | How long to wait for the WebSocket handshake and the agent's greeting. |
| Request Timeout (seconds) | Number | 60 | 1–3600 | Deadline for one read, write or browse. The agent applies no deadline of its own, so this is the only bound on a call against a slow archive. Raise it if a wide history read times out. |
| Heartbeat Interval (seconds) | Number | 30 | 5–300 | How often an application-level heartbeat is sent. Its reply carries the agent's PI connection state, so a live socket over a dead archive is reported unhealthy instead of looking fine. |
| Maximum Frame Size (bytes) | Number | 4194304 (4 MiB) | 65536–67108864 | Largest binary frame accepted. Must be at least the agent's own frame cap, which it advertises when the session opens. Both default to 4 MiB — if you raise one, raise the other. A mismatch is caught when the session opens rather than discovered as a truncated read under load. |
| Subscribe Batch Size (bytes) | Number | 524288 (512 KiB) | 65536–1048576 | How much encoded JSON goes in one subscribe message. The agent discards any control message above 1 MiB, so a large subscription is split into batches. Measured in bytes rather than tag count because PI path lengths vary by an order of magnitude. |
| Flow Control Window (frames) | Number | 128 | 8–256 | How many data frames the agent may send before waiting for more credit. This is the outermost ring of backpressure. Raising it above the agent's own buffer inverts the order backpressure is meant to happen in, and is refused. |
Testing the connection
Press Test Connection before saving. This is a real round trip — it dials the agent, completes the handshake and pings — and reports the latency in milliseconds.
| Message contains | Cause |
|---|---|
401 / unauthorized | Token mismatch. Check for a Bearer prefix or a trailing space. |
| connection refused | The agent is not running, or Agent Port is wrong. |
| no route / timeout | A firewall, or the agent is bound to 127.0.0.1 while MaestroHub is on another machine. |
| TLS handshake / certificate | Use TLS and the agent's EnableTLS disagree, or the certificate is not trusted and no CA was supplied. |
| unsupported protocol version | The agent build is older than the connector. Update the agent. |
the PI Agent is reachable but its connection to the PI Data Archive is "Disconnected" | MaestroHub reached the agent fine — the problem is between the agent and PI. Check the agent's health endpoint. |
After saving, the connection is created active and starts on its own. The Tag Browser, Scaling and Health tabs then become available.
The tag model
Every read, write and subscription targets PI points, identified by path.
| Path shape | Example | Notes |
|---|---|---|
| Server-qualified | \\PISRV01\SINUSOID | What the Tag Browser returns, and what you should store |
| Bare tag name | SINUSOID | Also resolves |
Whatever you send is echoed back verbatim in results and in failures, so you can match rows to your input.
The agent provides PI point data access, not AF attribute data access. A path containing a | — for example \\PISRV01\Production\Boiler1|Temperature — is refused with a message that names the limitation:
AF attribute data access is not supported by this agent; use a PI point path.
Where an AF attribute is backed by a PI point, use the underlying point instead. Native AF attributes with no PI point behind them are unreachable through this connector — the value lives in AF, not in the archive. If you need AF attribute data, use the PI Web API connector.
You can still browse the AF hierarchy to find assets, and subscribe to every PI point beneath an AF element.
Tag Browser
Browsing is not a function you create. It is the tag picker's engine — a question about the connection rather than a step in a pipeline — so it never appears in the function-type picker and has no pipeline node. It surfaces in two places, both calling the same operation:
- The connection's Tag Browser tab, where a selection becomes a read or subscribe function in one step.
- The Tag Browser panel beside the tag list on any read or subscribe function form, where a selection fills in that function's tags.
Two modes:
| Mode | What it does |
|---|---|
| Points | Search PI points by name. * and ? are wildcards; an empty filter matches everything. |
| AF | Walk the Asset Framework element tree one level at a time. |
Results are paged, and Load more fetches the next page.
A browse filter such as Çim* returns zero results even where such tags exist. The PI client encodes outbound names in the host's ANSI code page while the archive holds UTF-8, so any lookup that transmits a non-ASCII name misses. The agent recovers non-ASCII names it can reconstruct without transmitting them, so such tags still appear in unfiltered results and stream normally — it is specifically the filter that cannot match them. The fix is the agent host's code page.
Time expressions
Time fields accept three forms, and nothing else:
| Form | Examples | Meaning |
|---|---|---|
| RFC 3339 timestamp | 2026-09-01T08:00:00Z | An absolute instant |
| Relative offset from now | -1h, -30m, -7d, -90s | That much before now |
Empty, now, or * | Now |
An offset must carry a unit — ms, s, m, h, or d (days). A bare number is rejected. A leading + is also accepted (+1h is one hour into the future), which is only meaningful against future-data points.
Expressions such as *-1h, t, y, or Monday are not accepted — only the three forms above. This is deliberate: supporting a fraction of PI's time syntax while appearing to support all of it would return real data for the wrong window, and nothing downstream could tell. An unrecognised expression is rejected with a message naming the accepted forms.
Note that -1h (this connector) and *-1h (PI's own syntax, used by the PI Web API connector) mean the same thing but are written differently.
Functions
Open the connection's Functions tab and click New Function. There are six selectable function types.
| Function | Type ID | Category | Purpose |
|---|---|---|---|
| Get Current Value | pisystem.get_current | Read | The most recent value of one or more points |
| Get Recorded Values | pisystem.get_recorded | Read | Raw archived values over a time range |
| Get Interpolated Values | pisystem.get_interpolated | Read | Values resampled at a fixed interval |
| Get Summary Values | pisystem.get_summary | Read | Aggregates computed at the archive |
| Write Values | pisystem.write | Write | Write a batch of values to PI points |
| Subscribe to Tag Values | pisystem.subscribe | Trigger | Start a pipeline on every new value |
Browse Tags and Agent Status are marked internal and do not appear in the picker or the pipeline palette. Browse powers the Tag Browser; Agent Status powers the Health tab. Both are still served at runtime — they are simply not things you create.
The read result shape
All four read types return the same envelope:
| Field | Type | Description |
|---|---|---|
metadata.mode | string | current, recorded, interpolated or summary — a fact about the call, delivered with the metadata (_metadata.mode in a pipeline) |
values | array | One entry per value returned (see below) |
count | number | values.length |
metadata.truncated | boolean | true when the result was cut short — see Limits and paging; _metadata.truncated in a pipeline |
failed | array | One {path, error} per path that could not be read |
metadata.pages | number | How many requests were issued to the agent — metadata, like the two above |
Each entry in values:
| Field | Type | Description |
|---|---|---|
path | string | The path exactly as you sent it |
t | string | Timestamp, ISO 8601 UTC |
v | number | string | boolean | null | The value |
q | string | Quality — good, uncertain or bad |
systemState | number | Present only when v is null; the PI system-state code explaining why |
One bad path does not fail the call. It appears in failed with its own reason while every other tag returns normally — so one mistyped tag in a list of hundreds costs only that tag.
Get Current Value (pisystem.get_current)
Read the most recent archived value of one or more PI points.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Tag Paths | List | Yes | — | One PI point path per row. Supports ((parameter)) templates. |
Example input
| Field | Value |
|---|---|
| Tag Paths | \\PISRV01\SINUSOID, \\PISRV01\CDT158, MH.NO.SUCH.TAG |
Example output
{
"values": [
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:00:00Z", "v": 42.5, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T07:59:58Z", "v": 118.2, "q": "good" }
],
"count": 2,
"failed": [
{ "path": "MH.NO.SUCH.TAG", "error": "point not found" }
]
}
Use cases: show the current temperature of every reactor on a dashboard; read a setpoint before deciding whether to write a new one.
Get Recorded Values (pisystem.get_recorded)
Read values exactly as the PI Archive stored them — no interpolation, and unevenly spaced, because PI compresses.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Tag Paths | List | Yes | — | PI point paths to read. Supports templates. |
| Start Time | Text | Yes | — | Range start. RFC 3339 or a relative offset. Supports templates. |
| End Time | Text | No | (now) | Range end, same formats. Empty means now. Supports templates. |
| Maximum Values | Number | No | 0 | Total values to return across every tag. 0 issues a single request. See below. |
| Maximum Values Per Tag | Number | No | 0 | Per-tag cap within each request. 0 uses the agent's default. |
Limits and paging
One request returns at most 100,000 values across all tags.
- Maximum Values =
0(the default) issues exactly one request. If the archive held more than that request could return,truncatedcomes backtrue— the result is honest about being partial rather than looking complete. - Maximum Values greater than 0 pages the time range automatically until that many values are collected. Each page is one round trip, so set it to what you actually need.
Example input
| Field | Value |
|---|---|
| Tag Paths | \\PISRV01\CDT158 |
| Start Time | -1h |
| End Time | (empty) |
| Maximum Values | 0 |
Example output
{
"values": [
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T07:03:12.421Z", "v": 117.9, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T07:19:48.115Z", "v": 118.4, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T07:51:03.902Z", "v": 118.2, "q": "good" }
],
"count": 3,
"failed": []
}
Use cases: backfill yesterday's production data into a warehouse; pull the raw trace behind an alarm for a post-incident review.
Get Interpolated Values (pisystem.get_interpolated)
Read one value per interval across the range, interpolated by PI from archived data. Use this when values from several tags must line up on the same timestamps — raw archived data will not, because each tag compresses independently.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Tag Paths | List | Yes | — | PI point paths to read. Supports templates. |
| Start Time | Text | Yes | — | Range start. Supports templates. |
| End Time | Text | No | (now) | Range end. Empty means now. Supports templates. |
| Sampling Interval (ms) | Number | Yes | — | Milliseconds between successive samples. Must be greater than 0. Supports templates. |
Example input
| Field | Value |
|---|---|
| Tag Paths | \\PISRV01\CDT158, \\PISRV01\SINUSOID |
| Start Time | 2026-09-01T08:00:00Z |
| End Time | 2026-09-01T08:02:00Z |
| Sampling Interval (ms) | 60000 |
Example output — note both tags share the same timestamps, which is the point:
{
"values": [
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:00:00Z", "v": 118.2, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:01:00Z", "v": 118.6, "q": "good" },
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:02:00Z", "v": 119.1, "q": "good" },
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:00:00Z", "v": 42.5, "q": "good" },
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:01:00Z", "v": 43.9, "q": "good" },
{ "path": "\\\\PISRV01\\SINUSOID", "t": "2026-09-01T08:02:00Z", "v": 45.2, "q": "good" }
],
"count": 6,
"failed": []
}
Use cases: build an hourly energy profile across several meters; align a dozen sensors onto one timeline before a calculation.
Get Summary Values (pisystem.get_summary)
Ask PI to calculate aggregates over the range and return only the results. The calculation happens at the archive, so a month of one-second data becomes a handful of numbers on the wire instead of millions of values.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Tag Paths | List | Yes | — | PI point paths to summarise. Supports templates. |
| Start Time | Text | Yes | — | Range start. Supports templates. |
| End Time | Text | No | (now) | Range end. Empty means now. Supports templates. |
| Summary Types | Checkboxes | Yes | average | Which aggregates to compute. |
Summary Types are checkboxes, not free text. The available values are:
total, average, minimum, maximum, range, stddev, count, percent_good
A summary result carries no label saying which summary each value is — the wire format has no field for it. Tick three types and you get three unlabelled values for the same tag, in no documented order, with no way to tell the minimum from the maximum.
Request one summary type per function. If you need an average and a maximum, create two functions.
Example input
| Field | Value |
|---|---|
| Tag Paths | \\PISRV01\CDT158 |
| Start Time | -24h |
| End Time | (empty) |
| Summary Types | average only |
Example output
{
"values": [
{ "path": "\\\\PISRV01\\CDT158", "t": "2026-09-01T08:00:00Z", "v": 118.37, "q": "good" }
],
"count": 1,
"failed": []
}
Use cases: daily average and peak flow for a monthly report; percentage of good data for a sensor health check.
Write Values (pisystem.write)
Send a batch of values to the archive in one call.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Values | List of rows | Yes | — | The values to write. Each row is a path, a value, and an optional timestamp. Supports templates. |
| Existing Value Handling | Select | No | replace | What to do when a value already exists at the same timestamp. |
Each row in Values:
| Column | Required | Description |
|---|---|---|
path | Yes | The PI point to write to |
value | Yes | The value. Numbers, strings and booleans are all accepted; the agent coerces to the point's actual PI type. |
timestamp | No | RFC 3339 or a relative offset. Empty stamps the value with the current time. |
Existing Value Handling options:
| Option | Behaviour |
|---|---|
replace (default) | Overwrite the existing value at that timestamp |
insert | Add alongside the existing value |
no_replace | Keep the existing value; do not write |
Writing a digital state by name. A digital point's state can be written by name rather than by ordinal — write "Open" rather than 1. Note this only works in this direction: reading a digital point returns the ordinal number, not the name (see Limitations).
Example input
| Field | Value |
|---|---|
| Values | \\PISRV01\TESTTAG = 12.5 · \\PISRV01\TESTTAG2 = 7.25 at 2026-09-01T08:00:00Z · \\PISRV01\NOPE = 1 |
| Existing Value Handling | replace |
Example output — two writes applied, one path rejected:
{
"successCount": 2,
"failureCount": 1,
"results": [
{ "path": "\\\\PISRV01\\TESTTAG", "success": true },
{ "path": "\\\\PISRV01\\TESTTAG2", "success": true },
{ "path": "\\\\PISRV01\\NOPE", "success": false, "error": "point not found" }
],
"queued": false
}
| Field | Description |
|---|---|
successCount | How many items were written |
failureCount | How many items failed |
results | One entry per item, in the order you supplied them. error is present only on a failure. |
queued | true when PI was unavailable and the batch went into the agent's retry queue |
queuedNote | Present only when queued is true; explains what that means |
queued: true is not a durability guaranteeWhen PI is unavailable, the agent accepts the batch into a bounded, in-memory retry queue and reports queued: true. Those values have not been written yet, and the queue does not survive the agent stopping.
Treat queued: true as "accepted, outcome unknown" — not as success. If the write must not be lost, check the result and re-issue it yourself.
A value written to PI can drive downstream control, reporting and alarms. Gate writes behind a Condition node that bounds the value, or behind an explicit operator action, before the write reaches PI.
Batches larger than 10,000 items are split automatically, so a large write is not refused — but it is also not atomic. Each item succeeds or fails on its own.
Subscribe to Tag Values (pisystem.subscribe)
Register tags for streaming and start a pipeline as values arrive. This function type is a pipeline trigger — see the PI System Trigger node.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Tag Paths | List | No | — | Tags to subscribe explicitly |
| Name Filter | Text | No | — | Subscribe to every tag matching this pattern; * and ? are wildcards |
| AF Element Path | Text | No | — | Subscribe to every PI point beneath this Asset Framework element |
| Maximum Tags | Number | No | 10000 | Refuse the subscription if the selectors resolve to more tags than this (1–200,000) |
| Maximum Values Per Event | Number | No | 0 | Split large batches into events of at most this many values. 0 keeps each batch whole. |
| Include Recovered Values | Toggle | No | true | Deliver values the agent recovers after an outage |
The three selectors — Tag Paths, Name Filter and AF Element Path — combine. Their union is de-duplicated, so a tag matched twice costs one subscription. At least one must resolve to something: if all three select nothing, the subscription is refused with a message naming all three.
Unlike most triggers, this one delivers a batch. At full rate, one pipeline execution per value is not a workable shape, so the connector emits one event per decoded frame — up to 8,000 values. Downstream nodes iterate result.values rather than reading a single reading.
Maximum Tags guards against a wildcard quietly claiming the whole archive. When it binds, the error names how many tags actually matched.
Unlike the read and write functions, 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.
PI refuses to sign certain points up to a snapshot pipe — future-data points are the common case, and on a real archive they can be several percent of all points. The subscription is still established; those specific tags simply never deliver.
This is reported rather than hidden. Check the Health tab:
streaming.subscriptionsNotStreaming— how many are affectedconnector.tagsNotStreaming— which paths, each with the agent's reason and whether it is retryable
Using parameters
Path, time and value fields on read and write functions accept ((parameterName)) placeholders. Parameters are detected as you type and appear in the Function Parameters block, where you can set a type, mark them required, and give a default.
Example
- Function type: Get Recorded Values
- Tag Paths:
((tag)) - Start Time:
((from)) - End Time:
((to))
At execution the pipeline binds tag, from and to, so one function serves many contexts. The same function can be tested from the form — the Test Function dialog prompts for each detected parameter.
Testing functions
Read and write functions can be tested before saving with Test Function. The dialog shows the resolved configuration, prompts for any ((parameter)) values, runs the function against the live PI System, and renders the result with timing.
You do not need to save the function first.
Subscribe functions cannot be tested this way — they are validated by enabling the pipeline that uses them. Browse and Agent Status are internal and are exercised by the Tag Browser and the Health tab.
Health and Agent Status
The Health tab (available after saving) shows the agent's own view of itself, refreshed live. Useful fields when something looks wrong:
| Group | Field | What it tells you |
|---|---|---|
pi | state | The agent's connection to PI. Connected is what you want. |
streaming | subscriptions | How many points are actually streaming |
streaming | subscriptionsNotStreaming | How many were subscribed but deliver nothing — the number that explains a tag list where some rows never move |
throughput | valuesPerSecond1m | Values per second over the last minute |
flowControl | paused | Whether backpressure is currently applied |
writes | queueDepth | How many writes are waiting in the agent's retry queue |
connector | tagsNotStreaming | Which of your paths are not delivering, and why |
Scaling
The Scaling tab is fixed at one replica and cannot be changed.
This is not a limitation of MaestroHub but of the agent: it serves one WebSocket session at a time and deliberately prefers the newest arrival. That is the right behaviour for a redeploy — an ungraceful restart can otherwise leave a dead session holding the slot for minutes — but it means two MaestroHub instances pointed at one agent would evict each other indefinitely, and neither would finish subscribing.
If you need more throughput than one agent provides, run more agents against different tag sets and create one MaestroHub connection per agent.
Pipeline integration
Functions you author here become nodes in the Pipeline Designer:
- PI System Get Current Value (
connected.pisystem.get_current) - PI System Get Recorded Values (
connected.pisystem.get_recorded) - PI System Get Interpolated Values (
connected.pisystem.get_interpolated) - PI System Get Summary Values (
connected.pisystem.get_summary) - PI System Write Values (
connected.pisystem.write) - PI System Trigger (
trigger.pisystem) — starts a pipeline on each batch of streamed values
For node-level details, see the PI System Nodes and PI System Trigger pages.
Common use cases
Stream a production line into the UNS
Author a Subscribe function with a Name Filter covering the line's tags, drive it from the PI System Trigger, and route result.values into the Unified Namespace. Each event carries a batch, so iterate the array rather than reading one value.
Nightly history backfill
Author a Get Recorded Values function with Start Time -24h, set Maximum Values to the volume you expect so the range pages automatically, and drive it from a Schedule trigger into a database or storage node. Check truncated in the result to know whether you got everything.
Shift reporting
Author a Get Summary Values function with a single summary type (average) over the shift window, and forward the values to a reporting sink. The aggregate is computed at the archive, so the transfer is a handful of numbers.
Setpoint writeback
Compute a setpoint upstream, validate it with a Condition node, then issue a parameterised Write Values function with ((setpoint)) as the value. Check successCount and results in the output — and remember that queued: true is not yet a write.
Limitations
These are deliberate and known. They are listed here so you can design around them rather than discover them.
| Behaviour | Why |
|---|---|
AF attribute paths (anything with a |) are refused. | The agent provides PI point data access, not AF attribute data access. Use the underlying PI point, or the PI Web API connector. |
| Native AF attributes with no PI point behind them are unreachable. | The value lives in AF, not in the archive — there is nothing to read. |
| A digital point reads back as a number, not a state name. | The state's ordinal is what crosses the wire. A state name only goes the other way, on a write. |
| A timestamp point's value reads back as a number. | It is carried as Unix-epoch 100-nanosecond ticks, like every other timestamp on the wire. |
| A 32-bit float point does not read back the exact decimal you wrote. | Single-precision rounding: 42.3 becomes 42.29999923706055. This is how the point stores it, not a transport error. |
| A summary read carries no label saying which summary each value is. | The wire format has no field for it. Request one summary type per function. |
| A browse filter containing non-ASCII characters matches nothing. | The PI client transmits names in the host's ANSI code page while the archive holds UTF-8. Affects the filter only — such tags still appear unfiltered and stream normally. |
| Future-data points cannot be subscribed. | PI itself refuses to sign them up to a snapshot pipe. Reported through tagsNotStreaming rather than hidden. |
| Only one MaestroHub session per agent. | The agent serves one session and prefers the newest. See Scaling. |
PI time syntax is not supported — only RFC 3339, relative offsets and empty/now. | Supporting part of PI's syntax while appearing to support all of it would return real data for the wrong window. |
queued: true on a write is not a durability guarantee. | The agent's retry queue is in memory and bounded, and does not survive the agent stopping. |
| Writes are not covered by store-and-forward. | If a write cannot be delivered, it is reported as a failure for the pipeline to handle — it is not buffered and retried by MaestroHub. |
| Agent configuration is read once, at startup. | Editing appsettings.json while the agent runs changes nothing until you restart it. "I changed the setting and nothing happened" is usually a missing restart. |
Troubleshooting
Connection
| Symptom | Cause | Fix |
|---|---|---|
| Test Connection returns 401 | Token mismatch | Check the token matches appsettings.json exactly. No Bearer prefix, no trailing space. |
| Connection refused | Agent not running, or wrong port | Confirm the agent is running and Agent Port matches its Port. |
| Timeout / no route | Firewall, or agent bound to loopback | Open TCP 45283 from the MaestroHub host; check the agent's ListenAddress is not 127.0.0.1. |
| TLS handshake failure | Use TLS disagrees with the agent, or an untrusted certificate | Make both sides agree; supply the CA, or use skip-verify for a lab rig — never both. |
| Save refused: skip-verify + CA both set | They contradict each other | Provide the CA to verify a private authority, or skip verification for a lab rig. |
unsupported protocol version | Agent build is older than the connector | Update the agent from the portal. |
Reachable, but PI is Disconnected | The agent cannot reach PI | The problem is between the agent and PI, not MaestroHub. Check the agent's health endpoint and log. |
Reads and writes
| Symptom | Cause | Fix |
|---|---|---|
One tag missing from values | That path failed | Look in failed — it names the path and the reason. |
AF attribute data access is not supported | The path contains a | | Use the underlying PI point path. |
_metadata.truncated is true | More data existed than one request returns | Set Maximum Values to page the range, or narrow the window. |
| Read times out | The archive is slow for that query | Raise Request Timeout; it is the only bound on the call. |
| A digital tag returns a number | Expected | Reads return the ordinal. Map it downstream, or write by name. |
| Time expression rejected | PI syntax used | Use RFC 3339, a relative offset (-1h), or leave empty for now. |
Write reports queued: true | PI was unavailable | Not yet written. Check PI, and re-issue if the value matters. |
Subscriptions
| Symptom | Cause | Fix |
|---|---|---|
| Subscription refused: resolved to no tags | Selectors match nothing | Check Tag Paths, Name Filter and AF Element Path; verify spellings in the Tag Browser. |
| Subscription refused: too many tags | The selectors exceeded Maximum Tags | Narrow the filter, or raise the limit deliberately. |
| Some tags never deliver | PI will not stream them | Check connector.tagsNotStreaming on the Health tab — it names each path and the reason. |
| Filter with accented characters matches nothing | Code-page limitation | Subscribe those tags by explicit path, or fix the agent host's code page. |
Most problems resolve fastest bottom-up: confirm the agent reports healthy, then that Test Connection passes, then that the Tag Browser returns tags, then that a Get Current Value function reads one of them. Each step rules out everything below it.