In the API
Info
All the examples in this document assume you are using an instance of Arrigo Local installed on your local computer. Remember to change hostname and protocol (both http
and ws
) if you are testing against another computer:
http://localhost/arrigo/api
→ https://myserver.com/arrigo/api
ws://localhost/arrigo/api
→ wss://myserver.com/arrigo/api
List files
After logging in you need to get hold of the folderId
of the folder you're interested in. Once you have the id you can query for the Pvl files in it. Only UserAreas
have Pvl lists in them so you need to use an inline fragment on the folder
in order to access the fields. The list exposes two fields:
Name | Type | Description |
---|---|---|
name | String | The technical name of the file is specified (as read from ArrigoFolder.ExoXml). |
path | String! | A base64 encoded node path, later used for getting the contents of the file or setting up subscriptions. |
Example:
returns
{
"data": {
"folder": {
"name": "RXhhbXBsZUFyZWE=",
"publishedVariableLists": [
{
"name": "Examples",
"path": "RXhhbXBsZUFyZS5FeGFtcGxlcy5maWxl"
}
]
}
}
}
Getting file data
With the path and the node
query you can request the rest of the static data from the Pvl:
returns
{
"data": {
"node": {
"content": {
"Cwl": {
"type": "PublishedVariables",
"attributes": {
"Name": "Examples",
"Title": "Examples",
"Description": "",
"Comment": ""
},
"children": [
{
"type": "PublishedVariable",
"attributes": {
"Name": "Second",
"Title": "Second",
"Description": "Real time clock: Second (0-59)",
"Unit": "s",
"Variable": "((undefined))",
"Writable": "Yes",
"ReadAccess": "Guest",
"WriteAccess": "Guest",
"Comment": "",
"VisualFilter": "1"
}
}
],
"Advise": {
"A": [
"0:0"
]
},
"AdviseRefs": {
"A": [
[
0
]
]
},
"ElementRefs": [
[
[
0
],
4
]
]
}
},
"hash": "ED9C0F49"
}
}
}
Subscriptions
The path also lets you set up subscriptions for updates/advises from a Pvl file. There is a limit in the API of one update per second per variable to prevent clients from choking on massive and rapid update bursts. If you have a lot of bound variables in a file you can still get bursts of multiple updates simultaneously, but we guarantee that they don't come more frequent than in 1 second intervals.
You are also guaranteed to get an initial value/state for each bound variable directly when you start a subscription. If there is (or just recently has been) another ongoing subscription (by you or another user) which include one of "your" bound variables you will get the last sent update for that variable, otherwise you'll probably get a "Value pending" result.
Note
To consume subscriptions you need to have a GraphQL subscription compliant client (there are WebSockets and protocols and stuff driving it all in the background). Apollo Client is a nice one for javascript environments. You can also use the built-in Playground UI of the API to do some exploratory testing (go to http://localhost/arrigo/api
to try it out).
Authentication
When you set up a subscription you need to authenticate using the Auth Token you got when logging in. This is to make the subscription stream unique.
The following snippet shows how to pass the AT when subscribing using Apollo Client:
import { WebSocketLink } from 'apollo-link-ws';
const wsLink = new WebSocketLink({
uri: `ws://localhost/arrigo/api/graphql/ws`,
options: {
connectionParams: {
Authorization: "Bearer [your authToken]"
},
});
Token expiration
When an AT expires, the subscription stream is closed by the API and an error code is returned (INVALID_AUTH_TOKEN
). If this happens you have refresh the AT and setup the subscription again.
If you do a preemptive refresh you have to manually close the subscription and set it up again with the new AT.
Starting a subscription
You start a subscription by passing the path to the subscription/data
query:
subscription {
data(path: "TGFuZHNrcm9uYS5zeXN0ZW1haXIuZmlsZQ==") {
value
path
technicalAddress
type
timestamp
}
}
Each advise update is then delivered, one at a time, in the following format:
{
"data": {
"data": {
"value": 12,
"path": "Cwl.Advise.A[0]",
"technicalAddress": "ExampleController.QSystem.Sec",
"type": "Update",
"timeStamp": "2019-09-26T12:00:12"
}
}
}
See Value updates for more info.
Update types
Each update contains a type
field. The possible values are:
Name | Description |
---|---|
Response | Normal response |
Update | Published advise update |
ErrorNotExist | The requested variable doesn't exist in the current domain |
ErrorUnreachable | The requested variable exists but isn't reachable at this point in time |
Pending | The requested variable exists and is reachable but the Scada Function is waiting for an initial value |
UnknownError | An unknown error occurred when processing the request |
Gray- and blacklisted variable
The Scada Function running in the background has lists containing variables that cannot be reached for the moment (gray-listed) and variables that cannot be resolved (black-listed).
When subscribing to path's containing gray- or black-listed variables, the time stamp field indicates when the variable was put in the list. Please note that gray-listed variables might start publishing updates if they become available at a later point in time.
Errors
If the path you're using is invalid an Invalid path
error message is returned.
If the AT is invalid or has expired the stream is closed and an INVALID_AUTH_TOKEN
error message is returned.
Polling
The data
query has two other fields (besides content
) that are used when for polling variable values:
Field | Description |
---|---|
variables | List of Value updates |
minPollingInterval | The minimum polling interval you need to use (in seconds) |
hash | Hash code for the file |
With a valid path you poll all the exposed variables by with the query:
{
data(path: "TGFuZHNrcm9uYS5zeXN0ZW1haXIuZmlsZQ==") {
variables {
value
path
technicalAddress
type
timestamp
}
hash
}
}
and get a response:
{
"data": {
"data": {
"variables": [{
"value": 14,
"path": "Cwl.Advise.A[0]",
"technicalAddress": "ExampleController.QSystem.Hour",
"type": "Update",
"timeStamp": "2019-09-26T14:26:44"
}],
"hash": "ED9C0F49"
}
}
}
Please note that you'll probably get a Pending
result as a response to the first poll. Subsequent polls should return the actual value of the variables.
Hash check (check if the file has been changed )
The hash
field for the data
query should be compared to the hash
field for node
query to verify that the file hasn't been changed.
If the hashes differ, the source file has been changed and the content (node query) needs to be reloaded/re-queried for full synchronization between content and the data.
Poll interval
It is recommended that you poll at least every minPollingInterval
seconds to avoid variables from being un-advised in the Scada Function (and thus needing extra resources at the next poll to set up advises again). There is no limit on how often you can poll but the API is throttled to 1 update/second/variable.
Value updates
Each update contains the following fields:
Field | Description |
---|---|
path | The path (in the tree) to the updated variable reference |
value | The actual value |
type | Type of update. See Update types for more info |
timeStamp | Timestamp of the update |
technicalAddress | Technical address of the variable |
Note!
The technicalAddress
field might be removed in future versions so do not rely on it! Instead you should focus on using the path
field in combination with the actual Pvl content (from node
) to update your tree.
Writing values
With the nodePath and a path you can use the setData
mutation to change the value of variables:
mutation {
setData(
nodePath: "TGFuZHNrcm9uYS5zeXN0ZW1haXIuZmlsZQ==",
path: "Cwl.Advise.A[0]",
value: "5")
}
It is not possible to mutate multiple paths/variables in one single query.
The mutation returns true
if value could be set, otherwise false
.
Note
It is up to you as the implementer to make sure that the correct datatype is used when writing to variables. Writing the string 5 to an integer will work fine but five will definitely fail.