Configurator
The CDP configurator command-line tool allows to automate configuration and testing of CDP applications. The Configurator has two alternate ways for providing command sequences. The first option is through a YAML file and the second option is through a command-line interface.
Motivation And Use Cases
Though CDP Studio is a powerful tool to visually construct and setup any automation or control system with several features to ease reusing existing configuration structures and validating behaviors, it not well suited for programmatic use when generating project structures, testing or integrating to CI pipeline is involved.
Configurator tool is intended to be used for programmatic generation of projects, testing, validation and other CI pipline integrations or custom built tools. It can add new nodes, remove nodes, move nodes, set and get or test values using mathematical expressions and also verify events generated by nodes.
- Set up automated tests for your system and run them in your CI tool
- Set up automated tests for your library and run them in your CI tool
- Set up a new system using a script instead of manually adding resources in CDP Studio Configure mode
- Create graphical tools that guide users in configuration and adding whole subsystems to projects
- Set up as part of External tools in CDP Studio to automate tedious tasks
Locating Configurator Tool In Toolkits
The Configurator tool is provided as part of each installed CDP Studio toolkit See the Toolkits location in your system for more information.
The host Configurator tool resides in above mentioned toolkits folder.
/<toolkit name>/CDP-X.x/bin
Configurator Tool Options
Supported configurator tool options are listed in the table below.
Option | Description |
---|---|
-m | Path(s) separated by : to models folder for configuration (may be metamodels when parsing a library) NB! If folder is named "Recipes" it is treated as recipes folder |
-a | Path to application folder with Application.xml to load application configuration |
-c | ip:port of running StudioAPI server used in place of -a for connecting to running system |
-u | Optional username for connectiong to StudioAPI server |
-p | Optional password for connecting to StudioAPI server |
-g | Generate Nodes XML configuration snipplet based on the passed in list: "<add_to_model>,<add_model>,<node_name>" |
-y | Path to YAML file to be parsed for configure, test, verify tag(s) (optional) |
-t | With this option the test, verify tags are used from YAML file instead of configure tag (test tag when -c is provided and verify tag when -a is provided) |
-l | Path to library folder with Models, option inplace of -a |
-r | Path to library folder with Recipes |
-dc | Dump entire configuration tree to CSV file |
-dm | Dump entire model tree to CSV file |
-uri | Base uri for relative uri(s) in YAML file. Relative uri(s) start with '.' symbol |
-tch | Touch provided model files to cause any tools monitoring them to reload and reparse |
-cli | Use command line interface of configurator (use instead of -y and -t options) |
For better understanding, next are some examples of invoking the configurator tool with arguments.
To configure app in path -a <path> with YAML file from -y <file> via its configure section:
configurator -m <path> -a <path> -y <file>
To verify configuration of application in path -a <path> with YAML file from -y <file> via its verify section:
configurator -m <path> -a <path> -y <file> -t
To parse library in path -l <path> with models in paths -m <path> (model path list should include metamodels located in toolkits/<toolkit name>/CDP-X.x/templates/metamodels as first path):
configurator -m <path> -l <path>
To test app running at -c <ip:port> with yaml file from -y <file> via its test section:
configurator -m <path> -c <ip:port> -y <file> -t [-u <user>] [-p <pass>]
To generate XML sniplet of model instantiation in a parent model with given node name:
configurator -m <path> -g "<add_to_model>,<add_model>,<node_name>"
Configurator Tool YAML Syntax
The YAML syntax supports three different functions defined under the three toplevel tags: configure, test, verify.
- configure allows directly configuring an application configuration files
- test allows testing/asserting a running system
- verify allows asserting directly on application configuration files to verify some expected state
Configure Tag
The supported tag structure in configure tag is the following:
- configure as toplevel tag contains a list of dictionaries with possible keys uri: full node path, add: list of child dictionaries. (the uri key supports also relative paths starting with '.' symbol if a base uri was passed in with -uri argument)
- remove : list of dictionaries with name tags to remove
- move : list of dictionaries with old name tag and new uri or offset tags to "move to" or "move by relative to current location"
- values : dictionary of values for uri
- children : list of recursive dictionaries with required name and add, move, remove, values, children. Tags add and children can recurse multiple levels
Example:
configure: - uri: App add: - name: MyComponent model: MyComponentModel values: Debug: 5 SomeTrueBool: 1 children: - name: Priority values: Value: high add: - name: MyTest model: MyComponentModel remove: - name: ComponentoNonGrata move: - name: MyOldSignalName uri: App.MyComponent.MyNewSignalName - name: MyOldSignalName2 offset: -1 - uri: App.MyOtherComponent values: Debug: 1
Test Tag
This is intended to allow running simple tests on CDP Applications. The test top-level tag allows to define named sequential test cases with setup, test, and assert sections that are applied to the running CDP Application through StudioAPI server.
Failure to find defined uri's or failure to assert conditions will all be considered as test failures in a given test case. The test will also produce a TestResults.xml in JUnit format for CI tools to parse.
The supported tag structure in test tag is the following:
- test as toplevel tag contains a list of dictionaries with possible keys case: test case name, setup: test setup assignments, test: test assignments, assert: assert statements with only case as the required tag.
- setup : list of dictionaries with uri as the required key and values and children are optional
- test : list of dictionaries with uri as the required key and values, children, delay_ms and wait_ms are optional
- assert : list of dictionaries with uri as the required key and values, evaluate, children, event, delay_ms and wait_ms are optional. event can have optional dictionaries like code, status and values to match a event
The steps are always executed in the same order regardless of definition order: setup, test and assert. The only behavioral difference is with assert which does not assign its values but asserts them by comparing its value to the one provided by the StudioAPI server.
In addition to comparing values with constants, the 'evaluate' expression can be used to test any number of values of the uri using a mathematical expression. The 'evaluate' tester supports the same set of operators and - functions as are described in the Sequencer StateTransition Expression documentation. The 'evaluate' accepts both a single expression string and a list of expression strings. All expressions must return true as a result of the evaluation for the test to be processed without error. Note that the bool(PrimitiveTypeValue)
function can be used to construct a boolean value out of other primitive types.
Also, assert can have an additional keyword event that can be used to assert that an event was generated by the node that match the parameters under the keyword.
The asserts are considered handled as soon as the value gets the asserted value with a default timeout of 5000 milliseconds. It is possible to set shorter timeout values with wait_ms key. This will only apply to values on the same level and is not be waited fully when the asserts are fulfilled before timeout. It is also possible to set a delay into the test sequence by adding delay_ms key. This will only apply to values on the same level and is always fully applied.
There are optional additional top-level tags application_name and test_name when using the test top-level object.
Example:
test_name: ConfiguratorTesting application_name: &app_name ConfiguratorTest test: - case: WhenSignalIsSet_RoutedSignalsGetValue setup: - uri: ConfiguratorTest values: OutS: 5 InS: 5 test: - uri: ConfiguratorTest delay_ms: 500 values: OutS: 10 assert: - uri: ConfiguratorTest wait_ms: 50 values: InS: 10 evaluate: - 'rint(InS * 1000) / 1000 == 5' - 'OutS > InS' - 'Result == "OK"' children: - name: OutS values: Value: 10 - name: Moooos values: Value: 5
Example for event testing:
test_name: AlarmTesting application_name: &app_name AlarmTest test: - case: WhenAlarmIsSet_AlarmEventIsTriggered setup: - uri: '%{AppName}.TestAlarm' values: CM_ALARMSET: 1 assert: - uri: '%{AppName}.TestAlarm' event: code: { AlarmSet: 1, AlarmClr: 0, AlarmAck: 0, Reprise: 0 } status: { NotifySet: 0, WarningSet: 1, ErrorSet: 0, WarningUnacked: 1 } values: Level: WARNING evaluate: - 'Text.find("Alarm") != -1'
Verify Tag
This is intended to be used to verify post-run configuration state by verifying that uri's that must exist are present and stored values in the configuration are as expected.
The top-level verify object contains a list of dictionaries with the same syntax as the top-level test object. That said only the assert steps are reasonable in post-run verification.
Configurator Tool CLI Syntax
The command-line interface is started with -cli option instead of using -y and -t options. This makes it possible to issue configuration commands manually or use the CLI interface from a script to manipulate or check the configuration. This allows building CI related test scripts but also generator tools to generate control systems from given parameters, options, etc.
The -cli option also accepts -uri option when provided for initial location after startup. The prompt of the CLI consists of the current location uri followed by a '#' character.
Command | Alias | Usage | Description |
---|---|---|---|
uri <uri> | cd | uri MyApp.MyComp | Set new location. Command also accepts relative uris starting with at least one '.' charecter denoting current location (except at System level) |
add <model> <name> | add CDPSignal<double> MySignal | Add new node to current location based on provided model and name | |
get <name> | get MySignal | Get the value of named node in current location. The value is returned as standard output before next prompt | |
set <name> <value> | set MySignal 6.0 | Set the value of named node in current location | |
remove <name> | rm | remove MySignal | Remove the named node from current location |
move <name> <uri> | mv | move MySignal MyApp.MyMovedSignal | Move or rename the named node to path given by uri |
list [basemodel] | ls | list CDPAlarm | List all the child nodes that are at the current level at the current location. If [basemodel] is specified, only results where [basemodel] is found in the model or base model name will be outputted. For example, list CDPAlarm will list all CDPAlarm based objects, but also objects containing CDPAlarm in the model or basemodel name, such as MyLib.MyCDPAlarm and SomeLib.CDPAlarmExtended. |
find [-b basemodel] [-n name] | find | find -b CDPAlarm -n Fault | Find all the child nodes and children, starting at the current location, that partially matches the basemodel filter and/or the shortname filter. The usage above will find all CDPAlarm based objects in the current (sub)tree that has the name "Fault". The basemodel matching as shown above will find all objects containing CDPAlarm in model or basemodel, such as MyLib.MyCDPAlarm and SomeLib.CDPAlarmExtended. |
quit | exit | quit | Quit the configurator tool running the command line interface |
help | li help | Print CLI help |
Example CLI session after starting the configurator tool:
$ configurator -m ./Models -a . -cli Configurator started... Looking for models from ./Models Parsed application from . # uri ConfiguratorCLI ConfiguratorCLI# get fs 10 ConfiguratorCLI# add CDPSignal<double> MySignal OK ConfiguratorCLI# set MySignal 10 ConfiguratorCLI# get MySignal 10 ConfiguratorCLI# exit Configurator closed...
Python Example CLI Usage
It is quite simple to use the command-line interface in a scripting environment. This example shows how to use Python 'pexpect' module to write a script that uses the CLI. This example assumes that you are familiar with python. The 'pexpect' module is used because it simplifies these kinds of command-line interface scripts.
import sys import os import pexpect def command(cli_in, execute): cli_in.sendline(execute) cli_in.expect("# ") def get_value(cli_data): return cli_data.split("\r\n")[1]
With these helper functions you can access all the configurator commands. The cli.before can be also used to detect command errors as then it would contain an error message starting with "Error:". Checking this would allow the script to also report errors. The 'pexpect' based example works on Linux, but on Windows, 'wexpect' can be used in a similar fashion.
Example using the above functionality and 'pexpect' on Linux:
cli = pexpect.spawn("./configurator -m ./Models -a . -cli") cli.delaybeforesend = None # disable the pexpect 50 ms default delay before send cli.expect("# ") command(cli, "uri ConfiguratorCLI") command(cli, "set fs 20") command(cli, "get fs") fs = get_value(cli.before) command(cli, "add CDPComponent TestC") command(cli, "uri .TestC") command(cli, "add CDPSignal<double> S1")
Configurator Python API
The cdpconfigurator.py class is an Operating System independent (Windows&Linux) API to simplify accessing the configurator executable from a Python version 3.x script.
With this python class, you can easily add and remove objects in an Application, set or get values, list contents, find objects, etc. When you combine this approach together with CDP Studio Recipes, it becomes a very powerful tool that enables you to, for instance, generate complete systems from sources such as an Excel file or a database.
Note: For supported CDP versions, the cdpconfigurator.py is located in the CDP Studio installation folder under toolkits/<toolkit>/CDP<version>/bin.
Requirements: In addition to 'os' and 'sys.platform':
- Linux: pexpect
- Windows: wexpect
Below are some python Configurator API usage examples:
Initialize the Configurator Object and Set Location to the First Object (Application)
import sys import os toolkit_path = "/home/user/CDPStudio/toolkits/linux_x86_64_13/CDP-4.10" sys.path.append(os.path.join(toolkit_path, "bin")) from cdpconfigurator import CDPConfigurator app_path = "/home/user/CDPStudioWorkspace/systems/CabinSystem/CabinApp/Application" commandline_options = "-a " + app_path model_paths = (CDPConfigurator.get_default_models_and_recipes_path(toolkit_path) + ":../libraries/CustomModelsLib/Templates/Models" + ":../libraries/AnotherModelLib/Templates/Models") app = CDPConfigurator(toolkit_path, commandline_options, model_paths) app_tree = app.ls() app_component = app_tree[0][0] app.cd(app_component) print("Location: " + app_component + " of type "+app_tree[0][1])
List All Alarms in the Application, along with Their 'Description' Attributes
items = app.find("Alarm") for item in items: try: app.cd(item[0]) except Exception as e: print("Could not cd() into '" + item[0] + "', error : " + str(e)) try: desc = app.get_value("Description") except Exception as e: print("Could not find " + item[0] + ".Description") print("Alarm: " + item[0], " Description=" + desc)
Add a Component of a given Model Name, and Change Location to the Newly Added Object:
try: app.add("CustomComp", "CustomModelsLib.CustomComponent") app.cd(".CustomComp") except Exception as e: raise SystemExit("Failed adding and going into CustomComp: " + str(e))
Add a Signal to the Current Location and Set Its 'Description' Property
try: app.add("IsRunning", "CDPSignal<int>") app.cd(".IsRunning") app.set_value("Description","The motor is running.") except Exception as e: raise SystemExit("Error adding IsRunning/Description: " + str(e))
Move One Level up the Tree:
try: app.cd("..") except Exception: print("Failed walking up one level in the tree.")
Move/Rename a Signal
app.move("Running", ".IsRunning")
Verify That an Object Named 'ControlPosition' Is Found at the Current Location
items = app.ls() for item in items: if item[0] == "ControlPosition": print("Found ControlPosition that has model " + item[1])
Set 'ControlPosition' Value to '4' and Verify It Was Set
try: app.cd(".ControlPosition") app.set_value("Value", "4") except Exception as e: result = str(e) raise SystemExit("Setting ControlPosition.Value to 4 failed: " + result) try: value = app.get_value("Value") except Exception: print("Failed to get Value!") print("Value is " + value) if (int(value, 10) != 4): raise SystemExit("Could not get correct ControlPosition Value!")
Remove 'ControlPosition' Object At Current Location
app.remove("ControlPosition")
Duplicate the Component 'Motor' and Change It into a Recipe Named 'ACInductionMotor'
app.transform_component_to_recipe( app_path, "Motor", os.path.join(app_path, "Recipes"), "ACInductionMotor")
See the cdpconfigurator.py class API for further description and help.
Get started with CDP Studio today
Let us help you take your great ideas and turn them into the products your customer will love.