Skip to content
AI-driventedge-dot is developed largely by AI coding agents, and it is alpha software. Check what it does before you rely on it, and report what you find.

AVEVA PI Web API

The aveva-pi connector reads and writes a PI System through its PI Web API REST interface. It is the first tedge-dot connector for a server rather than a fieldbus device:

  • A point is a PI point (by tag name or path), an AF attribute (by path) or any stream (by WebId). WebIds are looked up once per connection.
  • One request reads all of a device’s due points, however many there are.
  • By default, values are pushed over the PI Web API Channels WebSocket and keep their PI timestamps.
  • After an outage or a restart, the gap is filled from the PI archive, so Cumulocity gets the readings it missed, in order.
  • Writable points can be changed from the cloud.

What you’ll build: one device, pi-demo, that receives four values from the demo’s PI Web API simulator (aveva-pi-sim, plain HTTP on host port 8480) and writes a temperature setpoint back to it.

AVEVA, PI System and PI Web API are trademarks of AVEVA Group Limited or its subsidiaries. tedge-dot is not affiliated with or endorsed by AVEVA.

With thin-edge.io and a tedge-dot package installed:

  1. Start the simulator:

    Terminal window
    docker compose -f /usr/share/tedge-dot/demo/docker-compose.yaml up -d aveva-pi-sim
  2. Enable the demo config. The service picks it up within a few seconds:

    Terminal window
    sudo install -m 0640 -o tedge -g tedge /usr/share/tedge-dot/demo/aveva-pi.toml /etc/tedge/plugins/ot/aveva-pi.toml
  3. Watch the values arrive. pi-demo appears in Cumulocity as a child device of the gateway:

    Terminal window
    tedge mqtt sub 'te/device/pi-demo/#'

A device is one PI Web API endpoint:

demo/config/aveva-pi.toml
[[device]]
name = "pi-demo"
type = "aveva-pi-server"
protocol_address = { base_url = "http://127.0.0.1:8480/piwebapi", data_server = "PISRV01", af_database = '\\AFSRV01\Plant' }
View demo/config/aveva-pi.toml on GitHub
KeyRequiredNotes
base_urlyesThe PI Web API root, such as https://pi.example.com/piwebapi.
data_serverfor tag pointsThe PI Data Archive that bare tag names live in.
af_databaseno\\<AF server>\<database>, the prefix of relative attribute paths.
deliverynoHow values arrive: see below.

Any [connection] key, such as auth or tls, can be overridden per device. Several devices may share one base_url, and then they share one connection pool.

delivery decides how readable points are delivered:

deliveryHow values arriveTimestamp
"channel" (default)Pushed over a Channels WebSocket as PI archives them, with backfill after an outage.The PI time of each value.
"recorded"The archive is polled every sampling_interval (else poll_interval), from where the last poll stopped.The PI time of each value.
"snapshot"The current value is read at every poll_interval, as the community plugin did.The read time; the PI time is in the sample’s addr.timestamp.

If the server refuses the Channels WebSocket while its REST requests work, the device falls back to recorded until the next reconnect and logs the refusal once.

demo/config/aveva-pi.toml
# Pushed over Channels, with their PI timestamps.
[[device.point]]
id = "sinusoid"
name = "Sinusoid"
datatype = "float64"
address = { tag = "SINUSOID" }
[[device.point]]
id = "reactor_temp"
name = "Reactor temperature"
datatype = "float32"
address = { tag = "REACTOR01.TEMP" }
[[device.point]]
id = "pump02_state"
name = "Pump 02 state"
datatype = "string"
address = { tag = "PUMP02.STATE" }
[[device.point]]
id = "discharge_pressure"
name = "Pump 01 discharge pressure"
datatype = "float64"
address = { path = 'Line1\Pump01|Discharge Pressure' }
# A device parameter: writable from the cloud.
[[device.point]]
id = "setpoint"
name = "Temperature setpoint"
datatype = "float64"
unit = "degC"
access = "read_write"
address = { tag = "SETPOINT" }
meta.parameter = { key = "control.setpoint" }
View demo/config/aveva-pi.toml on GitHub

Each point has exactly one address key:

