Commit a29444fb authored by oroulet's avatar oroulet Committed by Christian Bergmiller

add Client.set_values for wrtting multiple node values at once

parent 9cb6411c
......@@ -13,6 +13,7 @@ from ..common.subscription import Subscription
from ..common.shortcuts import Shortcuts
from ..common.structures import load_type_definitions, load_enums
from ..common.utils import create_nonce
from ..common.ua_utils import value_to_datavalue
from ..crypto import uacrypto, security_policies
_logger = logging.getLogger(__name__)
......@@ -28,7 +29,6 @@ class Client:
use UaClient object, available as self.uaclient
which offers the raw OPC-UA services interface.
"""
def __init__(self, url: str, timeout: int = 4, loop=None):
"""
:param url: url of the server.
......@@ -78,9 +78,7 @@ class Client:
"""
_logger.info("find_endpoint %r %r %r", endpoints, security_mode, policy_uri)
for ep in endpoints:
if (ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and
ep.SecurityMode == security_mode and
ep.SecurityPolicyUri == policy_uri):
if (ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and ep.SecurityMode == security_mode and ep.SecurityPolicyUri == policy_uri):
return ep
raise ua.UaError("No matching endpoints: {0}, {1}".format(security_mode, policy_uri))
......@@ -117,11 +115,12 @@ class Client:
raise ua.UaError("Wrong format: `{}`, expected at least 4 comma-separated values".format(string))
policy_class = getattr(security_policies, "SecurityPolicy{}".format(parts[0]))
mode = getattr(ua.MessageSecurityMode, parts[1])
return await self.set_security(
policy_class, parts[2], parts[3], parts[4] if len(parts) >= 5 else None, mode
)
return await self.set_security(policy_class, parts[2], parts[3], parts[4] if len(parts) >= 5 else None, mode)
async def set_security(self, policy, certificate_path: str, private_key_path: str,
async def set_security(self,
policy,
certificate_path: str,
private_key_path: str,
server_certificate_path: str = None,
mode: ua.MessageSecurityMode = ua.MessageSecurityMode.SignAndEncrypt):
"""
......@@ -580,8 +579,18 @@ class Client:
async def get_values(self, nodes):
"""
Read the value of multiple nodes in one roundtrip.
Read the value of multiple nodes in one ua call.
"""
nodes = [node.nodeid for node in nodes]
results = await self.uaclient.get_attribute(nodes, ua.AttributeIds.Value)
nodeids = [node.nodeid for node in nodes]
results = await self.uaclient.get_attributes(nodeids, ua.AttributeIds.Value)
return [result.Value.Value for result in results]
async def set_values(self, nodes, values):
"""
Write values to multiple nodes in one ua call
"""
nodeids = [node.nodeid for node in nodes]
dvs = [value_to_datavalue(val) for val in values]
results = await self.uaclient.set_attributes(nodeids, dvs, ua.AttributeIds.Value)
for result in results:
result.check()
......@@ -642,15 +642,35 @@ class UaClient:
response.ResponseHeader.ServiceResult.check()
# nothing to return for this service
async def get_attribute(self, nodes, attr):
self.logger.info("get_attribute")
async def get_attributes(self, nodeids, attr):
self.logger.info("get_attributes of several nodes")
request = ua.ReadRequest()
for node in nodes:
for nodeid in nodeids:
rv = ua.ReadValueId()
rv.NodeId = node
rv.NodeId = nodeid
rv.AttributeId = attr
request.Parameters.NodesToRead.append(rv)
data = await self.protocol.send_request(request)
response = struct_from_binary(ua.ReadResponse, data)
response.ResponseHeader.ServiceResult.check()
return response.Results
async def set_attributes(self, nodeids, datavalues, attributeid=ua.AttributeIds.Value):
"""
Set an attribute of multiple nodes
datavalue is a ua.DataValue object
"""
self.logger.info("set_attributes of several nodes")
request = ua.WriteRequest()
for idx, nodeid in enumerate(nodeids):
attr = ua.WriteValue()
attr.NodeId = nodeid
attr.AttributeId = attributeid
attr.Value = datavalues[idx]
request.Parameters.NodesToWrite.append(attr)
data = await self.protocol.send_request(request)
response = struct_from_binary(ua.WriteResponse, data)
response.ResponseHeader.ServiceResult.check()
return response.Results
......@@ -2,11 +2,12 @@
High level node object, to access node attribute
and browse address space
"""
from datetime import datetime
import logging
from asyncua import ua
from .ua_utils import value_to_datavalue
from .events import Event, get_filter_from_event_type
from .ua_utils import data_type_to_variant_type
from .manage_nodes import create_folder, create_object, create_object_type, create_variable, create_variable_type, \
......@@ -214,16 +215,8 @@ class Node:
and you modfy it afterward, then the object in db will be modified without any
data change event generated
"""
datavalue = None
if isinstance(value, ua.DataValue):
datavalue = value
elif isinstance(value, ua.Variant):
datavalue = ua.DataValue(value)
datavalue.SourceTimestamp = datetime.utcnow()
else:
datavalue = ua.DataValue(ua.Variant(value, varianttype))
datavalue.SourceTimestamp = datetime.utcnow()
await self.set_attribute(ua.AttributeIds.Value, datavalue)
dv = value_to_datavalue(value, varianttype)
await self.set_attribute(ua.AttributeIds.Value, dv)
set_data_value = set_value
......
......@@ -13,6 +13,22 @@ from asyncua import ua
logger = logging.getLogger('__name__')
def value_to_datavalue(val, varianttype=None):
"""
convert anyting to a DataValue using varianttype
"""
datavalue = None
if isinstance(val, ua.DataValue):
datavalue = val
elif isinstance(val, ua.Variant):
datavalue = ua.DataValue(val)
datavalue.SourceTimestamp = datetime.utcnow()
else:
datavalue = ua.DataValue(ua.Variant(val, varianttype))
datavalue.SourceTimestamp = datetime.utcnow()
return datavalue
def val_to_string(val, truncate=False):
"""
convert a python object or python-asyncua object to a string
......@@ -160,7 +176,7 @@ async def get_node_supertypes(node, includeitself=False, skipbase=True):
:param node: can be a ua.Node or ua.NodeId
:param includeitself: include also node to the list
:param skipbase don't include the toplevel one
:returns list of ua.Node, top parent first
:returns list of ua.Node, top parent first
"""
parents = []
if includeitself:
......@@ -202,7 +218,7 @@ async def is_child_present(node, browsename):
return if a browsename is present a child from the provide node
:param node: node wherein to find the browsename
:param browsename: browsename to search
:returns returne True if the browsename is present else False
:returns returne True if the browsename is present else False
"""
child_descs = await node.get_children_descriptions()
for child_desc in child_descs:
......@@ -229,7 +245,7 @@ async def get_base_data_type(datatype):
Looks up the base datatype of the provided datatype Node
The base datatype is either:
A primitive type (ns=0, i<=21) or a complex one (ns=0 i>21 and i<=30) like Enum and Struct.
Args:
datatype: NodeId of a datype of a variable
Returns:
......@@ -245,7 +261,7 @@ async def get_base_data_type(datatype):
async def get_nodes_of_namespace(server, namespaces=None):
"""
Get the nodes of one or more namespaces .
Get the nodes of one or more namespaces .
Args:
server: opc ua server to use
namespaces: list of string uri or int indexes of the namespace to export
......
......@@ -87,3 +87,24 @@ async def test_custom_enum_struct(server, client):
val = await myvar.get_value()
assert 242 == val.IntVal1
assert ua.ExampleEnum.EnumVal2 == val.EnumVal
async def test_multiple_read_and_write(server, client):
f = await server.nodes.objects.add_folder(3, 'Multiple_read_write_test')
v1 = await f.add_variable(3, "a", 1)
await v1.set_writable()
v2 = await f.add_variable(3, "b", 2)
await v2.set_writable()
v3 = await f.add_variable(3, "c", 3)
await v3.set_writable()
v_ro = await f.add_variable(3, "ro", 3)
vals = await client.get_values([v1, v2, v3])
assert vals == [1, 2, 3]
await client.set_values([v1, v2, v3], [4, 5, 6])
vals = await client.get_values([v1, v2, v3])
assert vals == [4, 5, 6]
with pytest.raises(ua.uaerrors.BadUserAccessDenied):
await client.set_values([v1, v2, v_ro], [4, 5, 6])
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment