Skip to content

Writing through the API

Pvl files

Pvl files (Published Variable List) are used to manage variable lists exposed by the API. These lists expose a set of variables for other services and the variables can be configured as readable, writable or both.

Preparing the controller

In an existing controller, add a new EXOL variable using Controller Builder and upload the new configuration to the controller.
In this example we have created an Index variable with the name WritePvl.

Creating the list

Start by selecting the ReadAndWrite area in Project Builder and then starting Arrigo Folder Tool.

  • Add a new PublishedVariableListFile and rename it to WritingValues.
  • Select WritingValues and pick Create a new file in this user area... via the Published Variable File attribute.
  • Save the changes in Arrigo Folder Tool.

Open the newly created Pvl file for editing.

  • Add a new Published variable and name it VariableInController.
  • Enter *MyController.WritePvl in the Variable attribute (change according to your actual controller name).
  • Set the attribute Writeable to Yes.
  • Change the attribute Write access if needed.
  • Save the file.

Finding the file using the built-in query builders

Open a browser and navigate to http://localhost/arrigo/api/.

Info

It is sometimes necessary to press Shift+F5 for the content to show. This forces the browser to ignore its cached content and retrieve a fresh copy of the page.

The landing page contains a lot of info regarding the query language and how to access the API either using PowerShell or curl. Make sure to read it!
When you're done reading you can click Log in in the top right corner. Enter a valid username and password and click the Log in button.

Info

There are two built-in query builders (GraphiQL and Playground). They both function nearly identical so selecting which to use is just a question of personal preference. However, only Playground supports subscriptions.

If no content is shown after logging in you might have to do the Shift+F5 trick again.

Delete the information from the left pane (query pane) and paste in the query:

{
  folder(id: "UmVhZEFuZFdyaXRl") {
    ... on UserArea {
      publishedVariableLists {
        name
        path
      }
    }
  }
}

Click the Play button in the top left or press Ctrl+Enter to execute the query.
The result should appear in the right pane (result pane):

{
  "data": {
    "folder": {
      "publishedVariableLists": [
        {
          "name": "WritingValues",
          "path": "UmVhZEFuZFdyaXRlLldyaXRpbmdWYWx1ZXMuRmlsZQ=="
        }
      ]
    }
  }
}

Explanation of the query

The query you just posted to the API is written using a language called GraphQL. We will not go into the details here but we'll give a brief explanation of what happened.
If you've done some programming before you'll probably notice that GraphQL looks a bit like Javascript and that the result is json. Just like Javascript, GraphQL uses curly braces ({ and }) to define scopes.

The line folder(id: "UmVhZEFuZFdyaXRl") instructs the API to fetch the folder named UmVhZEFuZFdyaXRl. The name/id is simply a Base64 encoded version of the folder name (ReadAndWrite).

... on UserArea is an inline fragment that tells the API to only run the next part of the query if this folder is a UserArea.
UserAreas are folders but with some special powers. Published variable lists only exist in UserAreas, not in folders. In programmatic terms one could say that a UserArea is an implementation of the Folder interface.

The last part instructs the API to fetch the name and path (more on this later on) for the Published variable list it finds.

Investigating the file

Now that we have a path to the Pvl file we can query some more.

Run

{
  node(path: "UmVhZEFuZFdyaXRlLldyaXRpbmdWYWx1ZXMuRmlsZQ==") {
    content
  }
}

in the query builder to see the actual content of the file:

{
  "data": {
    "node": {
      "content": {
        "Cwl": {
          "type": "PublishedVariables",
          "attributes": {
            "Name": "WritingValues",
            "Title": "WritingValues",
            "Description": "",
            "Comment": ""
          },
          "children": [
            {
              "type": "PublishedVariable",
              "attributes": {
                "Name": "VariableInController",
                "Title": "VariableInController",
                "Description": "",
                "Unit": "",
                "Variable": "((undefined))",
                "Writable": "Yes",
                "ReadAccess": "Guest",
                "WriteAccess": "Operator",
                "Comment": "",
                "VisualFilter": "0"
              }
            }
          ],
          "Advise": {
            "A": [
              "UmVhZEFuZFdyaXRlLldyaXRpbmdWYWx1ZXMuRmlsZQ==:0"
            ]
          },
          "AdviseRefs": {
            "A": [
              [
                0
              ]
            ]
          },
          "ElementRefs": [
            [
              [],
              4
            ]
          ]
        }
      }
    }
  }
}