AddressKind
{ tag = "SINUSOID" }A PI point on the device’s data_server.
{ path = '\\PISRV01\SINUSOID' }A PI point by its full path.
{ path = '\\AF\Plant\Line1\Pump01|Discharge Pressure' }An AF attribute by its full path.
{ path = 'Line1\Pump01|Discharge Pressure' }An AF attribute, relative to af_database.
{ web_id = "F1DP…" }Any stream, by WebId. It isn’t looked up.

Use TOML literal strings ('…') for paths, so the backslashes stay as written. A tag that the server doesn’t have makes that point bad with not found: <path>, without taking the device down, and it is looked up again every resolve_retry (5 minutes by default).

  • Datatypes. Every point needs a datatype. Use float32 or float64 for PI Float points, int16 or int32 for Int points, and string for String and Timestamp points.
  • Digital states. A PI digital point has a code and a name from its state set, for example 1 and Running. int32 gives the code, which becomes a measurement series. string gives the name, which is useful for a device parameter, an event or an alarm, but is not a measurement. For both, configure two points with the same tag.
  • Units. A point without a unit takes its engineering unit from PI.
  • Quality. A value that PI marks as not good, or a system state such as I/O Timeout, arrives as a bad sample with PI’s state name as the error. Questionable, substituted and annotated values stay good and are flagged in the sample’s addr.flags.

A real PI Web API server is HTTPS and needs credentials. The packaged /etc/tedge/plugins/ot/aveva-pi.toml has no devices and lists every option as a comment.

  1. Put the secret in a file that only the tedge user can read. The connector never reads a password or token from the config itself:

    Terminal window
    sudo install -d -m 0700 -o tedge -g tedge /etc/tedge/plugins/ot/secrets
    printf '%s' 'the-password' | sudo install -m 0600 -o tedge -g tedge /dev/stdin /etc/tedge/plugins/ot/secrets/aveva-pi.password
  2. Name the method and the file in [connection]:

    /etc/tedge/plugins/ot/aveva-pi.toml
    [connection]
    auth = { method = "basic", username = 'CORP\pi-reader', password_file = "/etc/tedge/plugins/ot/secrets/aveva-pi.password" }
    tls = { ca_file = "/etc/tedge/plugins/ot/certs/pi-ca.pem" }
  3. Add a [[device]] with an https:// base_url, and points as above.

auth.methodKeysNotes
basicusername, password_fileA domain user is written 'DOMAIN\user'.
bearertoken_fileThe file is read again after a 401, so an external job can rotate the token.
certificatecert_file, key_fileA PEM client certificate and key (mutual TLS).

Kerberos and NTLM aren’t supported.

  • Server certificate. It is checked against the system trust store, plus the CA bundle in tls.ca_file (in tedge-dot-c, the bundle replaces the system store). The certificate needs a subject alternative name for the host in base_url. tls.insecure_skip_verify = true turns the check off and logs a warning on every connect.
  • Plain HTTP. An http:// base_url is accepted only without authentication. With any auth.method except none, the config is refused, so credentials are never sent unencrypted.
  • Local-only settings. The secret file keys, tls and state_dir can’t be changed by a management command from the cloud.

With channel or recorded delivery, the connector keeps a cursor for each point: the time of the last value it delivered. The cursors are written to /var/lib/tedge-dot/aveva-pi/<service>/<device>.json (state_dir), so they survive a restart.

After an outage or a restart, the connector reads the archive from the cursor onwards, delivers those values in time order, and then continues with the live ones. The catch-up is limited by backfill:

[connection]
backfill = { max_age = "1h", max_events = 10000 } # per point; backfill = false turns it off

A longer gap is logged once, with the point and the time range that was skipped. The reporting policy applies to replayed values on their PI time, so a backfilled hour is reduced as the live stream would have been.

A point with access = "read_write" (or "write") can be written. The demo’s setpoint is also a device parameter, so it can be changed from the Cumulocity Parameters tab.

  • A write sends the value with the server’s time.
  • For a digital point, write either the state name or its code. A state that isn’t in the point’s state set fails without a request.
  • update_option (default "Replace") and buffer_option (default "BufferIfPossible") in [connection] set how PI stores the value.
  • A write is never retried. If PI refuses it, for example with a 403 because the account may only read, the command fails with PI’s error message and the device stays connected.

If you run the community thin-edge-aveva-pi-connector, a script turns its pi_config.json and datapoints.json into a connector config. The measurements land on a child device with a different type, so read the migration guide before you switch.