Unit Operation Definition
This chapter contains information about creating and configuring a Unit Operation Definition (UOD). Its intended audience is primarily engineers.
A UOD is a python code file that is loaded by the OpenPectus Engine. It describes the capabilities of a physical process unit and enables the engine to communicate with the hardware.
UOD File
A UOD file is a Python file ending with .py
. A concise demo UOD file is shipped with Open Pectus and can be found here. It is a good starting point for development of a new UOD.
The minimal content of a UOD file is:
def create() -> UnitOperationDefinitionBase:
builder = UodBuilder()
return (
builder
.with_instrument("Unit Operation Name")
.with_author("Author Name", "author@email.org")
.with_filename(__file__)
.with_location("Site / Building / Room")
.with_hardware_opcua(host="opc.tcp://192.168.0.1:4840")
<additional details>
.build()
)
The <additional details>
are elaborated in the following.
Required options
The following options are mandatory:
instrument name: The name of the instrument/unit. Usually the same name as the UOD file but all characters are permitted, including spaces and dashes.
author: The name and email address of the author of the UOD.
filename: The file name of the UOD. Set with with_filename(__file__) to automatically fill in the correct file name.
location: The name of the location that houses the hardware unit.
hardware: See Hardware
Todo
Provide rationale/explanation of usage of the above options to the UOD author.
Hardware
Open Pectus has builtin support for the OPC-UA protocol as well as LabJack hardware. The UOD must specify which hardware to use by using one of the builder methods: with_hardware_opcua(host)
or with_hardware_labjack(serial_number)
.
It is also possible to use a combination of sources with the Composite_Hardware
class.
Registers
Registers are ‘slots’ in the hardware that can be read and/or written. The available registers depend entirely on the hardware.
Registers are defined using the with_hardware_register(self, name: str, direction, **options): builder method. For OPC-UA, this may look like:
with_hardware_register("FT01", "Read", path='Objects;2:System;3:FT01')
Note that the name
argument specifies the name to use for the register inside Open Pectus while the path
option specifies how to refer to the register when communicating with the hardware.
Registers are not used directly. Instead register values are wrapped in instances of the Tag
class. Two special register options to_tag
and from_tag
specify how to convert between the register values (the raw values read from the hardware) and tag
values (the values used in P-code). If these options are not set, no conversion of the value is performed.
As an example, a unit may have a valve that is exposed as a register whose values are the integers 0 and 1. The to_tag
and from_tag
options can be used to map theses values to a tag with the string values ‘On’ and ‘Off’ which would be a more natural choice for process operators.
Commands
The UOD can define custom commands. These are python functions that define behavior for the unit and have access to the hardware. As an example, we might want a Reset
command that performs a number of things, and make a simple Reset
command available in P-code.
We could additionally expose the Reset
command as a button in the web frontend using Process values.
The actual implementation of a command should be a function in the UOD, for example
def reset(cmd: UodCommand, **kvargs) -> None:
# implement command logic
# possibly use one or more tags, available as cmd.context.tags
# posibly using hardware directly, available as cmd.context.hwl
# possily using the command instance, available as cmd.
# raise ValueError if an error occurs, to report it to the user.
pass
To make the function available as an Open Pectus command, it must be registered using with_command(name="Reset", exec_fn=reset)
.
Additional arguments are available to with_command
that allows initialization, finalization and custom argument parsing.
Command arguments
Say we want to define a command with the pcode:
Power: 0.5
, i.e. a command that takes a single argument of type float
.
The python function of the command may look like this:
def power(cmd: UodCommand, number):
number = float(number)
...
This can be achieved using the with_command_regex_arguments
method:
.with_command_regex_arguments(
name="Power",
exec_fn=power,
arg_parse_regex=RegexNumber(units=None)
)
This allows Open Pectus to parse the argument to a float
via a predefined regular expression, and pass it to the power
function when the command is executed.
Note that, currently, the regex will not convert the argument to a float. The execution function needs to do that conversion: number = float(number)
. The regex does ensure that this conversion will work. If the value cannot be parsed as a float, the regex parsing function will pause the method and alert the user.
Note that the argument names to the exec function are defined in the regular expression. RegexNumber
defines ‘number’ (and ‘number_unit’ if one or more units are given). The exec function argument must use the same name. Validation is built in to help ensure that arguments and names match up correctly. This validation runs at engine startup.
There are predefined regex parsers for numbers (RegexNumber
), text (RegexText
) and categorical values (RegexCategorical
).
General Arguments and Parsers
It is advised to use with_command_regex_arguments
and one of the predefined regular expressions for parsing if possible.
However, in the general case, to support multiple arguments of different types, a custom parser can be defined. This can be achieved using a custom command parser passed to with_command.
Note
Custom parsing cannot be validated during engine startup so any argument or name mismatch between argument parser and execution function is not caught until the command is executed. In other words - make sure to test it properly.
Process values
In order for a tag to be shown in the Open Pectus web frontend, it must be defined as a process value with with_process_value
. This has three purposes:
Select the tag to be displayed
Display the tag’s current value (and unit if available)
Possibly allow the use to interact with the value, e.g. by setting a new value or executing a command that is related to the tag.
The following types of interaction is supported:
with_process_value
- Read-only. The tag value is displayed but it cannot be modified.with_process_value_entry
- Read/write. The tag value is displayed and its value can be changed by clicking the value and typing the new desired value.with_process_value_choice
. The tag value is displayed and when clicked, a list of commands are shown. When a command from the list is selected, the corresponding command is executed.
Common scenarios
The Demo Uod included with Open Pectus includes most of the supported scenarios. It is located in openpectus/engine/configuration/demo_uod.py
.
Todo
Show a few examples with register, tag, command and process value
Cover choice commands
Show example using regular expression helpers
Built In System Tags
A number of system tags are built into Open Pectus and are automatically set by the system.