Commit 2566969b authored by ORD's avatar ORD

Merge pull request #114 from iirob/master

Server side deleting of nodes.
parents 6ee90273 96958774
import sys
sys.path.insert(0, "..")
import logging
import time
try:
from IPython import embed
except ImportError:
import code
def embed():
vars = globals()
vars.update(locals())
shell = code.InteractiveConsole(vars)
shell.interact()
from opcua import Client
from opcua import ua
class SubHandler(object):
"""
Subscription Handler. To receive events from server for a subscription
data_change and event methods are called directly from receiving thread.
Do not do expensive, slow or network operation there. Create another
thread if you need to do such a thing
"""
def datachange_notification(self, node, val, data):
print("Python: New data change event", node, val)
def event_notification(self, event):
print("Python: New event", event)
def status_change_notification(self, status):
print ("Python: New status change", status)
if __name__ == "__main__":
logging.basicConfig(level=logging.WARN)
#logger = logging.getLogger("KeepAlive")
#logger.setLevel(logging.DEBUG)
#client = Client("opc.tcp://localhost:4840/freeopcua/server/")
client = Client("opc.tcp://admin@localhost:4840/freeopcua/server/") #connect using a user
try:
client.connect()
# Client has a few methods to get proxy to UA nodes that should always be in address space such as Root or Objects
root = client.get_root_node()
print("Root node is: ", root)
objects = client.get_objects_node()
print("Objects node is: ", objects)
# Node objects have methods to read and write node attributes as well as browse or populate address space
print("Children of root are: ", root.get_children())
# get a specific node knowing its node id
#var = client.get_node(ua.NodeId(1002, 2))
#var = client.get_node("ns=3;i=2002")
#print(var)
#var.get_data_value() # get value of node as a DataValue object
#var.get_value() # get value of node as a python builtin
#var.set_value(ua.Variant([23], ua.VariantType.Int64)) #set node value using explicit data type
#var.set_value(3.9) # set node value using implicit data type
# Now getting a variable node using its browse path
myvar = root.get_child(["0:Objects", "2:MyObject", "2:MyVariable"])
obj = root.get_child(["0:Objects", "2:MyObject"])
print("myvar is: ", myvar)
# subscribing to a variable node
handler = SubHandler()
sub = client.create_subscription(500, handler)
handle = sub.subscribe_data_change(myvar)
time.sleep(0.1)
# we can also subscribe to events from server
sub.subscribe_events()
# sub.unsubscribe(handle)
# sub.delete()
# calling a method on server
res = obj.call_method("2:multiply", 3, "klk")
print("method result is: ", res)
print("Children of MyObject are: ", obj.get_children())
print("myvar should be still there")
deletenode = ua.DeleteNodesItem()
deletenode.NodeId = obj.get_child(["2:MyVariable"]).nodeid
deletenode.DeleteTargetReferences = True
results = client.bclient.delete_nodes([deletenode])
results[0].check()
print("Children of MyObject are: ", obj.get_children())
print("myvar should disapear")
embed()
finally:
client.disconnect()
......@@ -429,7 +429,7 @@ class BinaryClient(object):
request = ua.DeleteNodesRequest()
request.Parameters.NodesToDelete = nodestodelete
data = self._uasocket.send_request(request)
response = ua.AddNodesResponse.from_binary(data)
response = ua.DeleteNodesResponse.from_binary(data)
response.ResponseHeader.ServiceResult.check()
return response.Results
......
......@@ -314,4 +314,3 @@ class Node(object):
def call_method(*args, **kwargs):
from opcua.common import methods
return methods.call_method(*args, **kwargs)
......@@ -33,6 +33,12 @@ class SubHandler(object):
"""
pass
def status_change_notification(self, status):
"""
called for every status change notfication from server
"""
pass
def event(self, handle, event):
"""
Deprecated use event_notification
......@@ -176,7 +182,7 @@ class Subscription(object):
def _call_status(self, status):
try:
self._handler.status_change(status.Status)
self._handler.status_change_notification(status.Status)
except Exception:
self.logger.exception("Exception calling status change handler")
......
......@@ -181,7 +181,7 @@ class NodeManagementService(object):
result = ua.AddNodesResult()
if item.RequestedNewNodeId in self._aspace:
self.logger.warning("AddNodeItem: node already exists")
self.logger.warning("AddNodesItem: node already exists")
result.StatusCode = ua.StatusCode(ua.StatusCodes.BadNodeIdExists)
return result
nodedata = NodeData(item.RequestedNewNodeId)
......@@ -233,6 +233,37 @@ class NodeManagementService(object):
return result
def delete_nodes(self, deletenodeitems, user=User.Admin):
results = []
for item in deletenodeitems:
results.append(self._delete_node(item, user))
return results
def _delete_node(self, item, user):
if not user == User.Admin:
return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
if item.NodeId not in self._aspace:
self.logger.warning("DeleteNodesItem: node does not exists")
return ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown)
if item.DeleteTargetReferences:
for elem in self._aspace.keys():
for rdesc in self._aspace[elem].references:
if rdesc.NodeId == item.NodeId:
self._aspace[elem].references.remove(rdesc)
for handle, callback in list(self._aspace[item.NodeId].attributes[ua.AttributeIds.Value].datachange_callbacks.items()):
try:
callback(handle, None, ua.StatusCode(ua.StatusCodes.BadNodeIdUnknown))
self._aspace.delete_datachange_callback(handle)
except Exception as ex:
self.logger.exception("Error calling datachange callback %s, %s, %s", k, v, ex)
del self._aspace[item.NodeId]
return ua.StatusCode()
def add_references(self, refs, user=User.Admin):
result = []
for ref in refs:
......@@ -260,6 +291,36 @@ class NodeManagementService(object):
self._aspace[addref.SourceNodeId].references.append(rdesc)
return ua.StatusCode()
def delete_references(self, refs, user=User.Admin):
result = []
for ref in refs:
result.append(self._delete_reference(ref, user))
return result
def _delete_reference(self, item, user):
if item.SourceNodeId not in self._aspace:
return ua.StatusCode(ua.StatusCodes.BadSourceNodeIdInvalid)
if item.TargetNodeId not in self._aspace:
return ua.StatusCode(ua.StatusCodes.BadTargetNodeIdInvalid)
if not user == User.Admin:
return ua.StatusCode(ua.StatusCodes.BadUserAccessDenied)
for rdesc in self._aspace[item.SourceNodeId].references:
if rdesc.NodeId is item.TargetNodeId:
if rdesc.RefrenceTypeId != item.RefrenceTypeId:
return ua.StatusCode(ua.StatusCode.BadReferenceTypeInvalid)
if rdesc.IsForward == item.IsForward or item.DeleteBidirectional:
self._aspace[item.SourceNodeId].references.remove(rdesc)
for rdesc in self._aspace[item.TargetNodeId].references:
if rdesc.NodeId is item.SourceNodeId:
if rdesc.RefrenceTypeId != item.RefrenceTypeId:
return ua.StatusCode(ua.StatusCode.BadReferenceTypeInvalid)
if rdesc.IsForward == item.IsForward or item.DeleteBidirectional:
self._aspace[item.SourceNodeId].references.remove(rdesc)
return ua.StatusCode()
def _add_node_attr(self, item, nodedata, name, vtype=None):
if item.SpecifiedAttributes & getattr(ua.NodeAttributesMask, name):
dv = ua.DataValue(ua.Variant(getattr(item, name), vtype))
......@@ -351,6 +412,14 @@ class AddressSpace(object):
with self._lock:
return self._nodes.__contains__(nodeid)
def __delitem__(self, nodeid):
with self._lock:
self._nodes.__delitem__(nodeid)
def keys(self):
with self._lock:
return self._nodes.keys()
def dump(self, path):
"""
dump address space as binary to file
......
......@@ -231,9 +231,15 @@ class InternalSession(object):
def add_nodes(self, params):
return self.iserver.node_mgt_service.add_nodes(params, self.user)
def delete_nodes(self, params):
return self.iserver.node_mgt_service.delete_nodes(params, self.user)
def add_references(self, params):
return self.iserver.node_mgt_service.add_references(params, self.user)
def delete_references(self, params):
return self.iserver.node_mgt_service.delete_references(params, self.user)
def add_method_callback(self, methodid, callback):
return self.aspace.add_method_callback(methodid, callback)
......
......@@ -137,7 +137,11 @@ class MonitoredItemService(object):
self._monitored_items.pop(mid)
return ua.StatusCode()
def datachange_callback(self, handle, value):
def datachange_callback(self, handle, value, error=None):
if error:
self.logger.info("subscription %s: datachange callback called with handle '%s' and erorr '%s'", self, handle, error)
self.trigger_statuschange(error)
else:
self.logger.info("subscription %s: datachange callback called with handle '%s' and value '%s'", self, handle, value.Value)
event = ua.MonitoredItemNotification()
with self._lock:
......@@ -289,7 +293,7 @@ class InternalSubscription(object):
notif = ua.StatusChangeNotification()
notif.Status = self._triggered_statuschanges.pop(0)
result.NotificationMessage.NotificationData.append(notif)
self.logger.debug("sending event notification %s", len(notif.Status))
self.logger.debug("sending event notification %s", notif.Status)
def publish(self, nb):
with self._lock:
......
......@@ -256,6 +256,18 @@ class UAProcessor(object):
self.logger.info("sending add node response")
self.send_response(requesthdr.RequestHandle, algohdr, seqhdr, response)
elif typeid == ua.NodeId(ua.ObjectIds.DeleteNodesRequest_Encoding_DefaultBinary):
self.logger.info("delete nodes request")
params = ua.DeleteNodesParameters.from_binary(body)
results = self.session.delete_nodes(params.NodesToDelete)
response = ua.DeleteNodesResponse()
response.Results = results
self.logger.info("sending delete node response")
self.send_response(requesthdr.RequestHandle, algohdr, seqhdr, response)
elif typeid == ua.NodeId(ua.ObjectIds.CreateSubscriptionRequest_Encoding_DefaultBinary):
self.logger.info("create subscription request")
params = ua.CreateSubscriptionParameters.from_binary(body)
......
......@@ -4930,8 +4930,14 @@ class DeleteNodesRequest(FrozenClass):
__repr__ = __str__
class DeleteNodesResult(FrozenClass):
class DeleteNodesResponse(FrozenClass):
'''
Delete one or more nodes from the server address space.
:ivar TypeId:
:vartype TypeId: NodeId
:ivar ResponseHeader:
:vartype ResponseHeader: ResponseHeader
:ivar Results:
:vartype Results: StatusCode
:ivar DiagnosticInfos:
......@@ -4942,12 +4948,16 @@ class DeleteNodesResult(FrozenClass):
self._binary_init(binary)
self._freeze = True
return
self.TypeId = FourByteNodeId(ObjectIds.DeleteNodesResponse_Encoding_DefaultBinary)
self.ResponseHeader = ResponseHeader()
self.Results = []
self.DiagnosticInfos = []
self._freeze = True
def to_binary(self):
packet = []
packet.append(self.TypeId.to_binary())
packet.append(self.ResponseHeader.to_binary())
packet.append(uatype_Int32.pack(len(self.Results)))
for fieldname in self.Results:
packet.append(fieldname.to_binary())
......@@ -4958,9 +4968,11 @@ class DeleteNodesResult(FrozenClass):
@staticmethod
def from_binary(data):
return DeleteNodesResult(data)
return DeleteNodesResponse(data)
def _binary_init(self, data):
self.TypeId = NodeId.from_binary(data)
self.ResponseHeader = ResponseHeader.from_binary(data)
length = uatype_Int32.unpack(data.read(4))[0]
array = []
if length != -1:
......@@ -4974,54 +4986,11 @@ class DeleteNodesResult(FrozenClass):
array.append(DiagnosticInfo.from_binary(data))
self.DiagnosticInfos = array
def __str__(self):
return 'DeleteNodesResult(' + 'Results:' + str(self.Results) + ', ' + \
'DiagnosticInfos:' + str(self.DiagnosticInfos) + ')'
__repr__ = __str__
class DeleteNodesResponse(FrozenClass):
'''
Delete one or more nodes from the server address space.
:ivar TypeId:
:vartype TypeId: NodeId
:ivar ResponseHeader:
:vartype ResponseHeader: ResponseHeader
:ivar Parameters:
:vartype Parameters: DeleteNodesResult
'''
def __init__(self, binary=None):
if binary is not None:
self._binary_init(binary)
self._freeze = True
return
self.TypeId = FourByteNodeId(ObjectIds.DeleteNodesResponse_Encoding_DefaultBinary)
self.ResponseHeader = ResponseHeader()
self.Parameters = DeleteNodesResult()
self._freeze = True
def to_binary(self):
packet = []
packet.append(self.TypeId.to_binary())
packet.append(self.ResponseHeader.to_binary())
packet.append(self.Parameters.to_binary())
return b''.join(packet)
@staticmethod
def from_binary(data):
return DeleteNodesResponse(data)
def _binary_init(self, data):
self.TypeId = NodeId.from_binary(data)
self.ResponseHeader = ResponseHeader.from_binary(data)
self.Parameters = DeleteNodesResult.from_binary(data)
def __str__(self):
return 'DeleteNodesResponse(' + 'TypeId:' + str(self.TypeId) + ', ' + \
'ResponseHeader:' + str(self.ResponseHeader) + ', ' + \
'Parameters:' + str(self.Parameters) + ')'
'Results:' + str(self.Results) + ', ' + \
'DiagnosticInfos:' + str(self.DiagnosticInfos) + ')'
__repr__ = __str__
......
......@@ -14,7 +14,7 @@ IgnoredEnums = []#["IdType", "NodeIdType"]
#we want to implement som struct by hand, to make better interface or simply because they are too complicated
IgnoredStructs = []#["NodeId", "ExpandedNodeId", "Variant", "QualifiedName", "DataValue", "LocalizedText"]#, "ExtensionObject"]
#by default we split requests and respons in header and parameters, but some are so simple we do not split them
NoSplitStruct = ["GetEndpointsResponse", "CloseSessionRequest", "AddNodesResponse", "BrowseResponse", "HistoryReadResponse", "HistoryUpdateResponse", "RegisterServerResponse", "CloseSecureChannelRequest", "CloseSecureChannelResponse", "CloseSessionRequest", "CloseSessionResponse", "UnregisterNodesResponse", "MonitoredItemModifyRequest", "MonitoredItemsCreateRequest", "ReadResponse", "WriteResponse", "TranslateBrowsePathsToNodeIdsResponse", "DeleteSubscriptionsResponse", "DeleteMonitoredItemsResponse", "CreateMonitoredItemsResponse", "ServiceFault", "AddReferencesRequest", "AddReferencesResponse", "ModifyMonitoredItemsResponse", "RepublishResponse", "CallResponse", "FindServersResponse", "RegisterServerRequest", "RegisterServer2Response"]
NoSplitStruct = ["GetEndpointsResponse", "CloseSessionRequest", "AddNodesResponse", "DeleteNodesResponse", "BrowseResponse", "HistoryReadResponse", "HistoryUpdateResponse", "RegisterServerResponse", "CloseSecureChannelRequest", "CloseSecureChannelResponse", "CloseSessionRequest", "CloseSessionResponse", "UnregisterNodesResponse", "MonitoredItemModifyRequest", "MonitoredItemsCreateRequest", "ReadResponse", "WriteResponse", "TranslateBrowsePathsToNodeIdsResponse", "DeleteSubscriptionsResponse", "DeleteMonitoredItemsResponse", "CreateMonitoredItemsResponse", "ServiceFault", "AddReferencesRequest", "AddReferencesResponse", "ModifyMonitoredItemsResponse", "RepublishResponse", "CallResponse", "FindServersResponse", "RegisterServerRequest", "RegisterServer2Response"]
#structs that end with Request or Response but are not
NotRequest = ["MonitoredItemCreateRequest", "MonitoredItemModifyRequest", "CallMethodRequest"]
OverrideTypes = {}#AttributeId": "AttributeID", "ResultMask": "BrowseResultMask", "NodeClassMask": "NodeClass", "AccessLevel": "VariableAccessLevel", "UserAccessLevel": "VariableAccessLevel", "NotificationData": "NotificationData"}
......
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