diff --git a/devices/HW_device_implementation.py b/devices/HW_device_template.py similarity index 91% rename from devices/HW_device_implementation.py rename to devices/HW_device_template.py index 0c76b0c519d96e0491da7d1852f48afc0bb74752..3d8b19c20b37cdf9b5d1d5780d28edad0c333f0d 100644 --- a/devices/HW_device_implementation.py +++ b/devices/HW_device_template.py @@ -7,7 +7,7 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -""" PCC Device Server for LOFAR2.0 +""" Hardware Device Server for LOFAR2.0 """ @@ -80,7 +80,7 @@ class HW_dev(hardware_device): # Run server # ---------- def main(args=None, **kwargs): - """Main function of the PCC module.""" + """Main function of the hardware device module.""" return run((HW_dev,), args=args, **kwargs) diff --git a/devices/LICENSE.txt b/devices/LICENSE.txt index 583bd5a9884fcc0c6e3e6908de9a930203b19d24..8a0eaeb196094a651006f51fd99c0c05cb16ccd6 100644 --- a/devices/LICENSE.txt +++ b/devices/LICENSE.txt @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] +Copyright 2021 ASTRON Netherlands Institute for Radio Astronomy Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/devices/PCC.py b/devices/PCC.py index a0cb16d4ac6b2bc50e06d5153f270366707b6104..d9f1c04305d26a0a4ea60942097d3ba639412c44 100644 --- a/devices/PCC.py +++ b/devices/PCC.py @@ -12,7 +12,8 @@ """ # PyTango imports -from tango.server import run +from tango import DebugIt +from tango.server import run, command from tango.server import device_property # Additional import @@ -55,6 +56,10 @@ class PCC(hardware_device): dtype='DevDouble', mandatory=True ) + OPC_namespace = device_property( + dtype='DevString', + mandatory=False + ) # ---------- # Attributes @@ -85,15 +90,6 @@ class PCC(hardware_device): RCU_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_monitor_rate_RW"], datatype=numpy.float64, access=AttrWriteType.READ_WRITE) - def setup_value_dict(self): - """ set the initial value for all the attribute wrapper objects""" - - self.value_dict = {str(i): i.initial_value() for i in self.attr_list()} - - def always_executed_hook(self): - """Method always executed before any TANGO command is executed.""" - pass - def delete_device(self): """Hook to delete resources allocated in init_device. @@ -116,15 +112,20 @@ class PCC(hardware_device): self.opcua_connection.stop() def initialise(self): - """ user code here. is called when the state is set to STANDBY """ - - """Initialises the attributes, values and properties of the PCC.""" - - # will contain all the values for this device - self.setup_value_dict() + """ user code here. is called when the state is set to INIT """ + + # Init the dict that contains function to OPC-UA function mappings. + self.function_mapping = {} + self.function_mapping["RCU_on"] = {} + self.function_mapping["RCU_off"] = {} + self.function_mapping["ADC_on"] = {} + self.function_mapping["RCU_update"] = {} + self.function_mapping["CLK_on"] = {} + self.function_mapping["CLK_off"] = {} + self.function_mapping["CLK_PLL_setup"] = {} #set up the OPC ua client - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_Time_Out, self.Standby, self.Fault, self) + self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self) # map the attributes to the OPC ua comm client for i in self.attr_list(): @@ -135,6 +136,83 @@ class PCC(hardware_device): # -------- # Commands # -------- + @command() + @DebugIt() + @only_when_on + @fault_on_error + def RCU_off(self): + """ + + :return:None + """ + self.function_mapping["RCU_off"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def RCU_on(self): + """ + + :return:None + """ + self.function_mapping["RCU_on"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def ADC_on(self): + """ + + :return:None + """ + self.function_mapping["ADC_on"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def RCU_update(self): + """ + + :return:None + """ + self.function_mapping["RCU_update"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def CLK_off(self): + """ + + :return:None + """ + self.function_mapping["CLK_off"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def CLK_on(self): + """ + + :return:None + """ + self.function_mapping["CLK_on"]() + + @command() + @DebugIt() + @only_when_on + @fault_on_error + def CLK_PLL_setup(self): + """ + + :return:None + """ + self.function_mapping["CLK_PLL_setup"]() + # ---------- # Run server diff --git a/devices/README.md b/devices/README.md index 598164453ad74836dbab9d961c9b700c742663ba..37f5db0d5e35aa74f6e26d63524713f40d0e42b6 100644 --- a/devices/README.md +++ b/devices/README.md @@ -10,8 +10,7 @@ declare what client the attribute has to use in the initialisation and provide s To see how to add support for new clients, see `clients/README.md` In addition it also provides an abstraction to the tango device, specifically for hardware devices. Examples of hardware devices -can be found in SDP.py, PCC.py and test_device.py. as well as a completely empty template in HW_device_implementation.py - +can be found in TODO and an empty template can be found in `HW_device_tempalte.py` Requires numpy ```pip install numpy``` @@ -24,6 +23,4 @@ Requires pytango ### usage You can start the device by calling it in any console with: -sdp.py instance_name -PCC.py instance_name -test_device.py instance_name \ No newline at end of file +<Device_name>.py instance_name diff --git a/devices/SDP.py b/devices/SDP.py index ef991709f2c53fddf2aecc9ce6f6f13be2eb574c..2f54bed7bc727323eed635b81880ad4b783f19c0 100644 --- a/devices/SDP.py +++ b/devices/SDP.py @@ -73,9 +73,6 @@ class SDP(hardware_device): tr_tod_R = attribute_wrapper(comms_annotation=["1:tr_tod_R"], datatype=numpy.uint64) tr_uptime_R = attribute_wrapper(comms_annotation=["1:tr_uptime_R"], datatype=numpy.uint64) - def setup_value_dict(self): - self.value_dict = {str(i): i.initial_value() for i in self.attr_list()} - def always_executed_hook(self): """Method always executed before any TANGO command is executed.""" pass @@ -106,7 +103,7 @@ class SDP(hardware_device): """Initialises the attributes and properties of the PCC.""" # set up the OPC ua client - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), self.OPC_Time_Out, self.Standby, self.Fault, self) + self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self) # will contain all the values for this object self.setup_value_dict() diff --git a/devices/SNMP.py b/devices/SNMP.py new file mode 100644 index 0000000000000000000000000000000000000000..7f1a7a4e91b841dc818b763dc5c3a04ae5d5ecd2 --- /dev/null +++ b/devices/SNMP.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the PCC project +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" PCC Device Server for LOFAR2.0 + +""" + +# PyTango imports +from tango.server import run +from tango.server import device_property +# Additional import + +from clients.SNMP_client import SNMP_client +from src.attribute_wrapper import * +from src.hardware_device import * + + +__all__ = ["PCC", "main"] + +class PCC(hardware_device): + """ + + **Properties:** + + - Device Property + SNMP_community + - Type:'DevString' + SNMP_host + - Type:'DevULong' + SNMP_timeout + - Type:'DevDouble' + """ + + # ----------------- + # Device Properties + # ----------------- + + SNMP_community = device_property( + dtype='DevString', + mandatory=True + ) + + SNMP_host = device_property( + dtype='DevString', + mandatory=True + ) + + SNMP_timeout = device_property( + dtype='DevDouble', + mandatory=True + ) + + # ---------- + # Attributes + # ---------- + + + attr0 = attribute_wrapper(comms_annotation={"oids": [""]}, datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) + attr1 = attribute_wrapper(comms_annotation={"oids": ["1.3.6.1.2.1.1.6.0"]}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + + attr2 = attribute_wrapper(comms_annotation={"host": "127.0.0.1", "oids": ["1.3.6.1.2.1.1.5.0"]}, datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + + attr3 = attribute_wrapper(comms_annotation={"host": "127.0.0.1", "oids": ["1.3.6.1.2.1.1.5.1", "1.3.6.1.2.1.1.5.2", "1.3.6.1.2.1.1.5.3"]}, dims=(3,), datatype=numpy.bool_) + attr4 = attribute_wrapper(comms_annotation={"host": "127.0.0.1", "oids": ["1.3.6.1.2.1.1.5.0"]}, dims=(3,), datatype=numpy.bool_) + # ["1.3.6.1.2.1.1.5.0"] gets transformed in to an array the size of dims with ".1", ".2" .. added + # ["1.3.6.1.2.1.1.5.0.1", "1.3.6.1.2.1.1.5.0.2", "1.3.6.1.2.1.1.5.0.3"] + + def always_executed_hook(self): + """Method always executed before any TANGO command is executed.""" + pass + + def delete_device(self): + """Hook to delete resources allocated in init_device. + + This method allows for any memory or other resources allocated in the + init_device method to be released. This method is called by the device + destructor and by the device Init command (a Tango built-in). + """ + self.debug_stream("Shutting down...") + + self.Off() + self.debug_stream("Shut down. Good bye.") + + # -------- + # overloaded functions + # -------- + def initialise(self): + """ user code here. is called when the state is set to STANDBY """ + + #set up the SNMP ua client + self.snmp_manager = SNMP_client(self.SNMP_community, self.SNMP_host, self.SNMP_timeout, self.Standby, self.Fault, self) + + # map the attributes to the OPC ua comm client + for i in self.attr_list(): + i.set_comm_client(self.OPCua_client) + + self.OPCua_client.start() + + # -------- + # Commands + # -------- + +# ---------- +# Run server +# ---------- +def main(args=None, **kwargs): + """Main function of the PCC module.""" + return run((PCC,), args=args, **kwargs) + + +if __name__ == '__main__': + main() + diff --git a/devices/clients/SNMP_client.py b/devices/clients/SNMP_client.py new file mode 100644 index 0000000000000000000000000000000000000000..2e742603e4542c524ad57b41e243886504676ba5 --- /dev/null +++ b/devices/clients/SNMP_client.py @@ -0,0 +1,166 @@ +from src.comms_client import * +import snmp + + +__all__ = ["SNMP_client"] + +numpy_to_snmp_dict = { + "<class 'numpy.bool_'>": opcua.ua.VariantType.Boolean, + "<class 'numpy.int8'>": opcua.ua.VariantType.SByte, + "<class 'numpy.uint8'>": opcua.ua.VariantType.Byte, + "<class 'numpy.int16'>": opcua.ua.VariantType.Int16, + "<class 'numpy.uint16'>": opcua.ua.VariantType.UInt16, + "<class 'numpy.int32'>": opcua.ua.VariantType.Int32, + "<class 'numpy.uint32'>": opcua.ua.VariantType.UInt32, + "<class 'numpy.int64'>": opcua.ua.VariantType.Int64, + "<class 'numpy.uint64'>": opcua.ua.VariantType.UInt64, + "<class 'numpy.datetime_data'>": opcua.ua.VariantType.DateTime, # is this the right type, does it even matter? + "<class 'numpy.float32'>": opcua.ua.VariantType.Float, + "<class 'numpy.float64'>": opcua.ua.VariantType.Double, + "<class 'numpy.double'>": opcua.ua.VariantType.Double, + "<class 'numpy.str_'>": opcua.ua.VariantType.String, + "<class 'numpy.str'>": opcua.ua.VariantType.String, + "str": opcua.ua.VariantType.String +} + +class SNMP_client(CommClient): + """ + messages to keep a check on the connection. On connection failure, reconnects once. + """ + + def start(self): + super().start() + + def __init__(self, community, host, timeout, on_func, fault_func, streams, try_interval=2): + """ + Create the SNMP and connect() to it + """ + super().__init__(on_func, fault_func, streams, try_interval) + + self.community = community + self.host = host + self.manager = snmp.Manager(community, host, timeout) + + # Explicitly connect + if not self.connect(): + # hardware or infra is down -- needs fixing first + fault_func() + return + + def connect(self): + """ + Try to connect to the client + """ + + self.streams.debug_stream("Connecting to server %s %s", self.community, self.host) + self.connected = True + return True + + def disconnect(self): + """ + disconnect from the client + """ + self.connected = False # always force a reconnect, regardless of a successful disconnect + + + def ping(self): + """ + ping the client to make sure the connection with the client is still functional. + """ + pass + + def _setup_annotation(self, annotation): + """ + This class's Implementation of the get_mapping function. returns the read and write functions + """ + + if isinstance(annotation, dict): + # check if required path inarg is present + if annotation.get('oids') is None: + AssertionError("SNMP get attributes require an oid") + oids = annotation.get("oids") # required + + if annotation.get('host') is None: + AssertionError("SNMP get attributes require an host") + host = annotation.get("host") # required + + else: + TypeError("SNMP attributes require a dict with oid and adress") + return + + return host, oids + + def setup_value_conversion(self, attribute): + """ + gives the client access to the attribute_wrapper object in order to access all data it could potentially need. + the OPC ua read/write functions require the dimensionality and the type to be known + """ + + dim_x = attribute.dim_x + dim_y = attribute.dim_y + snmp_type = numpy_to_snmp_dict[str(attribute.numpy_type)] # convert the numpy type to a corresponding UA type + + return dim_x, dim_y, snmp_type + + def setup_attribute(self, annotation, attribute): + """ + MANDATORY function: is used by the attribute wrapper to get read/write functions. must return the read and write functions + """ + + # process the annotation + host, oids = self._setup_annotation(annotation) + + # get all the necessary data to set up the read/write functions from the attribute_wrapper + dim_x, dim_y, snmp_type = self.setup_value_conversion(attribute) + + def _read_function(self): + vars = self.manager.get(host, *oids) + #TODO convert type + #todo + + def _write_function(self, value): + self.manager.set(host, oids, value) + + # return the read/write functions + return _read_function, _write_function + + + +class snmp_get: + """ + This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code + """ + + def __init__(self, host, oid, dim_x, dim_y, snmp_type): + self.host = host + self.oid = oid + self.dim_y = dim_y + self.dim_x = dim_x + self.snmp_type = snmp_type + + def read_function(self): + """ + Read_R function + """ + value = numpy.array(self.node.get_value()) + + if self.dim_y != 0: + value = numpy.array(numpy.split(value, indices_or_sections=self.dim_y)) + else: + value = numpy.array(value) + return value + + def write_function(self, value): + """ + write_RW function + """ + # set_data_value(opcua.ua.uatypes.Variant(value = value.tolist(), varianttype=opcua.ua.VariantType.Int32)) + + if self.dim_y != 0: + v = numpy.concatenate(value) + self.node.set_data_value(opcua.ua.uatypes.Variant(value=v.tolist(), varianttype=self.ua_type)) + + elif self.dim_x != 1: + self.node.set_data_value(opcua.ua.uatypes.Variant(value=value.tolist(), varianttype=self.ua_type)) + else: + self.node.set_data_value(opcua.ua.uatypes.Variant(value=value, varianttype=self.ua_type)) diff --git a/devices/clients/opcua_connection.py b/devices/clients/opcua_connection.py index b8707022f8355e84a286368532b2cffeb162e0ca..56539a2a296501d6b7a7497637570b3597e19246 100644 --- a/devices/clients/opcua_connection.py +++ b/devices/clients/opcua_connection.py @@ -3,40 +3,40 @@ from src.comms_client import * __all__ = ["OPCUAConnection"] -OPCua_to_numpy_dict = { - "VariantType.Boolean": numpy.bool_, - "VariantType.SByte": numpy.int8, - "VariantType.Byte": numpy.uint8, - "VariantType.Int16": numpy.int16, - "VariantType.UInt16": numpy.uint16, - "VariantType.Int32": numpy.int32, - "VariantType.UInt32": numpy.uint32, - "VariantType.Int64": numpy.int64, - "VariantType.UInt64": numpy.uint64, - "VariantType.DateTime": numpy.datetime_data, # is this the right type, does it even matter? - "VariantType.Float": numpy.float32, - "VariantType.Double": numpy.double, - "VariantType.String": numpy.str, - "VariantType.ByteString": numpy.uint8 # sequence of bytes, not a string -} +# OPCua_to_numpy_dict = { +# "VariantType.Boolean": numpy.bool_, +# "VariantType.SByte": numpy.int8, +# "VariantType.Byte": numpy.uint8, +# "VariantType.Int16": numpy.int16, +# "VariantType.UInt16": numpy.uint16, +# "VariantType.Int32": numpy.int32, +# "VariantType.UInt32": numpy.uint32, +# "VariantType.Int64": numpy.int64, +# "VariantType.UInt64": numpy.uint64, +# "VariantType.DateTime": numpy.datetime_data, # is this the right type, does it even matter? +# "VariantType.Float": numpy.float32, +# "VariantType.Double": numpy.double, +# "VariantType.String": numpy.str, +# "VariantType.ByteString": numpy.uint8 # sequence of bytes, not a string +# } numpy_to_OPCua_dict = { - "<class 'numpy.bool_'>": opcua.ua.VariantType.Boolean, - "<class 'numpy.int8'>": opcua.ua.VariantType.SByte, - "<class 'numpy.uint8'>": opcua.ua.VariantType.Byte, - "<class 'numpy.int16'>": opcua.ua.VariantType.Int16, - "<class 'numpy.uint16'>": opcua.ua.VariantType.UInt16, - "<class 'numpy.int32'>": opcua.ua.VariantType.Int32, - "<class 'numpy.uint32'>": opcua.ua.VariantType.UInt32, - "<class 'numpy.int64'>": opcua.ua.VariantType.Int64, - "<class 'numpy.uint64'>": opcua.ua.VariantType.UInt64, - "<class 'numpy.datetime_data'>": opcua.ua.VariantType.DateTime, # is this the right type, does it even matter? - "<class 'numpy.float32'>": opcua.ua.VariantType.Float, - "<class 'numpy.float64'>": opcua.ua.VariantType.Double, - "<class 'numpy.double'>": opcua.ua.VariantType.Double, - "<class 'numpy.str_'>": opcua.ua.VariantType.String, - "<class 'numpy.str'>": opcua.ua.VariantType.String, - "str": opcua.ua.VariantType.String + numpy.bool_: opcua.ua.VariantType.Boolean, + numpy.int8: opcua.ua.VariantType.SByte, + numpy.uint8: opcua.ua.VariantType.Byte, + numpy.int16: opcua.ua.VariantType.Int16, + numpy.uint16: opcua.ua.VariantType.UInt16, + numpy.int32: opcua.ua.VariantType.Int32, + numpy.uint32: opcua.ua.VariantType.UInt32, + numpy.int64: opcua.ua.VariantType.Int64, + numpy.uint64: opcua.ua.VariantType.UInt64, + numpy.datetime_data: opcua.ua.VariantType.DateTime, # is this the right type, does it even matter? + numpy.float32: opcua.ua.VariantType.Float, + numpy.double: opcua.ua.VariantType.Double, + numpy.float64: opcua.ua.VariantType.Double, + numpy.str_: opcua.ua.VariantType.String, + numpy.str: opcua.ua.VariantType.String, + str: opcua.ua.VariantType.String } # <class 'numpy.bool_'> @@ -50,7 +50,7 @@ class OPCUAConnection(CommClient): def start(self): super().start() - def __init__(self, address, timeout, on_func, fault_func, streams, try_interval=2): + def __init__(self, address, namespace, timeout, on_func, fault_func, streams, try_interval=2): """ Create the OPC ua client and connect() to it and get the object node """ @@ -64,11 +64,13 @@ class OPCUAConnection(CommClient): fault_func() return - self.streams.debug_stream("Demo ||\t connection established") - # determine namespace used try: - self.name_space_index = self.client.get_namespace_index("http://lofar.eu") + if type(namespace) is str: + self.name_space_index = self.client.get_namespace_index(namespace) + elif type(namespace) is int: + self.name_space_index = namespace + except Exception as e: self.streams.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.") self.name_space_index = 2 @@ -149,7 +151,7 @@ class OPCUAConnection(CommClient): dim_x = attribute.dim_x dim_y = attribute.dim_y - ua_type = numpy_to_OPCua_dict[str(attribute.numpy_type)] # convert the numpy type to a corresponding UA type + ua_type = numpy_to_OPCua_dict[attribute.numpy_type] # convert the numpy type to a corresponding UA type return dim_x, dim_y, ua_type diff --git a/devices/src/attribute_wrapper.py b/devices/src/attribute_wrapper.py index f003df926801b207c7538fd52593a19d676c609f..1ea654f92f68c00080c3c31059a381411939580f 100644 --- a/devices/src/attribute_wrapper.py +++ b/devices/src/attribute_wrapper.py @@ -6,24 +6,13 @@ import numpy from src.wrappers import only_when_on, fault_on_error -def swap_dims_tuple(dims): - """ - arrays are inconsistent between tango and numpy. - This function exists to swap around the tuple containing the dimension data - """ - if len(dims) == 2: - return tuple((dims[1], dims[0])) - else: - return dims - - class attribute_wrapper(attribute): """ Wraps all the attributes in a wrapper class to manage most of the redundant code behind the scenes """ - def __init__(self, comms_annotation=None, datatype=None, dims=(1,), **kwargs): + def __init__(self, comms_annotation=None, datatype=None, dims=(1,), access=AttrWriteType.READ, init_value=None, **kwargs): """ wraps around the tango Attribute class. Provides an easier interface for 1d or 2d arrays. Also provides a way to abstract managing the communications interface. @@ -38,10 +27,7 @@ class attribute_wrapper(attribute): self.comms_annotation = comms_annotation # store data that can be used by the comms interface. not used by the wrapper itself self.numpy_type = datatype # tango changes our attribute to their representation (E.g numpy.int64 becomes "DevLong64") - wrap_RW = kwargs.get("access", AttrWriteType.READ) - - self.init_value = kwargs.get("init_value", None) # if not None, gets used as default value - + self.init_value = init_value max_dim_y = 0 # tango doesn't recognise numpy.str_, for consistencies sake we convert it here and hide this from the top level @@ -52,10 +38,6 @@ class attribute_wrapper(attribute): # check if not scalar if isinstance(dims, tuple): - # fill the array with initial values - # self.value = numpy.zeros(swap_dims_tuple(dims), dtype=datatype) - # self.value = numpy.full(swap_dims_tuple(dims), datatype(0), dtype=data_type) - # get first dimension max_dim_x = dims[0] @@ -72,7 +54,7 @@ class attribute_wrapper(attribute): max_dim_x = 1 - if wrap_RW == AttrWriteType.READ_WRITE: + if access == AttrWriteType.READ_WRITE: """ if the attribute is of READ_WRITE type, assign the RW and write function to it""" @only_when_on @@ -83,7 +65,7 @@ class attribute_wrapper(attribute): read_RW returns the value that was last written to the attribute """ try: - return device.value_dict[str(self)] + return device.value_dict[self] except: print() @@ -94,7 +76,7 @@ class attribute_wrapper(attribute): _write_RW writes a value to this attribute """ self.write_function(value) - device.value_dict[str(self)] = value + device.value_dict[self] = value self.fget = read_RW self.fset = write_RW @@ -109,12 +91,12 @@ class attribute_wrapper(attribute): """ _read_R reads the attribute value, stores it and returns it" """ - device.value_dict[str(self)] = self.read_function() - return device.value_dict[str(self)] + device.value_dict[self] = self.read_function() + return device.value_dict[self] self.fget = read_R - super().__init__(dtype=datatype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, **kwargs) + super().__init__(dtype=datatype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, access=access, **kwargs) return @@ -131,7 +113,12 @@ class attribute_wrapper(attribute): dims = (self.dim_x,) # x and y are swapped for numpy and Tango. to maintain tango conventions, x and y are swapped for numpy - value = numpy.zeros(swap_dims_tuple(dims), dtype=self.numpy_type) + if len(dims) == 2: + numpy_dims = tuple((dims[1], dims[0])) + else: + numpy_dims = dims + + value = numpy.zeros(numpy_dims, dtype=self.numpy_type) return value def set_comm_client(self, client): diff --git a/devices/src/hardware_device.py b/devices/src/hardware_device.py index 68afa9b82bfb28bcb30d972f9519b96007f56c56..0b9d488fb5f9df78d0768923c0b360c034b06acc 100644 --- a/devices/src/hardware_device.py +++ b/devices/src/hardware_device.py @@ -54,6 +54,11 @@ class hardware_device(Device): """ Return a list of all the attribute_wrapper members of this class. """ return [v for k, v in cls.__dict__.items() if type(v) == attribute_wrapper] + def setup_value_dict(self): + """ set the initial value for all the attribute wrapper objects""" + + self.value_dict = {i: i.initial_value() for i in self.attr_list()} + def init_device(self): """ Instantiates the device in the OFF state. """ @@ -76,12 +81,10 @@ class hardware_device(Device): :return:None """ self.set_state(DevState.INIT) - self.initialise() + self.setup_value_dict() - # if self.get_state() == DevState.STANDBY: - # # Already STANDBY. Don't complain. - # return - # self.set_state(DevState.STANDBY) + + self.initialise() @only_in_states([DevState.INIT]) def Standby(self): diff --git a/devices/test_device.py b/devices/test_device.py index e38ff5b9a39646890a2dbe31767a7ec0f178ab5a..27bdbfcb079e5019be6826ad8e7d3354ac296722 100644 --- a/devices/test_device.py +++ b/devices/test_device.py @@ -59,14 +59,6 @@ class test_device(hardware_device): float32_image_R = attribute_wrapper(comms_annotation="numpy.float32 type read image (dims = 8x2)", datatype=numpy.float32, dims=(8, 2)) uint8_image_RW = attribute_wrapper(comms_annotation="numpy.uint8 type read/write image (dims = 2x8)", datatype=numpy.uint8, dims=(2, 8), access=AttrWriteType.READ_WRITE) - tr_tod_R = attribute_wrapper(comms_annotation=["1:tr_tod_R"], datatype=numpy.uint64) - tr_uptime_R = attribute_wrapper(comms_annotation=["1:tr_uptime_R"], datatype=numpy.uint64) - - def setup_value_dict(self): - """ set the initial value for all the attribute wrapper objects""" - - self.value_dict = {str(i): i.initial_value() for i in self.attr_list()} - # -------- # overloaded functions # -------- @@ -80,10 +72,6 @@ class test_device(hardware_device): #set up the OPC ua client self.example_client = example_client(self.Standby, self.Fault, self) - - # NOTE: MANDATORY will contain all attribute values for this tango device instance - self.setup_value_dict() - # map an access helper class for i in self.attr_list(): i.set_comm_client(self.example_client)