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 ...@@ -13,6 +13,7 @@ from ..common.subscription import Subscription
from ..common.shortcuts import Shortcuts from ..common.shortcuts import Shortcuts
from ..common.structures import load_type_definitions, load_enums from ..common.structures import load_type_definitions, load_enums
from ..common.utils import create_nonce from ..common.utils import create_nonce
from ..common.ua_utils import value_to_datavalue
from ..crypto import uacrypto, security_policies from ..crypto import uacrypto, security_policies
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
...@@ -28,7 +29,6 @@ class Client: ...@@ -28,7 +29,6 @@ class Client:
use UaClient object, available as self.uaclient use UaClient object, available as self.uaclient
which offers the raw OPC-UA services interface. which offers the raw OPC-UA services interface.
""" """
def __init__(self, url: str, timeout: int = 4, loop=None): def __init__(self, url: str, timeout: int = 4, loop=None):
""" """
:param url: url of the server. :param url: url of the server.
...@@ -78,9 +78,7 @@ class Client: ...@@ -78,9 +78,7 @@ class Client:
""" """
_logger.info("find_endpoint %r %r %r", endpoints, security_mode, policy_uri) _logger.info("find_endpoint %r %r %r", endpoints, security_mode, policy_uri)
for ep in endpoints: for ep in endpoints:
if (ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and if (ep.EndpointUrl.startswith(ua.OPC_TCP_SCHEME) and ep.SecurityMode == security_mode and ep.SecurityPolicyUri == policy_uri):
ep.SecurityMode == security_mode and
ep.SecurityPolicyUri == policy_uri):
return ep return ep
raise ua.UaError("No matching endpoints: {0}, {1}".format(security_mode, policy_uri)) raise ua.UaError("No matching endpoints: {0}, {1}".format(security_mode, policy_uri))
...@@ -117,11 +115,12 @@ class Client: ...@@ -117,11 +115,12 @@ class Client:
raise ua.UaError("Wrong format: `{}`, expected at least 4 comma-separated values".format(string)) raise ua.UaError("Wrong format: `{}`, expected at least 4 comma-separated values".format(string))
policy_class = getattr(security_policies, "SecurityPolicy{}".format(parts[0])) policy_class = getattr(security_policies, "SecurityPolicy{}".format(parts[0]))
mode = getattr(ua.MessageSecurityMode, parts[1]) mode = getattr(ua.MessageSecurityMode, parts[1])
return await self.set_security( return await self.set_security(policy_class, parts[2], parts[3], parts[4] if len(parts) >= 5 else None, mode)
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, server_certificate_path: str = None,
mode: ua.MessageSecurityMode = ua.MessageSecurityMode.SignAndEncrypt): mode: ua.MessageSecurityMode = ua.MessageSecurityMode.SignAndEncrypt):
""" """
...@@ -580,8 +579,18 @@ class Client: ...@@ -580,8 +579,18 @@ class Client:
async def get_values(self, nodes): 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] nodeids = [node.nodeid for node in nodes]
results = await self.uaclient.get_attribute(nodes, ua.AttributeIds.Value) results = await self.uaclient.get_attributes(nodeids, ua.AttributeIds.Value)
return [result.Value.Value for result in results] 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: ...@@ -642,15 +642,35 @@ class UaClient:
response.ResponseHeader.ServiceResult.check() response.ResponseHeader.ServiceResult.check()
# nothing to return for this service # nothing to return for this service
async def get_attribute(self, nodes, attr): async def get_attributes(self, nodeids, attr):
self.logger.info("get_attribute") self.logger.info("get_attributes of several nodes")
request = ua.ReadRequest() request = ua.ReadRequest()
for node in nodes: for nodeid in nodeids:
rv = ua.ReadValueId() rv = ua.ReadValueId()
rv.NodeId = node rv.NodeId = nodeid
rv.AttributeId = attr rv.AttributeId = attr
request.Parameters.NodesToRead.append(rv) request.Parameters.NodesToRead.append(rv)
data = await self.protocol.send_request(request) data = await self.protocol.send_request(request)
response = struct_from_binary(ua.ReadResponse, data) response = struct_from_binary(ua.ReadResponse, data)
response.ResponseHeader.ServiceResult.check() response.ResponseHeader.ServiceResult.check()
return response.Results 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 @@ ...@@ -2,11 +2,12 @@
High level node object, to access node attribute High level node object, to access node attribute
and browse address space and browse address space
""" """
from datetime import datetime
import logging import logging
from asyncua import ua from asyncua import ua
from .ua_utils import value_to_datavalue
from .events import Event, get_filter_from_event_type from .events import Event, get_filter_from_event_type
from .ua_utils import data_type_to_variant_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, \ from .manage_nodes import create_folder, create_object, create_object_type, create_variable, create_variable_type, \
...@@ -214,16 +215,8 @@ class Node: ...@@ -214,16 +215,8 @@ class Node:
and you modfy it afterward, then the object in db will be modified without any and you modfy it afterward, then the object in db will be modified without any
data change event generated data change event generated
""" """
datavalue = None dv = value_to_datavalue(value, varianttype)
if isinstance(value, ua.DataValue): await self.set_attribute(ua.AttributeIds.Value, dv)
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)
set_data_value = set_value set_data_value = set_value
......
...@@ -13,6 +13,22 @@ from asyncua import ua ...@@ -13,6 +13,22 @@ from asyncua import ua
logger = logging.getLogger('__name__') 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): def val_to_string(val, truncate=False):
""" """
convert a python object or python-asyncua object to a string 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): ...@@ -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 node: can be a ua.Node or ua.NodeId
:param includeitself: include also node to the list :param includeitself: include also node to the list
:param skipbase don't include the toplevel one :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 = [] parents = []
if includeitself: if includeitself:
...@@ -202,7 +218,7 @@ async def is_child_present(node, browsename): ...@@ -202,7 +218,7 @@ async def is_child_present(node, browsename):
return if a browsename is present a child from the provide node return if a browsename is present a child from the provide node
:param node: node wherein to find the browsename :param node: node wherein to find the browsename
:param browsename: browsename to search :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() child_descs = await node.get_children_descriptions()
for child_desc in child_descs: for child_desc in child_descs:
...@@ -229,7 +245,7 @@ async def get_base_data_type(datatype): ...@@ -229,7 +245,7 @@ async def get_base_data_type(datatype):
Looks up the base datatype of the provided datatype Node Looks up the base datatype of the provided datatype Node
The base datatype is either: 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. A primitive type (ns=0, i<=21) or a complex one (ns=0 i>21 and i<=30) like Enum and Struct.
Args: Args:
datatype: NodeId of a datype of a variable datatype: NodeId of a datype of a variable
Returns: Returns:
...@@ -245,7 +261,7 @@ async def get_base_data_type(datatype): ...@@ -245,7 +261,7 @@ async def get_base_data_type(datatype):
async def get_nodes_of_namespace(server, namespaces=None): 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: Args:
server: opc ua server to use server: opc ua server to use
namespaces: list of string uri or int indexes of the namespace to export 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): ...@@ -87,3 +87,24 @@ async def test_custom_enum_struct(server, client):
val = await myvar.get_value() val = await myvar.get_value()
assert 242 == val.IntVal1 assert 242 == val.IntVal1
assert ua.ExampleEnum.EnumVal2 == val.EnumVal 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