QuickOPC is primarily written in Microsoft .NET. The Python API is layered on top of the .NET components of QuickOPC. This is achieved with the help of Python.NET package (pythonnet), which allows Python code to consume .NET types in nearly seamless way.
It is recommended that you read the Python.NET documentation, in order to get understanding of the concepts, and also some limitations that this approach has. In adition, the Knowledge Base article Inside the Python support in QuickOPC discusses the details and concrete implementation choices taken in QuickOPC to make the Python support possible.
The paragraphs below describe how specific concepts are mapped between .NET and Python. The intent is to help you write QuickOPC programs in Python easily, and also allow you to interpret and convert .NET code to Python.NET.
Referencing NuGet packages in .NET world corresponds to installing Python packages. You wil typically use the pip install command to install the package needed. Most QuickOPC functionality is contaiedn in the packages named opclabs_quickopc.
Your code gains access to the module (Python package) by importing it. This is usually achieved by placing the import statement at the beginning of your Python script. QuickOPC programs in Python will always need at least the following statement:
Importing the opclabs_quickopc package |
Copy Code
|
---|---|
import opclabs_quickopc |
If you use more packages, you will need to have import statement for them as well.
The opclabs_quickopc package also invokes the Python.NET infrastructure and initializes the .NET Common Language Runtime (CLR).
.NET namespaces are treated similarly to Python packages. In order to gain access to all types from a particular .NET namespace, use the import statement with an asterisk (*). For example, to import all types from the OpcLabs.EasyOpc.UA namespace, use the following import statement:
Importing all types from the OpcLabs.EasyOpc.UA namespace |
Copy Code
|
---|---|
from OpcLabs.EasyOpc.UA import *
|
You can also import individual types, by specifying the type name name instead of the asterisk (*).
QuickOPC makes use of .NET extension methods (available originally in C#, but also in other .NET languages). Extension methods are static methods with an argument for the instance of the object they operate on. The class that declares the extension method is not the type of the object, but a separate, independent static class. Languages that support extension methods then allow the extension method to be used "as if it were" a regular method on the object itself.
Python.NET does not have a standard mechanism for callling extension methods. All QuickOPC extension methods can still be called, though. You just need to use the "normal" syntax of calling them. That is, use the the name of the extension class, place a dot after that name, and follow it by the name of the extension method. In the list of method arguments, place the object instance on the first position, followed by the other, "regular" arguments of the method (if any).
For illustration, have a look at the C# and Python code in this example: Examples - OPC Unified Architecture - Read a single value. The C# code uses the syntax "client.ReadValue(...);" , where ReadValue looks like a method on the EasyUAClient object. In reality, however, ReadValue is a method on the IEasyUAClientExtension Class that is capable of extending any object implementing the IEasyUAClient Interface. The Python code for the same functionality thus needs to look like this:
Calling an extension method in Python |
Copy Code
|
---|---|
value = IEasyUAClientExtension.ReadValue(client, ...) |
Languages like C# support implicit conversion operators; Python.NET does not. Where such conversion is needed, you have following options in .NET:
When a method has multiple overloads, Python.NET chooses the overload in runtime based on the actual values of the arguments, if possible. See Overloaded and Generic Methods in Python.NET documentation. Note that when the arguments has a null value in .NET (None in Python), Python.NET cannot determine its true type, which can make the unambiguous automatic selection of the right overload impossible. In such cases you need to explicitly indicate the desired method overload, as described in the Python.NET documentation.
Special care needs to be taken where implicit conversion operators (see above) are used with arguments of methods that have overloads in "normal" .NET code. In languages like C#, where the implicit conversion operators are supported, you will see code that, for example, looks like that it passes in a string as a method argument, and you would be tempted to think that there is a method overload that accepts the string for that argument. When you replicate such code in Python, you would get an error indicating that no such overload exists. This might be due to the fact that the actual methods overload takes a different type for the argument in question, and in C#, the strings gets converted to the expected arguments type using the implicit conversion operator. For illustration, have a look at C# and Python code here: Examples - OPC Unified Architecture - Read a single value. The C# code appears to be passing strings to the ReadValue call, for the endpoint descriptor and node descriptor; but in reality, these strings get converted using implicit conversion operators to the actual types of the arguments, which as UAEndpointDescriptor and UANodeDescriptor. The Python code for the same example needs to explicitly construct the UAEndpointDescriptor and UANodeDescriptor objects from the strings, and then pass these objects to the ReadValue call.
If you want to use one of the methods in QuickOPC that accepts a callback method as its argument, you will need to construct a corresponding event handler delegate from the Python method, and use the event handler as an argument in place of the callback method. In QuickOPC, the event handler delegates are consistently named in such a way that they end with a postfix "EventHandler". The part before the prefix corresponds to the event arguments class - the one whose name ends with "EventArgs". For example, an event handler for an event carrying event arguments of the type EasyUADataChangeNotificationEventArgs Class is the EasyUADataChangeNotificationEventHandler Delegate.
Creating the event handler delegate is as simple as invoking it with the constructor syntax, passing in the name of the Python method that handles the callback. For an illustration, see e.g. Examples - OPC Unified Architecture - Callback using lambda or regular method.
In some cases, when a Python integer is passed to QuickOPC method in place of an arbitrary value (System.Object in the method signature in .NET), Python.NET will pass an instance of the PyInt object into the method, and not a value of type System.Int32 or similar. Because QuickOPC cannot process the PyInt object, this will result in error messages like
Python.Runtime.PyInt cannot be serialized because it does not have a parameterless constructor.
or
There was an error generating the XML document.
You can prevent these errors by explicitly converting the Python integer into the intended .NET type. For example, instead of passing in 12345, you would pass in Int32(12345). Include the following import statement to get access to all types declared in .NET System namespace:
from System import *
For more information, see also Disable implicit conversions that might lose information in the Python.NET GitHub repository.
You can pass in Python lists to methods that expect .NET arrays as their input arguments. When .NET method return arrays, you can iterate over them using normal Python means.
If a .NET method expects an IEnumerable or IEnumerable<> as its input arguments, you can create an instance of .NET List<> class (in System.Collections.Generic namespace), and use the List's Add method to populate the list. Examples of this approach can be seen e.g. here: Examples - OPC Unified Architecture - Browsing for all kinds of nodes and Examples - OPC Unified Architecture - Unsubscribe from just some monitored items.