A tool for efficient CLI handling of OPC DA structures
Project description
OPCTree - A tool for efficient CLI handling of OPC DA structures
This packaged is made for simplifying working with OPC data from the Python CLI. It enables easy scan of a nested structure, searching through all branches, reading value and properties, writing value, comparing the live values to the saved ones.
The program copies the OPC structure of the server so that the user can use the OPC dot-notation in the CLI to traverse to the object the user wants to work on.
Problems with installation
When this is written, is the latest version of OpenOPC, which is required, not
published on pypi.org, and can't be installed by pip install OpenOPC-DA,
instead you need to download it/build it yourself from
https://github.com/j3mg/openopc or get hold of the wheel elsewhere. The version
number isn't published jet, but it should be higher than the current >1.5.0.
If your OPC-server where you want to install it don't have access to internet
you need to download/build wheels on another machine and transfer it.
Notation
A OPC "tree" is built upon branches, i.e. data structs and "leaves", i.e. OPC variables. The branches can have other branches as well as leaves on them. The branches and the leaves within a struct are called children to the parent node, i.e. the data struct. Children can be transferred to another temporary parent node, that parent node is then referred to as the "plastic parent" of the children.
Initialization
>>> import OPCTree
>>> nested_levels_to_search = 3
>>> root = OPCTree.connect_and_build(nested_levels_to_search)
This will prompt you to input which server to connect to
if you haven't specified it in OPCTree.settings.OPC_SERVER .
After connecting will it start searching through the structure, as many
levels as given to connect_and_build.
When initialized can you traverse the OPC-tree with the dot-notation, and run functions on the specified node. While traversing the tree structure can Tab be used to auto-complete and Tab Tab for showing available children.
In the list will also the methods defined on the item show, if you want to distinguish which is the actual OPC children, then you can look in
>>>root.opc_children
That is a list caring the name of all opc children on the parent. Observe that
the names differ slightly from their opc-path if their name wasn't possible to
use as name, for example if the opc-path included opc_children as item then that
can't be used as name of a child since that already is used for the list of
children.
Load more children
If you want to load more children to a part of the tree, use
.load_children(levels).
>>>root.Applications.Application_1.load()
[!CAUTION] Observe that loading children removes all current children before loading the new. So the items loaded to the node before are all lost.
If you want to reload items to a structure that already is loaded then you can use
reload_children(). This will keep the already loaded items, but delete the
items that no longer is in the OPC-server. If you are on the level that has changed
items, and you don't want to search through the hole subtree for changes, then can you
use reload_children(ignore_existing=True), that will only load the subtrees
that isn't in the current object and remove the ones that no longer is in the OPC-server.
First read of properties
After loading a tree you can read the properties of all leafs (opc variables) by writing
>>>root.Applications.Application_1.first_read()
on the level of your choosing. Reading the properties of the variables is required before writing values to them. Reading all item properties in you system might take quite some time, depending on the number of tags in your system. You can press ctrl+C to abort the action, you might want to limit your scope before reading.
The read will be performed in chunks of 40 variables at the time, which might be take longer time than necessary, if you have a fast server you can specify a higher number like this:
>>>root.Applications.Application_1.first_read(max_chunk=1000)
which reads 1000 properties on each call.
Saving and restoring
After reading all properties you probably want to save your updated root. Do that by writing
>>>root.save(\<optional string with name\>)
the object is now saved as '.pickle'. If you don't specify a
name will it be saved as the name specified in settings.OPC_OBJ_PICKLE
(standard: 'opc_obj.pickle').
The object can now be restored with either
>>>restored_root = opc_fetch.restore('<file name if specified when saved>')
or
>>>root_to_restore = root_to_restore.restore('<file name if specified when saved>')
Filter out specific parts by name or properties
You can now filter out the parts of the tree that you are interested in by using
the .all('<your regular expression filter>') notation to get a new root with all
children as leaves on the first level. If you for example want to filter out all
items that has an opc-path that ends with '.Forced' then you can write like this:
>>>temp_parent = root.all(r'\.Forced$')
Maybe you have a unit called 'H20' in App1.Diagram2 from which you want to filter out all controller values called 'Direct', 'Gain', 'Ti' and 'Td' from because you will do a re tuning of all controllers in this part of the program and want to save their old values as backup. You know that your controllers contain the string 'LIC', 'TIC' or 'PIC', then you could use:
>>>temp_parent = root.Applications.App1.Diagram2.all(r'H20*\.*(LIC|TIC|PIC).*(Direct|Gain|Ti|Td)$')
or if you want to filter out the objects GP001 to GP005
>>>temp_parent = root.all(r'App1\.Diagram2\.H20\.GP00[1-5]')
An easy way of testing if your regular expression matched the paths that you
wanted is to print some of the matches with .print_paths(<count to print>) like this:
>>>root.all(r'App1\.Diagram2\.H20\.GP00[1-5]').print_paths(10)
Printing 10 of 2552 paths
Applications.App1.Diagram2.H20.GP001
Applications.App1.Diagram2.H20.GP001.AEAmpsDesc
Applications.App1.Diagram2.H20.GP001.AEAmpsDescInter
Applications.App1.Diagram2.H20.GP001.AEClass
Applications.App1.Diagram2.H20.GP001.AECondNameH
Applications.App1.Diagram2.H20.GP001.AECondNameHH
Applications.App1.Diagram2.H20.GP001.AECondNameL
Applications.App1.Diagram2.H20.GP001.AECondNameLL
Applications.App1.Diagram2.H20.GP001.AEConfAmps
Applications.App1.Diagram2.H20.GP001.AEConfHH
Maybe you want to filter out values based on the opc properties of the items. You might first want to find which properties the item has by looking into
>>>temp_parent.<\child_path\>.name_prop
or if you rather access the properties by OPC index
>>>temp_parent.<child_path>.idx_prop
If you are connected to an ABB 800M OPC-server then will you on index '5002' have information on if the value is 'ColdRetain' or 'Retain', if you want to use that to filter out all 'ColdRetain' values, then can you define a custom filter like this:
>>>isColdRetain = lambda child: child.idx_prop[5002] == 'ColdRetain' if hasattr(child,'idx_prop') else False
and select with
>>>temp_parent = root.all(re_path='<optional_path>',filter_func=isColdRetain)
Some examples of filters, including the one above, is in the example_filters.py file.
Reading, writing and printing values
After the initial read that included the item properties will it go much faster to just reload the values of the leaves, which can be done with
>>>root.branch_to_read_from.read()
if you also want to print the vales that you have written, then can you use
>>>root.branch_to_read_from.print_values()
or directly on the returned branch from the read() result:
>>>root.branch_to_rad_from.read().print_values()
This can be combined with the all filter like this:
>>>root.Applications.App1.Diagram2.all(r'H20*\.*(LIC|TIC|PIC).*(Gain|Ti|Td)$').read().print_values()
Read 537 of 537 items
0.0 Applications.App1.Diagram2.H20.LIC1.Aux.Backward.Gain
0.0 Applications.App1.Diagram2.H20.LIC1.Aux.Backward.Td
0.0 Applications.App1.Diagram2.H20.LIC1.Aux.Backward.Ti
0.0 Applications.App1.Diagram2.H20.LIC1.FFGain
0.0 Applications.App1.Diagram2.H20.LIC1.InitFfGain
1.0 Applications.App1.Diagram2.H20.LIC1.InitGain
0.0 Applications.App1.Diagram2.H20.LIC1.InitTd
20.0 Applications.App1.Diagram2.H20.LIC1.InitTi
0.0 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC_MainOld.FfGain
1.0 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC_MainOld.Gain
0.0 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC_MainOld.Td
20.0 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC_MainOld.Ti
0.0 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC.Main.FfGain
...
Doing a read() will save the live value to the opc_variable items .value
If you want to compare the vales already fetched with the live values then
can you use .changed() which will filter out all values that has changed
and print them out:
>>> root.Applications.App1.Diagram2.all(r'H20*\.*(LIC|TIC|PIC).*(Direct|Gain|Ti|Td)$').changed()
Live value Saved value Tag
0.0 0.6 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC.Main.FfGain
20.0 40 Applications.App1.Diagram2.H20.LIC1.InteractionParPIDCC.Main.Ti
if you want to write the values in the Python object that differ from the live system to the live system you can use:
>>>root.Applications.App1.Diagram2.all(r'H20*\.*(LIC|TIC|PIC).*(Direct|Gain|Ti|Td)$').changed().write()
Renaming
It is possible to change the name of a child so that all the opc-paths is changed in the hole subtree. This can especially be useful if the live object has changed or is about to change so that all children don't have to be reloaded. It can also be used as a backup of all values in the live system. Renaming in the live system might result in loss of configured values, and then can this renaming be done so that the values in the Python object can be written to the live system.
You have to do the renaming from the parent node like this:
>>>branch_with_child_to_rename.rename_child(name_now='child_old_name', new_name='new_name')
or simply
>>>branch_with_child_to_rename.rename_child('child_old_name', 'new_name')
Custom coding and upgrading
If you want a function that is missing, don't hesitate to add it, after adding or modification of opc_obj.py, opc_vars.py or custom opc_class-libraries can the Python branch be upgraded to the latest version by running
>>>root = root.upgrade()
this will reload the libraries and upgrade the branch/root and all of its children.
If you fixed a bug or added some nice fetcher, request a pull on your branch of the code so that it can be added to the main branch for others to enjoy.
Visualization in browser
If you want to see the structure in a web-browser you can use .visualize()
on a node. This will generate a json-file, start a webserver providing the
json-fil together with the vis_template.html page with which you can see
the structure. The node is collapsed by default and are expanded when clicked.
Restore ABB 800M StartValueAnalyzer Data
The module allows building a root based on StartValueAnalyzer files from an ABB 800M PLC. If you want to use that you need to create a folder "Input" in your working directory, and in that folder put the files from the StartValueAnalyzer tool (found on 800xA installation media). So that you have paths like this: '\Input\StartValuesData_YYYY-MM-DD HH.MM.SS.mmmm'
Project/
├── .venv ← Your virtual environment
├── Input/ ← Folder to put the StartValuesData in
└── WorkingDir/ ← Optional folder for saving/restoring root\
You can then run new_root = OPCTree.create_from_StartValuesData() which will
create a new root built on your extracted values. All leaves will be given an
attribute .init_value holding the initial value, and the .value will hold
the retrieved retained value, which could be written back to the live application
with .write, if you are connected to the OPC server. If you aren't
you need to initialize it first, which could be done with OPCTree.initialize_opc_client().
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file opctree-1.1.3.tar.gz.
File metadata
- Download URL: opctree-1.1.3.tar.gz
- Upload date:
- Size: 28.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf4ba5eaa5774e0ee233980e3dbe98dcab9d3cefcb268aee8aac769e02067d16
|
|
| MD5 |
136b87481a1a75ffc3eaf0af5f28f0f7
|
|
| BLAKE2b-256 |
a9ba4b20c6d938726fd8261a69c9e434f21f6d2c97ae55d90b0ba9cdd56b5498
|
Provenance
The following attestation bundles were made for opctree-1.1.3.tar.gz:
Publisher:
publish.yml on PerEAJ/OPCTree
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
opctree-1.1.3.tar.gz -
Subject digest:
bf4ba5eaa5774e0ee233980e3dbe98dcab9d3cefcb268aee8aac769e02067d16 - Sigstore transparency entry: 202536548
- Sigstore integration time:
-
Permalink:
PerEAJ/OPCTree@fc3da729e5f9fb98a9d831ef57e6b79fdc6517cd -
Branch / Tag:
refs/tags/v1.1.3-rc1 - Owner: https://github.com/PerEAJ
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fc3da729e5f9fb98a9d831ef57e6b79fdc6517cd -
Trigger Event:
push
-
Statement type:
File details
Details for the file opctree-1.1.3-py3-none-any.whl.
File metadata
- Download URL: opctree-1.1.3-py3-none-any.whl
- Upload date:
- Size: 27.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
15b393558eb7d99a56dd213f90d843a0a6d0e89d206ca86dc8c1fea09ab5e9f9
|
|
| MD5 |
2fc9703dc6104fa3f2d243b31992df91
|
|
| BLAKE2b-256 |
fe2639d8156d3b227636ee2ca904ac750023b7f51e7c804bf3fd5f4297be84e6
|
Provenance
The following attestation bundles were made for opctree-1.1.3-py3-none-any.whl:
Publisher:
publish.yml on PerEAJ/OPCTree
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
opctree-1.1.3-py3-none-any.whl -
Subject digest:
15b393558eb7d99a56dd213f90d843a0a6d0e89d206ca86dc8c1fea09ab5e9f9 - Sigstore transparency entry: 202536550
- Sigstore integration time:
-
Permalink:
PerEAJ/OPCTree@fc3da729e5f9fb98a9d831ef57e6b79fdc6517cd -
Branch / Tag:
refs/tags/v1.1.3-rc1 - Owner: https://github.com/PerEAJ
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fc3da729e5f9fb98a9d831ef57e6b79fdc6517cd -
Trigger Event:
push
-
Statement type: