Commit e0ee31df authored by gokuldheep's avatar gokuldheep

Temp Server OPC UA

We have tried to implement all the functionalities that are provided by the OPC UA information model to its users as individual blocks. The acknowledgement of the server and client code is in progress.
parent 0da5f6e3
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/new_server" />
<excludeFolder url="file://$MODULE_DIR$/tempserver" />
<excludeFolder url="file://$MODULE_DIR$/tempserver_test" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7 (TempServer) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="58">
<item index="0" class="java.lang.String" itemvalue="auditlogslib" />
<item index="1" class="java.lang.String" itemvalue="saltlib" />
<item index="2" class="java.lang.String" itemvalue="commonlib" />
<item index="3" class="java.lang.String" itemvalue="userrolevalidationlib" />
<item index="4" class="java.lang.String" itemvalue="python_docx" />
<item index="5" class="java.lang.String" itemvalue="PyYAML" />
<item index="6" class="java.lang.String" itemvalue="pytz" />
<item index="7" class="java.lang.String" itemvalue="extensions_lib" />
<item index="8" class="java.lang.String" itemvalue="docx" />
<item index="9" class="java.lang.String" itemvalue="Pillow" />
<item index="10" class="java.lang.String" itemvalue="pkm-entities-lib" />
<item index="11" class="java.lang.String" itemvalue="pandas" />
<item index="12" class="java.lang.String" itemvalue="PyJWT" />
<item index="13" class="java.lang.String" itemvalue="Flask_JWT_Extended" />
<item index="14" class="java.lang.String" itemvalue="arrow" />
<item index="15" class="java.lang.String" itemvalue="jsonpath_rw" />
<item index="16" class="java.lang.String" itemvalue="logginglib" />
<item index="17" class="java.lang.String" itemvalue="pysaml2" />
<item index="18" class="java.lang.String" itemvalue="errorlib" />
<item index="19" class="java.lang.String" itemvalue="tx_manager_lib" />
<item index="20" class="java.lang.String" itemvalue="Jinja2" />
<item index="21" class="java.lang.String" itemvalue="licencecontrollerutilslib" />
<item index="22" class="java.lang.String" itemvalue="flask-compress" />
<item index="23" class="java.lang.String" itemvalue="elasticsearch" />
<item index="24" class="java.lang.String" itemvalue="lxml" />
<item index="25" class="java.lang.String" itemvalue="uploaderlib" />
<item index="26" class="java.lang.String" itemvalue="mongolib" />
<item index="27" class="java.lang.String" itemvalue="xmltodict" />
<item index="28" class="java.lang.String" itemvalue="Flask_Cors" />
<item index="29" class="java.lang.String" itemvalue="excellib" />
<item index="30" class="java.lang.String" itemvalue="comtypes" />
<item index="31" class="java.lang.String" itemvalue="concurrent-log-handler" />
<item index="32" class="java.lang.String" itemvalue="itsdangerous" />
<item index="33" class="java.lang.String" itemvalue="aeslib" />
<item index="34" class="java.lang.String" itemvalue="xlrd" />
<item index="35" class="java.lang.String" itemvalue="Flask" />
<item index="36" class="java.lang.String" itemvalue="coverage" />
<item index="37" class="java.lang.String" itemvalue="Werkzeug" />
<item index="38" class="java.lang.String" itemvalue="versionlib" />
<item index="39" class="java.lang.String" itemvalue="kafkalib" />
<item index="40" class="java.lang.String" itemvalue="paho_mqtt" />
<item index="41" class="java.lang.String" itemvalue="orjson" />
<item index="42" class="java.lang.String" itemvalue="xlsxwriter" />
<item index="43" class="java.lang.String" itemvalue="gunicorn" />
<item index="44" class="java.lang.String" itemvalue="kafka-python" />
<item index="45" class="java.lang.String" itemvalue="simplejson" />
<item index="46" class="java.lang.String" itemvalue="bcrypt" />
<item index="47" class="java.lang.String" itemvalue="customexceptionslib" />
<item index="48" class="java.lang.String" itemvalue="docxtpl" />
<item index="49" class="java.lang.String" itemvalue="pycryptodome" />
<item index="50" class="java.lang.String" itemvalue="uuidlib" />
<item index="51" class="java.lang.String" itemvalue="jsonpatch" />
<item index="52" class="java.lang.String" itemvalue="esprima" />
<item index="53" class="java.lang.String" itemvalue="openpyxl" />
<item index="54" class="java.lang.String" itemvalue="numpy" />
<item index="55" class="java.lang.String" itemvalue="pkm_changecontrol_lib" />
<item index="56" class="java.lang.String" itemvalue="pkm_entities_lib" />
<item index="57" class="java.lang.String" itemvalue="pkm_calculationbuilder_lib" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>
\ No newline at end of file
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (TempServer) (2)" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/../TempServer/.idea/TempServicer.iml" filepath="$PROJECT_DIR$/../TempServer/.idea/TempServicer.iml" />
</modules>
</component>
</project>
\ No newline at end of file
This diff is collapsed.
from enum import IntEnum
class AttributeIds(IntEnum):
NodeId = 1
NodeClass = 2
BrowseName = 3
DisplayName = 4
Description = 5
WriteMask = 6
UserWriteMask = 7
IsAbstract = 8
Symmetric = 9
InverseName = 10
ContainsNoLoops = 11
EventNotifier = 12
Value = 13
DataType = 14
ValueRank = 15
ArrayDimensions = 16
AccessLevel = 17
UserAccessLevel = 18
MinimumSamplingInterval = 19
Historizing = 20
Executable = 21
UserExecutable = 22
DataTypeDefinition = 23
RolePermissions = 24
UserRolePermissions = 25
AccessRestrictions = 26
AccessLevelEx = 27
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
from KL.constants import ObjectIds
from common.node import Node
class Shortcuts(object):
"""
This object contains Node objects to some commonly used nodes
"""
def __init__(self, server):
self.root = Node(server, ObjectIds.RootFolder)
self.objects = Node(server, ObjectIds.ObjectsFolder)
self.server = Node(server, ObjectIds.Server)
self.types = Node(server, ObjectIds.TypesFolder)
self.base_object_type = Node(server, ObjectIds.BaseObjectType)
self.base_data_type = Node(server, ObjectIds.BaseDataType)
self.base_event_type = Node(server, ObjectIds.BaseEventType)
self.base_variable_type = Node(server, ObjectIds.BaseVariableType)
self.folder_type = Node(server, ObjectIds.FolderType)
self.enum_data_type = Node(server, ObjectIds.Enumeration)
self.types = Node(server, ObjectIds.TypesFolder)
self.data_types = Node(server, ObjectIds.DataTypesFolder)
self.event_types = Node(server, ObjectIds.EventTypesFolder)
self.reference_types = Node(server, ObjectIds.ReferenceTypesFolder)
self.variable_types = Node(server, ObjectIds.VariableTypesFolder)
self.object_types = Node(server, ObjectIds.ObjectTypesFolder)
self.namespace_array = Node(server, ObjectIds.Server_NamespaceArray)
self.opc_binary = Node(server, ObjectIds.OPCBinarySchema_TypeSystem)
self.base_structure_type = Node(server, ObjectIds.Structure)
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
from KL.hand_protocol import SecurityPolicy
class SecurityPolicyBasic256Sha256(SecurityPolicy):
URI = "http://opcfoundation.org/UA/SecurityPolicy#Basic256Sha256"
signature_key_size = 32
symmetric_key_size = 32
AsymmetricEncryptionURI = "http://www.w3.org/2001/04/xmlenc#rsa-oaep"
AsymmetricSignatureURI = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
\ No newline at end of file
class CallbackDispatcher(object):
def __init__(self):
self._listeners = {}
\ No newline at end of file
import logging
from KL.binary import struct_from_binary, header_to_binary, struct_to_binary
from common import utils
from datetime import datetime
import copy
from KL import application_protocol, hand_protocol, types
logger = logging.getLogger('protocol')
class SecureConnection(object):
def __init__(self, security_policy):
self._sequence_number = 0
self._peer_sequence_number = None
self._incoming_parts = []
self.security_policy = security_policy
self._policies = []
self.security_token = application_protocol.ChannelSecurityToken()
self.next_security_token = application_protocol.ChannelSecurityToken()
self.prev_security_token = application_protocol.ChannelSecurityToken()
self.local_nonce = 0
self.remote_nonce = 0
self._open = False
self._allow_prev_token = False
self._max_chunk_size = 65536
def receive_from_header_and_body(self, header, body):
if header.MessageType == hand_protocol.MessageType.SecureOpen:
data = body.copy(header.body_size)
security_header = struct_from_binary(hand_protocol.AsymmetricAlgorithmHeader, data)
if not self.is_open():
# Only call select_policy if the channel isn't open. Otherwise
# it will break the Secure channel renewal.
self.select_policy(security_header.SecurityPolicyURI, security_header.SenderCertificate)
elif header.MessageType in (hand_protocol.MessageType.SecureMessage, hand_protocol.MessageType.SecureClose):
data = body.copy(header.body_size)
security_header = struct_from_binary(hand_protocol.SymmetricAlgorithmHeader, data)
self._check_sym_header(security_header)
if header.MessageType in (hand_protocol.MessageType.SecureMessage, hand_protocol.MessageType.SecureOpen,
hand_protocol.MessageType.SecureClose):
chunk = MessageChunk.from_header_and_body(self.security_policy, header, body)
return self._receive(chunk)
elif header.MessageType == hand_protocol.MessageType.Hello:
msg = struct_from_binary(hand_protocol.Hello, body)
self._max_chunk_size = msg.ReceiveBufferSize
return msg
elif header.MessageType == hand_protocol.MessageType.Acknowledge:
msg = struct_from_binary(hand_protocol.Acknowledge, body)
self._max_chunk_size = msg.SendBufferSize
return msg
elif header.MessageType == hand_protocol.MessageType.Error:
msg = struct_from_binary(hand_protocol.ErrorMessage, body)
logger.warning("Received an error: %s", msg)
return msg
else:
raise Exception("Unsupported message type {0}".format(header.MessageType))
def open(self, params, server):
"""
called on server side to open secure channel
"""
self.local_nonce = utils.create_nonce(self.security_policy.symmetric_key_size)
self.remote_nonce = params.ClientNonce
response = application_protocol.OpenSecureChannelResult()
response.ServerNonce = self.local_nonce
if not self._open or params.RequestType == application_protocol.SecurityTokenRequestType.Issue:
self._open = True
self.security_token.TokenId = 13 # random value
self.security_token.ChannelId = server.get_new_channel_id()
self.security_token.RevisedLifetime = params.RequestedLifetime
self.security_token.CreatedAt = datetime.utcnow()
response.SecurityToken = self.security_token
self.security_policy.make_local_symmetric_key(self.remote_nonce, self.local_nonce)
self.security_policy.make_remote_symmetric_key(self.local_nonce, self.remote_nonce)
else:
self.next_security_token = copy.deepcopy(self.security_token)
self.next_security_token.TokenId += 1
self.next_security_token.RevisedLifetime = params.RequestedLifetime
self.next_security_token.CreatedAt = datetime.utcnow()
response.SecurityToken = self.next_security_token
return response
def close(self):
self._open = False
def is_open(self):
return self._open
def set_policy_factories(self, policies):
self._policies = policies
def message_to_binary(self, message, message_type=hand_protocol.MessageType.SecureMessage, request_id=0):
"""
Convert OPC UA secure message to binary.
The only supported types are SecureOpen, SecureMessage, SecureClose
if message_type is SecureMessage, the AlgoritmHeader should be passed as arg
"""
chunks = MessageChunk.message_to_chunks(self.security_policy, message, self._max_chunk_size,
message_type=message_type, channel_id=self.security_token.ChannelId,
request_id=request_id, token_id=self.security_token.TokenId)
for chunk in chunks:
self._sequence_number += 1
if self._sequence_number >= (1 << 32):
logger.debug("Wrapping sequence number: %d -> 1", self._sequence_number)
self._sequence_number = 1
chunk.SequenceHeader.SequenceNumber = self._sequence_number
return b"".join([chunk.to_binary() for chunk in chunks])
class MessageChunk(types.FrozenClass):
def __init__(self, security_policy, body=b'', msg_type=hand_protocol.MessageType.SecureMessage,
chunk_type=hand_protocol.ChunkType.Single):
self.MessageHeader = hand_protocol.Header(msg_type, chunk_type)
if msg_type in (hand_protocol.MessageType.SecureMessage, hand_protocol.MessageType.SecureClose):
self.SecurityHeader = hand_protocol.SymmetricAlgorithmHeader()
elif msg_type == hand_protocol.MessageType.SecureOpen:
self.SecurityHeader = hand_protocol.AsymmetricAlgorithmHeader()
else:
raise Exception("Unsupported message type: {0}".format(msg_type))
self.SequenceHeader = hand_protocol.SequenceHeader()
self.Body = body
self.security_policy = security_policy
@staticmethod
def from_header_and_body(security_policy, header, buf):
assert len(buf) >= header.body_size, 'Full body expected here'
data = buf.copy(header.body_size)
buf.skip(header.body_size)
if header.MessageType in (hand_protocol.MessageType.SecureMessage, hand_protocol.MessageType.SecureClose):
security_header = struct_from_binary(hand_protocol.SymmetricAlgorithmHeader, data)
crypto = security_policy.symmetric_cryptography
elif header.MessageType == hand_protocol.MessageType.SecureOpen:
security_header = struct_from_binary(hand_protocol.AsymmetricAlgorithmHeader, data)
crypto = security_policy.asymmetric_cryptography
else:
raise Exception("Unsupported message type: {0}".format(header.MessageType))
obj = MessageChunk(crypto)
obj.MessageHeader = header
obj.SecurityHeader = security_header
decrypted = crypto.decrypt(data.read(len(data)))
signature_size = crypto.vsignature_size()
if signature_size > 0:
signature = decrypted[-signature_size:]
decrypted = decrypted[:-signature_size]
crypto.verify(header_to_binary(obj.MessageHeader) + struct_to_binary(obj.SecurityHeader) + decrypted,
signature)
data = utils.Buffer(crypto.remove_padding(decrypted))
obj.SequenceHeader = struct_from_binary(hand_protocol.SequenceHeader, data)
obj.Body = data.read(len(data))
return obj
import logging
from KL.attribute_ids import AttributeIds
from KL.types import NodeId, QualifiedName, NodeIdType
from KL.constants import ObjectIds
from KL.application_protocol import AddNodesItem, NodeClass, ReferenceDescription
from KL.hand_protocol import ObjectAttributes, VariableAttributes, MethodAttributes, DataTypeAttributes
import common.utils
logger = logging.getLogger(__name__)
def instantiate(parent, node_type, nodeid=None, bname=None, dname=None, idx=0, instantiate_optional=True):
rdesc = _rdesc_from_node(parent, node_type)
rdesc.TypeDefinition = node_type.nodeid
from common.node import Node
if nodeid is None:
nodeid = NodeId(namespaceidx=idx) # will trigger automatic node generation in namespace idx
if bname is None:
bname = rdesc.BrowseName
elif isinstance(bname, str):
bname = QualifiedName.from_string(bname)
nodeids = _instantiate_node(
parent.server,
Node(parent.server, rdesc.NodeId),
parent.nodeid,
rdesc,
nodeid,
bname,
dname=dname,
instantiate_optional=instantiate_optional)
return [Node(parent.server, nid) for nid in nodeids]
def _instantiate_node(server,
node_type,
parentid,
rdesc,
nodeid,
bname,
dname=None,
recursive=True,
instantiate_optional=True):
"""
instantiate a node type under parent
"""
addnode = AddNodesItem()
addnode.RequestedNewNodeId = nodeid
addnode.BrowseName = bname
addnode.ParentNodeId = parentid
addnode.ReferenceTypeId = rdesc.ReferenceTypeId
addnode.TypeDefinition = rdesc.TypeDefinition
if rdesc.NodeClass in (NodeClass.Object, NodeClass.ObjectType):
addnode.NodeClass = NodeClass.Object
_read_and_copy_attrs(node_type, ObjectAttributes(), addnode)
elif rdesc.NodeClass in (NodeClass.Variable, NodeClass.VariableType):
addnode.NodeClass = NodeClass.Variable
_read_and_copy_attrs(node_type, VariableAttributes(), addnode)
elif rdesc.NodeClass in (NodeClass.Method, ):
addnode.NodeClass = NodeClass.Method
_read_and_copy_attrs(node_type, MethodAttributes(), addnode)
elif rdesc.NodeClass in (NodeClass.DataType, ):
addnode.NodeClass = NodeClass.DataType
_read_and_copy_attrs(node_type, DataTypeAttributes(), addnode)
else:
logger.error("Instantiate: Node class not supported: %s", rdesc.NodeClass)
raise RuntimeError("Instantiate: Node class not supported")
return
if dname is not None:
addnode.NodeAttributes.DisplayName = dname
res = server.add_nodes([addnode])[0]
res.StatusCode.check()
added_nodes = [res.AddedNodeId]
if recursive:
from common.node import Node
parents = utils.get_node_supertypes(node_type, includeitself=True)
node = Node(server, res.AddedNodeId)
for parent in parents:
descs = parent.get_children_descriptions(includesubtypes=False)
for c_rdesc in descs:
# skip items that already exists, prefer the 'lowest' one in object hierarchy
if not utils.is_child_present(node, c_rdesc.BrowseName):
c_node_type = Node(server, c_rdesc.NodeId)
refs = c_node_type.get_referenced_nodes(refs=ObjectIds.HasModellingRule)
if not refs:
# spec says to ignore nodes without modelling rules
logger.info("Instantiate: Skip node without modelling rule %s as part of %s", c_rdesc.BrowseName, addnode.BrowseName)
continue
# exclude nodes with optional ModellingRule if requested
if not instantiate_optional and refs[0].nodeid in (NodeId(ObjectIds.ModellingRule_Optional), NodeId(ObjectIds.ModellingRule_OptionalPlaceholder)):
logger.info("Instantiate: Skip optional node %s as part of %s", c_rdesc.BrowseName, addnode.BrowseName)
continue
# if root node being instantiated has a String NodeId, create the children with a String NodeId
if res.AddedNodeId.NodeIdType is NodeIdType.String:
inst_nodeid = res.AddedNodeId.Identifier + "." + c_rdesc.BrowseName.Name
nodeids = _instantiate_node(
server,
c_node_type,
res.AddedNodeId,
c_rdesc,
nodeid=NodeId(identifier=inst_nodeid, namespaceidx=res.AddedNodeId.NamespaceIndex),
bname=c_rdesc.BrowseName,
instantiate_optional=instantiate_optional)
else:
nodeids = _instantiate_node(
server,
c_node_type,
res.AddedNodeId,
c_rdesc,
nodeid=NodeId(namespaceidx=res.AddedNodeId.NamespaceIndex),
bname=c_rdesc.BrowseName,
instantiate_optional=instantiate_optional)
added_nodes.extend(nodeids)
return added_nodes
def _rdesc_from_node(parent, node):
results = node.get_attributes([AttributeIds.NodeClass, AttributeIds.BrowseName, AttributeIds.DisplayName])
nclass, qname, dname = [res.Value.Value for res in results]
rdesc = ReferenceDescription()
rdesc.NodeId = node.nodeid
rdesc.BrowseName = qname
rdesc.DisplayName = dname
rdesc.NodeClass = nclass
if parent.get_type_definition() == NodeId(ObjectIds.FolderType):
rdesc.ReferenceTypeId = NodeId(ObjectIds.Organizes)
else:
rdesc.ReferenceTypeId = NodeId(ObjectIds.HasComponent)
typedef = node.get_type_definition()
if typedef:
rdesc.TypeDefinition = typedef
return rdesc
def _read_and_copy_attrs(node_type, struct, addnode):
names = [name for name in struct.__dict__.keys() if not name.startswith("_") and name not in ("BodyLength", "TypeId", "SpecifiedAttributes", "Encoding", "IsAbstract", "EventNotifier")]
attrs = [getattr(AttributeIds, name) for name in names]
for name in names:
results = node_type.get_attributes(attrs)
for idx, name in enumerate(names):
if results[idx].StatusCode.is_good():
if name == "Value":
setattr(struct, name, results[idx].Value)
else:
setattr(struct, name, results[idx].Value.Value)
else:
logger.warning("Instantiate: while copying attributes from node type {0!s}, attribute {1!s}, statuscode is {2!s}".format(node_type, name, results[idx].StatusCode))
addnode.NodeAttributes = struct
\ No newline at end of file
from common import node
from KL.types import NodeId, LocalizedText, ValueRank, AccessLevel, QualifiedName
from KL.constants import ObjectIds
from KL.application_protocol import AddNodesItem, NodeClass, VariableAttributes, Variant, VariantType, ObjectAttributes
from common import instantiate
def _parse_nodeid_qname(*args):
try:
if isinstance(args[0], int):
nodeid = NodeId(0, int(args[0]))
qname = QualifiedName(args[1], int(args[0]))
return nodeid, qname
if isinstance(args[0], NodeId):
nodeid = args[0]
elif isinstance(args[0], str):
nodeid = NodeId.from_string(args[0])
else:
raise RuntimeError()
if isinstance(args[1], QualifiedName):
qname = args[1]
elif isinstance(args[1], str):
qname = QualifiedName.from_string(args[1])
else:
raise RuntimeError()
return nodeid, qname
except Exception as ex:
raise TypeError(
"This method takes either a namespace index and a string as argument or a nodeid and a qualifiedname. Received arguments {0} and got exception {1}".format(
args, ex))
def create_object(parent, nodeid, bname, objecttype=None):
nodeid, qname = _parse_nodeid_qname(nodeid, bname)
if objecttype is not None:
objecttype = node.Node(parent.server, objecttype)
dname = LocalizedText(qname.Name)
nodes = instantiate.instantiate(parent, objecttype, nodeid, bname=qname, dname=dname)[0]
return nodes
else:
return node.Node(parent.server,
_create_object(parent.server, parent.nodeid, nodeid, qname, ObjectIds.BaseObjectType))
def create_variable(parent, nodeid, bname, val, varianttype=None, datatype=None):
nodeid, qname = _parse_nodeid_qname(nodeid, bname)
var = Variant(val, varianttype)
if datatype and isinstance(datatype, int):
datatype = NodeId(datatype, 0)
if datatype and not isinstance(datatype, NodeId):
raise RuntimeError("datatype argument must be a nodeid or an int refering to a nodeid")
return node.Node(parent.server,
_create_variable(parent.server, parent.nodeid, nodeid, qname, var, datatype=datatype,
isproperty=False))
def _create_variable(server, parentnodeid, nodeid, qname, var, datatype=None, isproperty=False):
addnode = AddNodesItem()
addnode.RequestedNewNodeId = nodeid
addnode.BrowseName = qname
addnode.NodeClass = NodeClass.Variable
addnode.ParentNodeId = parentnodeid
if isproperty:
addnode.ReferenceTypeId = NodeId(ObjectIds.HasProperty)
addnode.TypeDefinition = NodeId(ObjectIds.PropertyType)
else:
addnode.ReferenceTypeId = NodeId(ObjectIds.HasComponent)
addnode.TypeDefinition = NodeId(ObjectIds.BaseDataVariableType)
attrs = VariableAttributes()
attrs.Description = LocalizedText(qname.Name)
attrs.DisplayName = LocalizedText(qname.Name)
if datatype:
attrs.DataType = datatype
else:
attrs.DataType = _guess_datatype(var)
attrs.Value = var
if not isinstance(var.Value, (list, tuple)):
attrs.ValueRank = ValueRank.Scalar
else:
if var.Dimensions:
attrs.ValueRank = len(var.Dimensions)
attrs.ArrayDimensions = var.Dimensions
attrs.WriteMask = 0
attrs.UserWriteMask = 0
attrs.Historizing = False
attrs.AccessLevel = AccessLevel.CurrentRead.mask
attrs.UserAccessLevel = AccessLevel.CurrentRead.mask
addnode.NodeAttributes = attrs
results = server.add_nodes([addnode])
results[0].StatusCode.check()
return results[0].AddedNodeId
def _create_object(server, parentnodeid, nodeid, qname, objecttype):
addnode = AddNodesItem()
addnode.RequestedNewNodeId = nodeid
addnode.BrowseName = qname
addnode.ParentNodeId = parentnodeid
if node.Node(server, parentnodeid).get_type_definition() == NodeId(ObjectIds.FolderType):
addnode.ReferenceTypeId = NodeId(ObjectIds.Organizes)
else:
addnode.ReferenceTypeId = NodeId(ObjectIds.HasComponent)
addnode.NodeClass = NodeClass.Object
if isinstance(objecttype, int):
addnode.TypeDefinition = NodeId(objecttype)
else:
addnode.TypeDefinition = objecttype
attrs = ObjectAttributes()
attrs.EventNotifier = 0
attrs.Description = LocalizedText(qname.Name)
attrs.DisplayName = LocalizedText(qname.Name)
attrs.WriteMask = 0
attrs.UserWriteMask = 0
addnode.NodeAttributes = attrs
results = server.add_nodes([addnode])
results[0].StatusCode.check()
return results[0].AddedNodeId
def _guess_datatype(variant):
if variant.VariantType == VariantType.ExtensionObject:
if variant.Value is None:
raise Exception("Cannot guess DataType from Null ExtensionObject")
if type(variant.Value) in (list, tuple):
if len(variant.Value) == 0:
raise Exception("Cannot guess DataType from Null ExtensionObject")
extobj = variant.Value[0]
else:
extobj = variant.Value
classname = extobj.__class__.__name__
return NodeId(getattr(ObjectIds, classname))
else:
return NodeId(getattr(ObjectIds, variant.VariantType.name))
from KL.constants import ObjectIds
from KL.types import *
from KL.attribute_ids import AttributeIds
from datetime import datetime
from KL.application_protocol import ReadValueId, ReadParameters, BrowseDirection, NodeClass, BrowseNextParameters, \
BrowseDescription, BrowseParameters, BrowseResultMask
from common import manage_nodes
def _to_nodeid(nodeid):
if isinstance(nodeid, int):
return TwoByteNodeId(nodeid)
elif isinstance(nodeid, Node):
return nodeid.nodeid
elif isinstance(nodeid, NodeId):
return nodeid
elif type(nodeid) in (str, bytes):
return NodeId.from_string(nodeid)
else:
raise Exception("Could not resolve '{0}' to a type id".format(nodeid))
class Node(object):
"""
High level node object, to access node attribute,
browse and populate address space.
Node objects are usefull as-is but they do not expose the entire
OPC-UA protocol. Feel free to look at the code of this class and call
directly UA services methods to optimize your code
"""
def __init__(self, server, nodeid):
self.server = server
self.nodeid = None
if isinstance(nodeid, Node):
self.nodeid = nodeid.nodeid
elif isinstance(nodeid, NodeId):
self.nodeid = nodeid
elif type(nodeid) in (str, bytes):
self.nodeid = NodeId.from_string(nodeid)
elif isinstance(nodeid, int):
self.nodeid = NodeId(nodeid, 0)
else:
raise Exception(
"argument to node must be a NodeId object or a string defining a nodeid found {0} of type {1}".format(
nodeid, type(nodeid)))
self.basenodeid = None
def __eq__(self, other):
if isinstance(other, Node) and self.nodeid == other.nodeid:
return True
return False
def __ne__(self, other):
return not self.__eq__(other)
def __str__(self):
return self.nodeid.to_string()
def __repr__(self):
return "Node({0})".format(self.nodeid)
def __hash__(self):
return self.nodeid.__hash__()
def set_value(self, value, varianttype=None):
datavalue = None
if isinstance(value, DataValue):
datavalue = value
elif isinstance(value, Variant):
datavalue = DataValue(value)
datavalue.SourceTimestamp = datetime.utcnow()
else:
datavalue = DataValue(Variant(value, varianttype))
datavalue.SourceTimestamp = datetime.utcnow()
self.set_attribute(AttributeIds.Value, datavalue)
set_data_value = set_value
def get_value(self):
"""
Get value of a node as a python type. Only variables ( and properties) have values.
An exception will be generated for other node types.
WARNING: on server side, this function returns a ref to object in ua database. Do not modify it if it is a mutable
object unless you know what you are doing
"""
result = self.get_data_value()
return result.Value.Value
def set_attribute(self, attributeid, datavalue):
"""
Set an attribute of a node
attributeid is a member of AttributeIds
datavalue is a DataValue object
"""
attr = WriteValue()
attr.NodeId = self.nodeid
attr.AttributeId = attributeid
attr.Value = datavalue
params = WriteParameters()
params.NodesToWrite = [attr]
result = self.server.write(params)
result[0].check()
def get_data_value(self):
"""
Get value of a node as a DataValue object. Only variables (and properties) have values.
An exception will be generated for other node types.
DataValue contain a variable value as a variant as well as server and source timestamps
"""
return self.get_attribute(AttributeIds.Value)
def get_attribute(self, attr):
"""
Read one attribute of a node
result code from server is checked and an exception is raised in case of error
"""
rv = ReadValueId()
rv.NodeId = self.nodeid
rv.AttributeId = attr
params = ReadParameters()
params.NodesToRead.append(rv)
result = self.server.read(params)
result[0].StatusCode.check()
return result[0]
def add_object(self, nodeid, bname, objecttype=None):
return manage_nodes.create_object(self, nodeid, bname, objecttype)
def add_variable(self, nodeid, bname, val, varianttype=None, datatype=None):
return manage_nodes.create_variable(self, nodeid, bname, val, varianttype, datatype)
def get_type_definition(self):
references = self.get_references(refs=ObjectIds.HasTypeDefinition, direction=BrowseDirection.Forward)
if len(references) == 0:
return None
return references[0].NodeId
def get_references(self, refs=ObjectIds.References, direction=BrowseDirection.Both,
nodeclassmask=NodeClass.Unspecified, includesubtypes=True):
desc = BrowseDescription()
desc.BrowseDirection = direction
desc.ReferenceTypeId = _to_nodeid(refs)
desc.IncludeSubtypes = includesubtypes
desc.NodeClassMask = nodeclassmask
desc.ResultMask = BrowseResultMask.All
desc.NodeId = self.nodeid
params = BrowseParameters()
params.View.Timestamp = get_win_epoch()
params.NodesToBrowse.append(desc)
params.RequestedMaxReferencesPerNode = 0
results = self.server.browse(params)
references = self._browse_next(results)
return references
def _browse_next(self, results):
references = results[0].References
while results[0].ContinuationPoint:
params = BrowseNextParameters()
params.ContinuationPoints = [results[0].ContinuationPoint]
params.ReleaseContinuationPoints = False
results = self.server.browse_next(params)
references.extend(results[0].References)
return references
def get_referenced_nodes(self, refs=ObjectIds.References, direction=BrowseDirection.Both,
nodeclassmask=NodeClass.Unspecified, includesubtypes=True):
"""
returns referenced nodes based on specific filter
Paramters are the same as for get_references
"""
references = self.get_references(refs, direction, nodeclassmask, includesubtypes)
nodes = []
for desc in references:
node = Node(self.server, desc.NodeId)
nodes.append(node)
return nodes
def get_children_descriptions(self, refs=ObjectIds.HierarchicalReferences, nodeclassmask=NodeClass.Unspecified,
includesubtypes=True):
return self.get_references(refs, BrowseDirection.Forward, nodeclassmask, includesubtypes)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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