Commit 3ec413ed authored by Alexander Korolkov's avatar Alexander Korolkov

Clean up ExtensionObject handling

Remove downcast_extobject and ExtensionObject class.
All ExtensionObject members are now automatically converted
to objects of correct type. ExtensionObject members are now
initialised as None.
parent ada17a05
...@@ -265,7 +265,6 @@ class NodeManagementService(object): ...@@ -265,7 +265,6 @@ class NodeManagementService(object):
nodedata.attributes[getattr(ua.AttributeIds, name)] = AttributeValue(dv) nodedata.attributes[getattr(ua.AttributeIds, name)] = AttributeValue(dv)
def _add_nodeattributes(self, item, nodedata): def _add_nodeattributes(self, item, nodedata):
item = ua.downcast_extobject(item)
self._add_node_attr(item, nodedata, "AccessLevel", ua.VariantType.Byte) self._add_node_attr(item, nodedata, "AccessLevel", ua.VariantType.Byte)
self._add_node_attr(item, nodedata, "ArrayDimensions", ua.VariantType.Int32) self._add_node_attr(item, nodedata, "ArrayDimensions", ua.VariantType.Int32)
self._add_node_attr(item, nodedata, "BrowseName", ua.VariantType.QualifiedName) self._add_node_attr(item, nodedata, "BrowseName", ua.VariantType.QualifiedName)
......
...@@ -172,8 +172,8 @@ class InternalSession(object): ...@@ -172,8 +172,8 @@ class InternalSession(object):
for _ in params.ClientSoftwareCertificates: for _ in params.ClientSoftwareCertificates:
result.Results.append(ua.StatusCode()) result.Results.append(ua.StatusCode())
self.state = SessionState.Activated self.state = SessionState.Activated
id_token = ua.downcast_extobject(params.UserIdentityToken) id_token = params.UserIdentityToken
if id_token.TypeId == ua.FourByteNodeId(ua.ObjectIds.UserNameIdentityToken_Encoding_DefaultBinary): if isinstance(id_token, ua.UserNameIdentityToken):
if self.iserver.allow_remote_admin and id_token.UserName in ("admin", "Admin"): if self.iserver.allow_remote_admin and id_token.UserName in ("admin", "Admin"):
self.user = User.Admin self.user = User.Admin
return result return result
......
...@@ -61,7 +61,7 @@ class MonitoredItemService(object): ...@@ -61,7 +61,7 @@ class MonitoredItemService(object):
result = ua.MonitoredItemCreateResult() result = ua.MonitoredItemCreateResult()
if mdata.monitored_item_id == params.MonitoredItemId: if mdata.monitored_item_id == params.MonitoredItemId:
result.RevisedSamplingInterval = self.isub.data.RevisedPublishingInterval result.RevisedSamplingInterval = self.isub.data.RevisedPublishingInterval
result.RevisedQueueSize = ua.downcast_extobject(params.RequestedParameters.QueueSize) result.RevisedQueueSize = params.RequestedParameters.QueueSize
result.FilterResult = params.RequestedParameters.Filter result.FilterResult = params.RequestedParameters.Filter
mdata.parameters = result mdata.parameters = result
return result return result
...@@ -75,7 +75,7 @@ class MonitoredItemService(object): ...@@ -75,7 +75,7 @@ class MonitoredItemService(object):
result = ua.MonitoredItemCreateResult() result = ua.MonitoredItemCreateResult()
result.RevisedSamplingInterval = self.isub.data.RevisedPublishingInterval result.RevisedSamplingInterval = self.isub.data.RevisedPublishingInterval
result.RevisedQueueSize = params.RequestedParameters.QueueSize result.RevisedQueueSize = params.RequestedParameters.QueueSize
result.FilterResult = ua.downcast_extobject(params.RequestedParameters.Filter) result.FilterResult = params.RequestedParameters.Filter
self._monitored_item_counter += 1 self._monitored_item_counter += 1
result.MonitoredItemId = self._monitored_item_counter result.MonitoredItemId = self._monitored_item_counter
self.logger.debug("Creating MonitoredItem with id %s", result.MonitoredItemId) self.logger.debug("Creating MonitoredItem with id %s", result.MonitoredItemId)
......
...@@ -368,7 +368,7 @@ class Node(object): ...@@ -368,7 +368,7 @@ class Node(object):
valueid.IndexRange = '' valueid.IndexRange = ''
params = ua.HistoryReadParameters() params = ua.HistoryReadParameters()
params.HistoryReadDetails = ua.ExtensionObject.from_object(details) params.HistoryReadDetails = details
params.TimestampsToReturn = ua.TimestampsToReturn.Both params.TimestampsToReturn = ua.TimestampsToReturn.Both
params.ReleaseContinuationPoints = False params.ReleaseContinuationPoints = False
params.NodesToRead.append(valueid) params.NodesToRead.append(valueid)
...@@ -499,12 +499,12 @@ def _call_method(server, parentnodeid, methodid, arguments): ...@@ -499,12 +499,12 @@ def _call_method(server, parentnodeid, methodid, arguments):
def _vtype_to_argument(vtype): def _vtype_to_argument(vtype):
if isinstance(vtype, ua.Argument): if isinstance(vtype, ua.Argument):
return ua.ExtensionObject.from_object(vtype) return vtype
arg = ua.Argument() arg = ua.Argument()
v = ua.Variant(None, vtype) v = ua.Variant(None, vtype)
arg.DataType = _guess_uatype(v) arg.DataType = _guess_uatype(v)
return ua.ExtensionObject.from_object(arg) return arg
def _guess_uatype(variant): def _guess_uatype(variant):
...@@ -517,8 +517,7 @@ def _guess_uatype(variant): ...@@ -517,8 +517,7 @@ def _guess_uatype(variant):
extobj = variant.Value[0] extobj = variant.Value[0]
else: else:
extobj = variant.Value extobj = variant.Value
objectidname = ua.ObjectIdsInv[extobj.TypeId.Identifier] classname = extobj.__class__.__name__
classname = objectidname.split("_")[0]
return ua.NodeId(getattr(ua.ObjectIds, classname)) return ua.NodeId(getattr(ua.ObjectIds, classname))
else: else:
return ua.NodeId(getattr(ua.ObjectIds, variant.VariantType.name)) return ua.NodeId(getattr(ua.ObjectIds, variant.VariantType.name))
......
...@@ -66,15 +66,12 @@ class Subscription(object): ...@@ -66,15 +66,12 @@ class Subscription(object):
time.sleep(0.01) time.sleep(0.01)
for notif in publishresult.NotificationMessage.NotificationData: for notif in publishresult.NotificationMessage.NotificationData:
if notif.TypeId == ua.FourByteNodeId(ua.ObjectIds.DataChangeNotification_Encoding_DefaultBinary): if isinstance(notif, ua.DataChangeNotification):
datachange = ua.DataChangeNotification.from_binary(io.BytesIO(notif.to_binary())) self._call_datachange(notif)
self._call_datachange(datachange) elif isinstance(notif, ua.EventNotificationList):
elif notif.TypeId == ua.FourByteNodeId(ua.ObjectIds.EventNotificationList_Encoding_DefaultBinary): self._call_event(notif)
eventlist = ua.EventNotificationList.from_binary(io.BytesIO(notif.to_binary())) elif isinstance(notif, ua.StatusChangeNotification):
self._call_event(eventlist) self._call_status(notif)
elif notif.TypeId == ua.FourByteNodeId(ua.ObjectIds.StatusChangeNotification_Encoding_DefaultBinary):
statuschange = ua.StatusChangeNotification.from_binary(io.BytesIO(notif.to_binary()))
self._call_status(statuschange)
else: else:
self.logger.warning("Notification type not supported yet for notification %s", notif) self.logger.warning("Notification type not supported yet for notification %s", notif)
...@@ -223,7 +220,7 @@ class Subscription(object): ...@@ -223,7 +220,7 @@ class Subscription(object):
data.node = Node(self.server, mi.ItemToMonitor.NodeId) data.node = Node(self.server, mi.ItemToMonitor.NodeId)
data.attribute = mi.ItemToMonitor.AttributeId data.attribute = mi.ItemToMonitor.AttributeId
data.server_handle = result.MonitoredItemId data.server_handle = result.MonitoredItemId
data.mfilter = ua.downcast_extobject(result.FilterResult) data.mfilter = result.FilterResult
self._monitoreditems_map[mi.RequestedParameters.ClientHandle] = data self._monitoreditems_map[mi.RequestedParameters.ClientHandle] = data
mids.append(result.MonitoredItemId) mids.append(result.MonitoredItemId)
......
...@@ -451,11 +451,10 @@ def uadiscover(): ...@@ -451,11 +451,10 @@ def uadiscover():
sys.exit(0) sys.exit(0)
def print_history(res): def print_history(o):
if res.TypeId.Identifier == ua.ObjectIds.HistoryData_Encoding_DefaultBinary: if isinstance(o, ua.HistoryData):
buf = ua.utils.Buffer(res.Body)
print("{:30} {:10} {}".format('Source timestamp', 'Status', 'Value')) print("{:30} {:10} {}".format('Source timestamp', 'Status', 'Value'))
for d in ua.HistoryData.from_binary(buf).DataValues: for d in o.DataValues:
print("{:30} {:10} {}".format(str(d.SourceTimestamp), d.StatusCode.name, d.Value)) print("{:30} {:10} {}".format(str(d.SourceTimestamp), d.StatusCode.name, d.Value))
......
...@@ -4,13 +4,3 @@ from opcua.status_code import StatusCodes ...@@ -4,13 +4,3 @@ from opcua.status_code import StatusCodes
from opcua.uaprotocol_auto import * from opcua.uaprotocol_auto import *
from opcua.uaprotocol_hand import * from opcua.uaprotocol_hand import *
# FIXME: this is really crappy, should thing about a better implementation
# maybe never inherit extensionobject and parse only body....
def downcast_extobject(item):
if item.TypeId.Identifier == 0:
return item
objectidname = ObjectIdsInv[item.TypeId.Identifier]
classname = objectidname.split("_")[0]
cmd = "{}.from_binary(utils.Buffer(item.to_binary()))".format(classname)
return eval(cmd)
This diff is collapsed.
...@@ -308,5 +308,4 @@ class Argument(auto.Argument): ...@@ -308,5 +308,4 @@ class Argument(auto.Argument):
self.ValueRank = -2 self.ValueRank = -2
ObjectIdsInv = {v: k for k, v in ObjectIds.__dict__.items()}
AttributeIdsInv = {v: k for k, v in AttributeIds.__dict__.items()} AttributeIdsInv = {v: k for k, v in AttributeIds.__dict__.items()}
...@@ -116,6 +116,12 @@ def pack_uatype(uatype, value): ...@@ -116,6 +116,12 @@ def pack_uatype(uatype, value):
elif uatype in UaTypes: elif uatype in UaTypes:
fmt = '<' + uatype_to_fmt(uatype) fmt = '<' + uatype_to_fmt(uatype)
return struct.pack(fmt, value) return struct.pack(fmt, value)
elif uatype == "ExtensionObject":
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
# Using local import to avoid import loop
from opcua.uaprotocol_auto import extensionobject_to_binary
return extensionobject_to_binary(value)
else: else:
return value.to_binary() return value.to_binary()
...@@ -132,6 +138,12 @@ def unpack_uatype(uatype, data): ...@@ -132,6 +138,12 @@ def unpack_uatype(uatype, data):
fmt = '<' + uatype_to_fmt(uatype) fmt = '<' + uatype_to_fmt(uatype)
size = struct.calcsize(fmt) size = struct.calcsize(fmt)
return struct.unpack(fmt, data.read(size))[0] return struct.unpack(fmt, data.read(size))[0]
elif uatype == "ExtensionObject":
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
# Using local import to avoid import loop
from opcua.uaprotocol_auto import extensionobject_from_binary
return extensionobject_from_binary(data)
else: else:
code = "{}.from_binary(data)".format(uatype) code = "{}.from_binary(data)".format(uatype)
tmp = eval(code) tmp = eval(code)
...@@ -585,59 +597,6 @@ class LocalizedText(FrozenClass): ...@@ -585,59 +597,6 @@ class LocalizedText(FrozenClass):
return False return False
class ExtensionObject(FrozenClass):
'''
Any UA object packed as an ExtensionObject
:ivar TypeId:
:vartype TypeId: NodeId
:ivar Body:
:vartype Body: bytes
'''
def __init__(self):
self.TypeId = NodeId()
self.Encoding = 0
self.Body = b''
self._freeze()
def to_binary(self):
packet = []
if self.Body:
self.Encoding |= (1 << 0)
packet.append(self.TypeId.to_binary())
packet.append(pack_uatype('UInt8', self.Encoding))
if self.Body:
packet.append(pack_uatype('ByteString', self.Body))
return b''.join(packet)
@staticmethod
def from_binary(data):
obj = ExtensionObject()
obj.TypeId = NodeId.from_binary(data)
obj.Encoding = unpack_uatype('UInt8', data)
if obj.Encoding & (1 << 0):
obj.Body = unpack_uatype('ByteString', data)
return obj
@staticmethod
def from_object(obj):
ext = ExtensionObject()
oid = getattr(ObjectIds, "{}_Encoding_DefaultBinary".format(obj.__class__.__name__))
ext.TypeId = FourByteNodeId(oid)
ext.Body = obj.to_binary()
return ext
def __str__(self):
return 'ExtensionObject(' + 'TypeId:' + str(self.TypeId) + ', ' + \
'Encoding:' + str(self.Encoding) + ')'
__repr__ = __str__
class VariantType(Enum): class VariantType(Enum):
...@@ -753,7 +712,10 @@ class Variant(FrozenClass): ...@@ -753,7 +712,10 @@ class Variant(FrozenClass):
return VariantType.DateTime return VariantType.DateTime
else: else:
if isinstance(val, object): if isinstance(val, object):
try:
return getattr(VariantType, val.__class__.__name__) return getattr(VariantType, val.__class__.__name__)
except AttributeError:
return VariantType.ExtensionObject
else: else:
raise Exception("Could not guess UA type of {} with type {}, specify UA type".format(val, type(val))) raise Exception("Could not guess UA type of {} with type {}, specify UA type".format(val, type(val)))
......
...@@ -23,8 +23,6 @@ OverrideNames = {}#{"RequestHeader": "Header", "ResponseHeader": "Header", "Stat ...@@ -23,8 +23,6 @@ OverrideNames = {}#{"RequestHeader": "Header", "ResponseHeader": "Header", "Stat
#some object are defined in extensionobjects in spec but seems not to be in reality #some object are defined in extensionobjects in spec but seems not to be in reality
#in addition to this list all request and response and descriptions will not inherit #in addition to this list all request and response and descriptions will not inherit
#NoInherit = ["RequestHeader", "ResponseHeader", "ChannelSecurityToken", "UserTokenPolicy", "SignatureData", "BrowseResult", "ReadValueId", "WriteValue", "BrowsePath", "BrowsePathTarget", "RelativePath", "RelativePathElement", "BrowsePathResult"]#, "ApplicationDescription", "EndpointDescription" #NoInherit = ["RequestHeader", "ResponseHeader", "ChannelSecurityToken", "UserTokenPolicy", "SignatureData", "BrowseResult", "ReadValueId", "WriteValue", "BrowsePath", "BrowsePathTarget", "RelativePath", "RelativePathElement", "BrowsePathResult"]#, "ApplicationDescription", "EndpointDescription"
# many objects are defined as inheriting while inheriting ExtensionObjects while they do not so harcode those who really do
InheritExtensionObjects = ["UserIdentityToken", "NodeAttributes", "NotificationData", "MonitoringFilter"]#"SignatureData"]
class Bit(object): class Bit(object):
...@@ -459,10 +457,7 @@ def add_basetype_members(model): ...@@ -459,10 +457,7 @@ def add_basetype_members(model):
emptystruct = False emptystruct = False
if len(struct.fields) == 0: if len(struct.fields) == 0:
emptystruct = True emptystruct = True
#if not emptystruct and struct.basetype in ("ExtensionObject") and not struct.name in InheritExtensionObjects: if struct.basetype in ("ExtensionObject"):
if struct.basetype in ("ExtensionObject") and not struct.name in InheritExtensionObjects:
#if struct.name in NoInherit or struct.name.endswith("Request") or struct.name.endswith("Response") or struct.name.endswith("Description"):
struct.parents.remove(struct.basetype)
struct.basetype = None struct.basetype = None
continue continue
base = model.get_struct(struct.basetype) base = model.get_struct(struct.basetype)
......
...@@ -31,6 +31,22 @@ class CodeGenerator(object): ...@@ -31,6 +31,22 @@ class CodeGenerator(object):
continue continue
self.generate_struct_code(struct) self.generate_struct_code(struct)
self.iidx = 0
self.write("")
self.write("ExtensionClasses = {")
for struct in self.model.structs:
if struct.name in IgnoredStructs:
continue
if struct.name.endswith("Node") or struct.name.endswith("NodeId"):
continue
if "ExtensionObject" in struct.parents:
self.write(" ObjectIds.{0}_Encoding_DefaultBinary : {0},".format(struct.name))
self.write("}")
self.write("")
with open('uaprotocol_auto_add.py') as f:
for line in f:
self.write(line.rstrip())
def write(self, *args): def write(self, *args):
args = list(args) args = list(args)
args.insert(0, self.indent * self.iidx) args.insert(0, self.indent * self.iidx)
...@@ -44,6 +60,7 @@ class CodeGenerator(object): ...@@ -44,6 +60,7 @@ class CodeGenerator(object):
self.write("") self.write("")
self.write("from datetime import datetime") self.write("from datetime import datetime")
self.write("") self.write("")
self.write("from opcua.utils import Buffer")
self.write("from opcua.uatypes import *") self.write("from opcua.uatypes import *")
self.write("from opcua.object_ids import ObjectIds") self.write("from opcua.object_ids import ObjectIds")
self.write("") self.write("")
...@@ -147,6 +164,8 @@ class CodeGenerator(object): ...@@ -147,6 +164,8 @@ class CodeGenerator(object):
elif field.uatype in self.model.enum_list: elif field.uatype in self.model.enum_list:
uatype = self.model.get_enum(field.uatype).uatype uatype = self.model.get_enum(field.uatype).uatype
self.write("{}.append(pack_uatype('{}', {}))".format(listname, uatype, fname)) self.write("{}.append(pack_uatype('{}', {}))".format(listname, uatype, fname))
elif field.uatype in ("ExtensionObject"):
self.write("{}.append(extensionobject_to_binary({}))".format(listname, fname))
else: else:
self.write("{}.append({}.to_binary())".format(listname, fname)) self.write("{}.append({}.to_binary())".format(listname, fname))
if field.length: if field.length:
...@@ -190,15 +209,19 @@ class CodeGenerator(object): ...@@ -190,15 +209,19 @@ class CodeGenerator(object):
uatype = self.model.get_enum(field.uatype).uatype uatype = self.model.get_enum(field.uatype).uatype
self.write("obj.{} = unpack_uatype('{}', data)".format(field.name, uatype)) self.write("obj.{} = unpack_uatype('{}', data)".format(field.name, uatype))
else: else:
if field.uatype in ("ExtensionObject"):
frombinary = "extensionobject_from_binary(data)"
else:
frombinary = "{}.from_binary(data)".format(field.uatype)
if field.length: if field.length:
self.write("length = struct.unpack('<i', data.read(4))[0]") self.write("length = struct.unpack('<i', data.read(4))[0]")
self.write("if length != -1:") self.write("if length != -1:")
self.iidx += 1 self.iidx += 1
self.write("for _ in range(0, length):") self.write("for _ in range(0, length):")
self.iidx += 1 self.iidx += 1
self.write("obj.{}.append({}.from_binary(data))".format(field.name, field.uatype)) self.write("obj.{}.append({})".format(field.name, frombinary))
else: else:
self.write("obj.{} = {}.from_binary(data)".format(field.name, field.uatype)) self.write("obj.{} = {}".format(field.name, frombinary))
self.iidx = 2 self.iidx = 2
self.write("return obj") self.write("return obj")
...@@ -230,6 +253,8 @@ class CodeGenerator(object): ...@@ -230,6 +253,8 @@ class CodeGenerator(object):
return "datetime.now()" return "datetime.now()"
elif field.uatype in ("Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", "Double", "Float", "Byte"): elif field.uatype in ("Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", "Double", "Float", "Byte"):
return 0 return 0
elif field.uatype in ("ExtensionObject"):
return "None"
else: else:
return field.uatype + "()" return field.uatype + "()"
......
def extensionobject_from_binary(data):
"""
Convert binary-coded ExtensionObject to a Python object.
Returns an object, or None if TypeId is zero
"""
TypeId = NodeId.from_binary(data)
Encoding = unpack_uatype('UInt8', data)
if Encoding & (1 << 0):
Body = unpack_uatype('ByteString', data)
if TypeId.Identifier == 0:
return None
klass = ExtensionClasses[TypeId.Identifier]
return klass.from_binary(Buffer(Body))
def extensionobject_to_binary(obj):
"""
Convert Python object to binary-coded ExtensionObject.
If obj is None, convert to empty ExtensionObject (TypeId = 0, no Body).
Returns a binary string
"""
TypeId = NodeId()
Encoding = 0
Body = None
if obj is not None:
TypeId = FourByteNodeId(getattr(ObjectIds, "{}_Encoding_DefaultBinary".format(obj.__class__.__name__)))
Encoding |= (1 << 0)
Body = obj.to_binary()
packet = []
packet.append(TypeId.to_binary())
packet.append(pack_uatype('UInt8', Encoding))
if Body:
packet.append(pack_uatype('ByteString', Body))
return b''.join(packet)
...@@ -19,6 +19,8 @@ from opcua import uamethod ...@@ -19,6 +19,8 @@ from opcua import uamethod
from opcua import Event from opcua import Event
from opcua import ObjectIds from opcua import ObjectIds
from opcua import AttributeIds from opcua import AttributeIds
from opcua.uaprotocol_auto import extensionobject_from_binary
from opcua.uaprotocol_auto import extensionobject_to_binary
port_num1 = 48510 port_num1 = 48510
port_num2 = 48530 port_num2 = 48530
...@@ -132,10 +134,17 @@ class Unit(unittest.TestCase): ...@@ -132,10 +134,17 @@ class Unit(unittest.TestCase):
self.assertEqual(nid, nid2) self.assertEqual(nid, nid2)
def test_extension_object(self): def test_extension_object(self):
obj = ua.ExtensionObject() obj = ua.UserNameIdentityToken()
obj2 = ua.ExtensionObject.from_binary(ua.utils.Buffer(obj.to_binary())) obj.UserName = "admin"
self.assertEqual(obj2.TypeId, obj2.TypeId) obj.Password = b"pass"
self.assertEqual(obj2.Body, obj2.Body) obj2 = ua.extensionobject_from_binary(ua.utils.Buffer(extensionobject_to_binary(obj)))
self.assertEqual(type(obj), type(obj2))
self.assertEqual(obj.UserName, obj2.UserName)
self.assertEqual(obj.Password, obj2.Password)
v1 = ua.Variant(obj)
v2 = ua.Variant.from_binary(ua.utils.Buffer(v1.to_binary()))
self.assertEqual(type(v1), type(v2))
self.assertEqual(v1.VariantType, v2.VariantType)
def test_datetime(self): def test_datetime(self):
now = datetime.now() now = datetime.now()
......
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