Note

The curious reader might wonder why the Variable property of the PublishedVariable is ((undefined)). This is just an indication that the actual (technical) address of the variable was replaced by a binding. This binding can be seen in the Advise.A section.

Reading the actual value

Now that we have acquainted ourselves with the tools and technologies involved, it is finally time to actually read a value.

Run the query:

{
  data(path: "UmVhZEFuZFdyaXRlLldyaXRpbmdWYWx1ZXMuRmlsZQ==") {
    variables {
      technicalAddress
      timeStamp
      type
      value
    }
  }
}

and inspect the result:

{
  "data": {
    "data": {
      "variables": [
        {
          "technicalAddress": "MyController.WritePvl",
          "timeStamp": "2022-07-11T09:02:49.0870959Z",
          "type": "Response",
          "value": 0
        }
      ]
    }
  }
}

Info

The first time you run the query you will get a value of null and the type will be set to Pending. This means that an advise has been set up but you must run the query again to get the actual value.

Writing a new value

The time has come to write a new value in the controller. This is done using mutations in GraphQL.

Run the query:

mutation {
  writeData(
    variables: [
      {
        key: "UmVhZEFuZFdyaXRlLldyaXRpbmdWYWx1ZXMuRmlsZQ==:0"
        auditInfo: {
          description: "Testing writing through the API"
        }
        value: "1"
      }
    ]
  )
}

and inspect the result:

{
  "data": {
    "writeData": [
      "True"
    ]
  }
}

You can use the data query from before if you want to verify that the variable has changed.

Note

Values are always written as strings. The API the converts it to the correct data type.

Audit information

When you write/mutate a variable you can also include an optional audit information object. The values you pass in here will be displayed in the User Log in Arrigo. If you don't pass in an object, the API wil automatically create an entry for you.

The audit info object has four properties you can set:

Name Description
elementName The name of the element that was changed
elementTitle The title of the element that was changed
unit The unit of the element that was changed
description A description of the performed operation

Writing using PowerShell

You can use any text editor to write the script but we highly recommend that you use Windows PowerShell ISE since it has syntax completion and debugging features, and it is pre-installed on most Windows versions.

Create a new PowerShell script (WritingValues.ps1) in the ReadAndWrite area or download the script from here.

Paste the following code into the file and save it:

# Set the base URL.
$baseUrl = "http://localhost/arrigo/api/"

# Replace with the actual credentials.
$username = "[username]"
$password = "[password]"

# Encode the folder name as Base64.
$folderName = "ReadAndWrite"
$encodedFolderName = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($folderName))

# The name of the Pvl file we want to use
$pvlFileName = "WritingValues"

# The content type is always "application/json".
$headers = @{
  "Content-Type" = "application/json"
}

# Set the credentials as payload to the login endpoint.
$body = @"
{
  "username": "$username",
  "password": "$password"
}
"@

# The login endpoint URL.
$loginUrl = $baseUrl + "login"

# Post the payload and convert the response (from json) to a PowerShell object.
$loginResponse = Invoke-WebRequest -Uri $loginUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# Grab the auth token from the response.
# The token will be used in all further posts to the server.
# Read more about this at https://docs.arrigo.se/Reference%20Documentation/GraphQL%20API/01_Authentication/.
$authToken = $loginResponse.authToken

# Prepare the new headers with the correct content type and the auth token from the previous request.
$headers = @{
  "Content-Type" = "application/json"
  "Authorization" = "Bearer $authToken"
}

