Creating a Model in Python

Building the Basics

First, create a new blank Python file. You can name it anything you like, but it should end with the .py extension if you're running on a Windows machine.

import ardimodel

host = ardimodel.ModelHost()

# Add Models Here

host.ardiurl = "localhost"
host.ardisite="default"
host.Run()

The code above will put in the framework we need. It…

  • Imports the ardimodel Python module
  • Creates a 'host' object to hold all of our models.
  • Is given the ARDI server hostname and site name.
  • Is told to start running

Define the Model

We're going to create a calculation that figures out how many minutes of water we have left in the tank.

Each model is created by its own Python function. This function is called with one parameter - an empty model that you can add data points to. So let's create a function called TankModel.

def TankModel(mod):
   pass

Once we've created the model function, we can add it to the host object we created earlier, at the bottom of our code.

# Add Models Here
host.Add(ardimode.Create("Tank",TankModel))

This defines our (currently empty) model, and adds that model to our running system with the name 'Tank'.

Filling in the Inputs

Currently, we have a flow rate from our flow sensor, and a 0-100% gauge of tank level.

These are out inputs.

Inputs are created with the AddInput function of the model class.

Outflow = mod.AddInput("Outgoing Flow",10)
LevelPerc = mod.AddInput("Tank Level Perc",50)

This creates two new input data points. At the moment they aren't connected to anything - they'll be created with the given default values (10 for flow, 50% for tank level) and remain that way until changed.

Creating a Constant

In this case, we are trying to figure out how much time is left before the tank is empty. To do that, we'll need to know how much water the tank is currently holding.

We need to take the level of the tank and turn that into a volume. Assuming that this is a regularly-shaped, vertical tank, we'd just need to know the total capacity of the tank.

tank volume = tank_capacity * tank_level

That capacity is a value that won't change unless they make physical changes to the tank, so we can store it as a constant.

Constants are created with the AddConstant function of the model class.

Capacity = mod.AddConstant("Tank Max Volume",12000)

This defines a constant saying that the maximum capacity of the tank is 12000 litres.

Creating Calculations and Outputs

Outputs are created with the AddOutput function of the model class and calculations with AddCalc.

In this case, the value of exactly how much is left in the tank is probably useful, so we will publish it by having it as an output of our model too. If we didn't think it was very interesting or important, we could make the tank volume a calculation rather than an output.

Outputs and calculations are a little more difficult to create than inputs. They need a name (just like other data points), but they also need a function and a list of used points.

Function

The function is a Python function that calculates a value. As well as containing numbers, it can include any inputs, constants, calculations and other outputs you wish.

For example, the function here will be…

(LevelPerc.num() / 100) * Capacity.num()

Note that the 'num' function converts a DataPoint to a floating-point number. Some points will be strings, so it's important to always cast the value to a number if you're using it as one..

Used Points

This is a list of points that are used in the function. So in the example above, you'd include…

[LevelPerc,Capacity]

This tells the system to automatically update the value of our calculation/output when changes occur in the level percentage or capacity.

Our First Output
Volume = mod.AddOutput("Tank Volume", lambda: (LevelPerc.num() / 100) * Capacity.num(), [LevelPerc,Capacity])

And that's our first output created. We now have a calculated tank volume.

Now, to calculate the time-to-empty.

Outputs based on Outputs

Outputs work just like other data points, so you can use them in output formulas.

mod.AddOutput("Time-To-Empty", lambda: Volume.num() / Outflow.num(), [Outflow,Volume])

An interesting thing to note here is that the 'used points' contains only those points that are being directly used by the equation. When an update to tank level arrives, the volume will be re-calculated, and the volume being re-calculated will trigger the Time-To-Empty to be re-calculated too.

Our Simple Model

import ardimodel

host = ardimodel.ModelHost()

def TankModel(mod):
    Outflow = mod.AddInput("Outgoing Flow",10)
    LevelPerc = mod.AddInput("Tank Level Perc",50)
    
    Capacity = mod.AddConstant("Tank Max Volume",12000)
    
    Volume = mod.AddOutput("Tank Volume", lambda: (LevelPerc.num() / 100) * Capacity.num(), [LevelPerc,Capacity])
    mod.AddOutput("Time-To-Empty", lambda: Volume.num() / Outflow.num(), [Outflow,Volume])

# Add Models Here
host.Add(ardimodel.Create("Tank",TankModel))

host.ardiurl = "localhost"
host.ardisite="default"
host.Run()

We can now run the Python script and the model will be available through OPC-UA.

Results

.On the right you'll see an example of the model in OPC-UA.

You can drag any of the points from the tree on the left into the Data Access View to get their current values.

Double-clicking on the value of any of the inputs or constants allows you to change them. The output values will immediately change to match those changes.

Note that if your inputs are connected to other data points (such as live data from ARDI), you would be able to change their values through OPC-UA.

More Changes