# Build the query to get the Pvl files.
# This is the same query as you used in the query builder, but with the encoded folder name appended.
$body = @"
{
  "query": 
  "{
    folder(id: \"$encodedFolderName\") {
      ... on UserArea {
        publishedVariableLists {
          name
          path
        }
      }
    }
  }"
}
"@

# The query endpoint URL.
$queryUrl = $baseUrl + "graphql"

# Post the payload and convert the response (from json) to a PowerShell object.
$response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# Get the path for the Pvl file we're interested in
$pvlPath = $response.data.folder.publishedVariableLists | Where-Object { $_.name -eq $pvlFileName } | Select-Object -ExpandProperty path

# Build the query to get the contents of the Pvil file.
# This is the same query as you used in the query builder, but with the pvl path append
$body = @"
{
  "query":
  "{
    node(path: \"$pvlPath\") {
      content
    }
  }"
}
"@

# Post the payload and convert the response (from json) to a PowerShell object.
$response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# Get the binding key from the content
$bindingKey = $response.data.node.content.Cwl.Advise.A[0]

# Build the query to get the variable.
# This is the same query as you used in the query builder, but with the pvl path appended.
$body = @"
{
  "query":
  "{
    data(path: \"$pvlPath\") {
      variables {
        technicalAddress
        timeStamp
        type
        value
      }
    }
  }"
}
"@

# Post the payload and convert the response (from json) to a PowerShell object.
$response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# While (and if) the response type of the variable is "Pending", i.e. an advise has been set up
# but no value has been received yet, we wait for one second and try again.
while ($response.data.data.variables[0].type -eq "Pending") {
  Write-Host "Trying again in one second"
  Start-Sleep -Seconds 1
  $response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json
}

# The response is a list of variables, but in this case we know it only has one entry.
# Note that list indexing starts at zero!
Write-Host "The value of $($response.data.data.variables[0].technicalAddress) is $($response.data.data.variables[0].value)"

# Prompt for a new value.
$valueToWrite = Read-Host "Enter a new value and press [Enter]"

# Build the mutation.
# This is the same query as you used in the query builder, but with key and value appended.
$body = @"
{
  "query":
  "mutation {
    writeData(
      variables: [
        {
          key: \"$bindingKey\"
          auditInfo: {
            description: \"Testing writing through the API\"
          }
          value: \"$valueToWrite\"
        }
      ]
    )
  }"
}
"@

# Post the payload and convert the response (from json) to a PowerShell object.
$response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# Get the success flag/indication from the response
$result = $response.data.writeData[0]

if ($result -ne "True") {
  Write-Error "Something went wrong when trying to write a new value!"
  Exit
}

# If your controller has a "slow" connection you might have to wait a little
# while before querying for the new value.
# In that case just uncomment the two lines below.
# Write-Host "Waiting one second"
# Start-Sleep -Seconds 1

# Build the query to get the variable.
# This is the same query as you used in the query builder, but with the pvl path appended.
$body = @"
{
  "query":
  "{
    data(path: \"$pvlPath\") {
      variables {
        technicalAddress
        timeStamp
        type
        value
      }
    }
  }"
}
"@

# Post the payload and convert the response (from json) to a PowerShell object.
$response = Invoke-WebRequest -Uri $queryUrl -Method POST -Body $body -Headers $headers | ConvertFrom-Json

# The response is a list of variables, but in this case we know it only has one entry.
# Note that list indexing starts at zero!
Write-Host "The value of $($response.data.data.variables[0].technicalAddress) is now $($response.data.data.variables[0].value)"

Either double-click the file to run it or open a PowerShell console, navigate to the file and run it by entering the command .\WritingValues.ps1.
You should get the following result:

PS C:\EXO Projects\ArrigoDemoProject\ReadAndWrite> .\WritingValues.ps1
Trying again in one second
The value of MyController.WritePvl is 0
Enter a new value and press [Enter]: 1
The value of MyController.WritePvl is 1

Info

If the script won't run it could be due to your current PowerShell Execution Policy. For more information, see https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-5.1.