diff --git a/devices/HW_device_template.py b/devices/HW_device_template.py index a16f11211f43f8aa49e4c9de47b1c48d1c280090..c9365b496f7717d764b4f38d1011ece09a8d1665 100644 --- a/devices/HW_device_template.py +++ b/devices/HW_device_template.py @@ -21,69 +21,69 @@ from src.hardware_device import * __all__ = ["HW_dev"] class HW_dev(hardware_device): - """ - This class is the minimal (read empty) implementation of a class using 'hardware_device' - """ - - # ---------- - # Attributes - # ---------- - """ - attribute wrapper objects can be declared here. All attribute wrapper objects will get automatically put in a ist (attr_list) for easy access - - example = attribute_wrapper(comms_annotation="this is an example", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE) - ... - - """ - - 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 fault(self): - """ user code here. is called when the state is set to FAULT """ - pass - - def off(self): - """ user code here. is called when the state is set to OFF """ - pass - - def on(self): - """ user code here. is called when the state is set to ON """ - - pass - - def standby(self): - """ user code here. is called when the state is set to STANDBY """ - pass - - def initialise(self): - """ user code here. is called when the sate is set to INIT """ - pass + """ + This class is the minimal (read empty) implementation of a class using 'hardware_device' + """ + + # ---------- + # Attributes + # ---------- + """ + attribute wrapper objects can be declared here. All attribute wrapper objects will get automatically put in a ist (attr_list) for easy access + + example = attribute_wrapper(comms_annotation="this is an example", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE) + ... + + """ + + 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 fault(self): + """ user code here. is called when the state is set to FAULT """ + pass + + def off(self): + """ user code here. is called when the state is set to OFF """ + pass + + def on(self): + """ user code here. is called when the state is set to ON """ + + pass + + def standby(self): + """ user code here. is called when the state is set to STANDBY """ + pass + + def initialise(self): + """ user code here. is called when the sate is set to INIT """ + pass # ---------- # Run server # ---------- def main(args=None, **kwargs): - """Main function of the hardware device module.""" - return run((HW_dev,), args=args, **kwargs) + """Main function of the hardware device module.""" + return run((HW_dev,), args=args, **kwargs) if __name__ == '__main__': - main() + main() diff --git a/devices/PCC.py b/devices/PCC.py index 84773c9ae8b030390461e6ce233c5e0b18b0f693..753b06285329bc50a980f795839c6f48056c73ef 100644 --- a/devices/PCC.py +++ b/devices/PCC.py @@ -27,221 +27,221 @@ __all__ = ["PCC", "main"] @device_logging_to_python({"device": "PCC"}) class PCC(hardware_device): - # ----------------- - # Device Properties - # ----------------- - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) + # ----------------- + # Device Properties + # ----------------- + OPC_Server_Name = device_property( + dtype='DevString', + mandatory=True + ) - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) + OPC_Server_Port = device_property( + dtype='DevULong', + mandatory=True + ) - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) + OPC_Time_Out = device_property( + dtype='DevDouble', + mandatory=True + ) - OPC_Namespace = device_property( - dtype='DevString', - mandatory=False - ) + OPC_Namespace = device_property( + dtype='DevString', + mandatory=False + ) - # ---------- - # Attributes - # ---------- - RCU_state_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_state_R"], datatype=numpy.str_, access=AttrWriteType.READ_WRITE) + # ---------- + # Attributes + # ---------- + RCU_state_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_state_R"], datatype=numpy.str_, access=AttrWriteType.READ_WRITE) - RCU_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_mask_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) + RCU_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_mask_RW"], datatype=numpy.bool_, dims=(32,), access=AttrWriteType.READ_WRITE) - Ant_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:Ant_mask_RW"], datatype=numpy.bool_, dims=(3, 32), access=AttrWriteType.READ_WRITE) + Ant_mask_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:Ant_mask_RW"], datatype=numpy.bool_, dims=(3, 32), access=AttrWriteType.READ_WRITE) - RCU_attenuator_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_attenuator_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_attenuator_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) + RCU_attenuator_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_attenuator_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) - RCU_band_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_band_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_band_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) + RCU_band_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_band_RW"], datatype=numpy.int64, dims=(3, 32), access=AttrWriteType.READ_WRITE) - RCU_temperature_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_temperature_R"], datatype=numpy.float64, dims=(32,)) + RCU_temperature_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_temperature_R"], datatype=numpy.float64, dims=(32,)) - RCU_Pwr_dig_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_Pwr_dig_R"], datatype=numpy.int64, dims=(32,)) + RCU_Pwr_dig_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_Pwr_dig_R"], datatype=numpy.int64, dims=(32,)) - RCU_LED0_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_R"], datatype=numpy.int64, dims=(32,)) + RCU_LED0_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_R"], datatype=numpy.int64, dims=(32,)) - RCU_LED0_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_RW"], datatype=numpy.int64, dims=(32,), access=AttrWriteType.READ_WRITE) + RCU_LED0_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_LED0_RW"], datatype=numpy.int64, dims=(32,), access=AttrWriteType.READ_WRITE) - RCU_ADC_lock_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_lock_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_ADC_lock_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_lock_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_ADC_SYNC_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_SYNC_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_ADC_SYNC_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_SYNC_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_ADC_JESD_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_JESD_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_ADC_JESD_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_JESD_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_ADC_CML_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_CML_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_ADC_CML_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ADC_CML_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_OUT1_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT1_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_OUT1_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT1_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_OUT2_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT2_R"], datatype=numpy.int64, dims=(3, 32)) + RCU_OUT2_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_OUT2_R"], datatype=numpy.int64, dims=(3, 32)) - RCU_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ID_R"], datatype=numpy.int64, dims=(32,)) + RCU_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_ID_R"], datatype=numpy.int64, dims=(32,)) - RCU_version_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_version_R"], datatype=numpy.str_, dims=(32,)) + RCU_version_R = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_version_R"], datatype=numpy.str_, dims=(32,)) - HBA_element_beamformer_delays_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_R"], datatype=numpy.int64, dims=(32, 96)) + HBA_element_beamformer_delays_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_beamformer_delays_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) + HBA_element_beamformer_delays_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_beamformer_delays_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - HBA_element_pwr_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_R"], datatype=numpy.int64, dims=(32, 96)) + HBA_element_pwr_R = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_R"], datatype=numpy.int64, dims=(32, 96)) - HBA_element_pwr_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) + HBA_element_pwr_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:HBA_element_pwr_RW"], datatype=numpy.int64, dims=(32, 96), access=AttrWriteType.READ_WRITE) - uC_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:uC_ID_R"], datatype=numpy.int64, dims=(32,)) + uC_ID_R = attribute_wrapper(comms_annotation=["2:PCC", "2:uC_ID_R"], datatype=numpy.int64, dims=(32,)) - RCU_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_monitor_rate_RW"], datatype=numpy.float64, access=AttrWriteType.READ_WRITE) + RCU_monitor_rate_RW = attribute_wrapper(comms_annotation=["2:PCC", "2:RCU_monitor_rate_RW"], datatype=numpy.float64, access=AttrWriteType.READ_WRITE) - def delete_device(self): - """Hook to delete resources allocated in init_device. + 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.") + 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 off(self): - """ user code here. is called when the state is set to OFF """ + # -------- + # overloaded functions + # -------- + def off(self): + """ user code here. is called when the state is set to OFF """ - # Stop keep-alive - self.OPCua_client.disconnect() + # Stop keep-alive + self.OPCua_client.disconnect() - def initialise(self): - """ user code here. is called when the state is set to INIT """ + def initialise(self): + """ user code here. is called when the state is set to INIT """ - #set up the OPC ua client - namespace = "http://lofar.eu" - if self.OPC_Namespace is not None: - namespace = self.OPC_Namespace + #set up the OPC ua client + namespace = "http://lofar.eu" + if self.OPC_Namespace is not None: + namespace = self.OPC_Namespace - self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), namespace, self.OPC_Time_Out, self.Standby, self.Fault, self) + self.OPCua_client = OPCUAConnection("opc.tcp://{}:{}/".format(self.OPC_Server_Name, self.OPC_Server_Port), namespace, self.OPC_Time_Out, self.Standby, self.Fault, self) - # map the attributes to the OPC ua comm client - for i in self.attr_list(): - try: - i.set_comm_client(self.OPCua_client) - except: - pass + # map the attributes to the OPC ua comm client + for i in self.attr_list(): + try: + i.set_comm_client(self.OPCua_client) + except: + pass - # Init the dict that contains function to OPC-UA function mappings. - self.function_mapping = {} - self.function_mapping["RCU_off"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_off"]) - self.function_mapping["RCU_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_on"]) - self.function_mapping["ADC_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:ADC_on"]) - self.function_mapping["RCU_update"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_update"]) - self.function_mapping["CLK_off"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_off"]) - self.function_mapping["CLK_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_on"]) - self.function_mapping["CLK_PLL_setup"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_PLL_setup"]) + # Init the dict that contains function to OPC-UA function mappings. + self.function_mapping = {} + self.function_mapping["RCU_off"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_off"]) + self.function_mapping["RCU_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_on"]) + self.function_mapping["ADC_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:ADC_on"]) + self.function_mapping["RCU_update"] = self.OPCua_client._setup_annotation(["2:PCC", "2:RCU_update"]) + self.function_mapping["CLK_off"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_off"]) + self.function_mapping["CLK_on"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_on"]) + self.function_mapping["CLK_PLL_setup"] = self.OPCua_client._setup_annotation(["2:PCC", "2:CLK_PLL_setup"]) - self.OPCua_client.start() + self.OPCua_client.start() - # -------- - # Commands - # -------- - @command() - @DebugIt() - @only_when_on - @fault_on_error - def RCU_off(self): - """ + # -------- + # Commands + # -------- + @command() + @DebugIt() + @only_when_on + @fault_on_error + def RCU_off(self): + """ - :return:None - """ - self.function_mapping["RCU_off"]() + :return:None + """ + self.function_mapping["RCU_off"]() - @command() - @DebugIt() - @only_when_on - @fault_on_error - def RCU_on(self): - """ + @command() + @DebugIt() + @only_when_on + @fault_on_error + def RCU_on(self): + """ - :return:None - """ - self.function_mapping["RCU_on"]() + :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"]() + @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 # ---------- def main(args=None, **kwargs): - """Main function of the PCC module.""" - return run((PCC,), args=args, **kwargs) + """Main function of the PCC module.""" + return run((PCC,), args=args, **kwargs) if __name__ == '__main__': - main() + main() diff --git a/devices/SDP.py b/devices/SDP.py index f2e87890c112bcb3027747f7ec2fd464282b7faa..10a1d8a5578b0df80cad13ef2dd2b9e9482d51c0 100644 --- a/devices/SDP.py +++ b/devices/SDP.py @@ -26,108 +26,108 @@ __all__ = ["SDP", "main"] @device_logging_to_python({"device": "SDP"}) class SDP(hardware_device): - """ - - **Properties:** - - - Device Property - OPC_Server_Name - - Type:'DevString' - OPC_Server_Port - - Type:'DevULong' - OPC_Time_Out - - Type:'DevDouble' - """ - - # ----------------- - # Device Properties - # ----------------- - - OPC_Server_Name = device_property( - dtype='DevString', - mandatory=True - ) - - OPC_Server_Port = device_property( - dtype='DevULong', - mandatory=True - ) - - OPC_Time_Out = device_property( - dtype='DevDouble', - mandatory=True - ) - - # ---------- - # Attributes - # ---------- - fpga_mask_RW = attribute_wrapper(comms_annotation=["1:fpga_mask_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) - fpga_scrap_R = attribute_wrapper(comms_annotation=["1:fpga_scrap_R"], datatype=numpy.int32, dims=(2048,)) - fpga_scrap_RW = attribute_wrapper(comms_annotation=["1:fpga_scrap_RW"], datatype=numpy.int32, dims=(2048,), access=AttrWriteType.READ_WRITE) - fpga_status_R = attribute_wrapper(comms_annotation=["1:fpga_status_R"], datatype=numpy.bool_, dims=(16,)) - fpga_temp_R = attribute_wrapper(comms_annotation=["1:fpga_temp_R"], datatype=numpy.float_, dims=(16,)) - fpga_version_R = attribute_wrapper(comms_annotation=["1:fpga_version_R"], datatype=numpy.str_, dims=(16,)) - fpga_weights_R = attribute_wrapper(comms_annotation=["1:fpga_weights_R"], datatype=numpy.int16, dims=(16, 12 * 488 * 2)) - fpga_weights_RW = attribute_wrapper(comms_annotation=["1:fpga_weights_RW"], datatype=numpy.int16, dims=(16, 12 * 488 * 2), access=AttrWriteType.READ_WRITE) - tr_busy_R = attribute_wrapper(comms_annotation=["1:tr_busy_R"], datatype=numpy.bool_) - # NOTE: typo in node name is 'tr_reload_W' should be 'tr_reload_RW' - tr_reload_RW = attribute_wrapper(comms_annotation=["1:tr_reload_W"], datatype=numpy.bool_, 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 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 off(self): - """ user code here. is called when the state is set to OFF """ - - # Stop keep-alive - self.opcua_connection.stop() - - def initialise(self): - """ user code here. is called when the sate is set to INIT """ - """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), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self) - - # will contain all the values for this object - self.setup_value_dict() - - # map an access helper class - for i in self.attr_list(): - i.set_comm_client(self.OPCua_client) - - self.OPCua_client.start() - - # -------- - # Commands - # -------- + """ + + **Properties:** + + - Device Property + OPC_Server_Name + - Type:'DevString' + OPC_Server_Port + - Type:'DevULong' + OPC_Time_Out + - Type:'DevDouble' + """ + + # ----------------- + # Device Properties + # ----------------- + + OPC_Server_Name = device_property( + dtype='DevString', + mandatory=True + ) + + OPC_Server_Port = device_property( + dtype='DevULong', + mandatory=True + ) + + OPC_Time_Out = device_property( + dtype='DevDouble', + mandatory=True + ) + + # ---------- + # Attributes + # ---------- + fpga_mask_RW = attribute_wrapper(comms_annotation=["1:fpga_mask_RW"], datatype=numpy.bool_, dims=(16,), access=AttrWriteType.READ_WRITE) + fpga_scrap_R = attribute_wrapper(comms_annotation=["1:fpga_scrap_R"], datatype=numpy.int32, dims=(2048,)) + fpga_scrap_RW = attribute_wrapper(comms_annotation=["1:fpga_scrap_RW"], datatype=numpy.int32, dims=(2048,), access=AttrWriteType.READ_WRITE) + fpga_status_R = attribute_wrapper(comms_annotation=["1:fpga_status_R"], datatype=numpy.bool_, dims=(16,)) + fpga_temp_R = attribute_wrapper(comms_annotation=["1:fpga_temp_R"], datatype=numpy.float_, dims=(16,)) + fpga_version_R = attribute_wrapper(comms_annotation=["1:fpga_version_R"], datatype=numpy.str_, dims=(16,)) + fpga_weights_R = attribute_wrapper(comms_annotation=["1:fpga_weights_R"], datatype=numpy.int16, dims=(16, 12 * 488 * 2)) + fpga_weights_RW = attribute_wrapper(comms_annotation=["1:fpga_weights_RW"], datatype=numpy.int16, dims=(16, 12 * 488 * 2), access=AttrWriteType.READ_WRITE) + tr_busy_R = attribute_wrapper(comms_annotation=["1:tr_busy_R"], datatype=numpy.bool_) + # NOTE: typo in node name is 'tr_reload_W' should be 'tr_reload_RW' + tr_reload_RW = attribute_wrapper(comms_annotation=["1:tr_reload_W"], datatype=numpy.bool_, 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 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 off(self): + """ user code here. is called when the state is set to OFF """ + + # Stop keep-alive + self.opcua_connection.stop() + + def initialise(self): + """ user code here. is called when the sate is set to INIT """ + """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), "http://lofar.eu", self.OPC_Time_Out, self.Standby, self.Fault, self) + + # will contain all the values for this object + self.setup_value_dict() + + # map an access helper class + 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 SDP module.""" - return run((SDP,), args=args, **kwargs) + """Main function of the SDP module.""" + return run((SDP,), args=args, **kwargs) if __name__ == '__main__': - main() + main() diff --git a/devices/clients/opcua_connection.py b/devices/clients/opcua_connection.py index 9e5e488905775d0b8941e5c508d7179b5b9d73bd..676b24c650f0de251d850625fce1a47b1bc6518a 100644 --- a/devices/clients/opcua_connection.py +++ b/devices/clients/opcua_connection.py @@ -4,202 +4,202 @@ import opcua __all__ = ["OPCUAConnection"] numpy_to_OPCua_dict = { - 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 + 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_'> class OPCUAConnection(CommClient): - """ - Connects to OPC-UA in the foreground or background, and sends HELLO - messages to keep a check on the connection. On connection failure, reconnects once. - """ - - def start(self): - super().start() - - 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 - """ - super().__init__(on_func, fault_func, streams, try_interval) - - self.client = opcua.Client(address, timeout) - - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - fault_func() - return - - # determine namespace used - try: - 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: - #TODO remove once SDP is fixed - self.streams.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.") - self.name_space_index = 2 - - self.obj = self.client.get_objects_node() - - def _servername(self): - return self.client.server_url.geturl() - - def connect(self): - """ - Try to connect to the client - """ - - try: - self.streams.debug_stream("Connecting to server %s", self._servername()) - self.client.connect() - self.connected = True - self.streams.debug_stream("Connected to %s. Initialising.", self._servername()) - return True - except socket.error as e: - self.streams.error_stream("Could not connect to server %s: %s", self._servername(), e) - raise Exception("") from e - - - def disconnect(self): - """ - disconnect from the client - """ - self.connected = False # always force a reconnect, regardless of a successful disconnect - - try: - self.client.disconnect() - except Exception as e: - self.streams.error_stream("Disconnect from OPC-UA server {} failed: {}".format(self._servername(), e)) - - def ping(self): - """ - ping the client to make sure the connection with the client is still functional. - """ - try: - if self.connected is True: - self.client.send_hello() - else: - self.streams.debug_stream("Will not ping OPC-UA server {} because the connection is inactive.".format(self._servername())) - except Exception as e: - raise Exception("Lost connection to server {}.".format(self._servername())) from e - - 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('path') is None: - raise Exception("OPC-ua mapping requires a path argument in the annotation, was given: %s", annotation) - - path = annotation.get("path") # required - elif isinstance(annotation, list): - path = annotation - else: - raise Exception("OPC-ua mapping requires either a list of the path or dict with the path. Was given {} type containing: {}".format(type(annotation), annotation)) - - try: - node = self.obj.get_child(path) - except Exception as e: - raise Exception("Could not get node: {} on server {}".format(path, self._servername())) from e - - return node - - 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 - 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 - - 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 - node = self._setup_annotation(annotation) - - # get all the necessary data to set up the read/write functions from the attribute_wrapper - dim_x, dim_y, ua_type = self.setup_value_conversion(attribute) - - # configure and return the read/write functions - prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type) - - try: - # NOTE: debug statement tries to get the qualified name, this may not always work. in that case forgo the name and just print the path - node_name = str(node.get_browse_name())[len("QualifiedName(2:"):] - self.streams.debug_stream("connected OPC ua node {} of type {} to attribute with dimensions: {} x {} ".format(str(node_name)[:len(node_name)-1], str(ua_type)[len("VariantType."):], dim_x, dim_y)) - except: - pass - - # return the read/write functions - return prot_attr.read_function, prot_attr.write_function + """ + Connects to OPC-UA in the foreground or background, and sends HELLO + messages to keep a check on the connection. On connection failure, reconnects once. + """ + + def start(self): + super().start() + + 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 + """ + super().__init__(on_func, fault_func, streams, try_interval) + + self.client = opcua.Client(address, timeout) + + # Explicitly connect + if not self.connect(): + # hardware or infra is down -- needs fixing first + fault_func() + return + + # determine namespace used + try: + 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: + #TODO remove once SDP is fixed + self.streams.warn_stream("Cannot determine the OPC-UA name space index. Will try and use the default = 2.") + self.name_space_index = 2 + + self.obj = self.client.get_objects_node() + + def _servername(self): + return self.client.server_url.geturl() + + def connect(self): + """ + Try to connect to the client + """ + + try: + self.streams.debug_stream("Connecting to server %s", self._servername()) + self.client.connect() + self.connected = True + self.streams.debug_stream("Connected to %s. Initialising.", self._servername()) + return True + except socket.error as e: + self.streams.error_stream("Could not connect to server %s: %s", self._servername(), e) + raise Exception("") from e + + + def disconnect(self): + """ + disconnect from the client + """ + self.connected = False # always force a reconnect, regardless of a successful disconnect + + try: + self.client.disconnect() + except Exception as e: + self.streams.error_stream("Disconnect from OPC-UA server {} failed: {}".format(self._servername(), e)) + + def ping(self): + """ + ping the client to make sure the connection with the client is still functional. + """ + try: + if self.connected is True: + self.client.send_hello() + else: + self.streams.debug_stream("Will not ping OPC-UA server {} because the connection is inactive.".format(self._servername())) + except Exception as e: + raise Exception("Lost connection to server {}.".format(self._servername())) from e + + 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('path') is None: + raise Exception("OPC-ua mapping requires a path argument in the annotation, was given: %s", annotation) + + path = annotation.get("path") # required + elif isinstance(annotation, list): + path = annotation + else: + raise Exception("OPC-ua mapping requires either a list of the path or dict with the path. Was given {} type containing: {}".format(type(annotation), annotation)) + + try: + node = self.obj.get_child(path) + except Exception as e: + raise Exception("Could not get node: {} on server {}".format(path, self._servername())) from e + + return node + + 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 + 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 + + 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 + node = self._setup_annotation(annotation) + + # get all the necessary data to set up the read/write functions from the attribute_wrapper + dim_x, dim_y, ua_type = self.setup_value_conversion(attribute) + + # configure and return the read/write functions + prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type) + + try: + # NOTE: debug statement tries to get the qualified name, this may not always work. in that case forgo the name and just print the path + node_name = str(node.get_browse_name())[len("QualifiedName(2:"):] + self.streams.debug_stream("connected OPC ua node {} of type {} to attribute with dimensions: {} x {} ".format(str(node_name)[:len(node_name)-1], str(ua_type)[len("VariantType."):], dim_x, dim_y)) + except: + pass + + # return the read/write functions + return prot_attr.read_function, prot_attr.write_function class ProtocolAttribute: - """ - This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code - """ - - def __init__(self, node, dim_x, dim_y, ua_type): - self.node = node - self.dim_y = dim_y - self.dim_x = dim_x - self.ua_type = ua_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)) + """ + This class provides a small wrapper for the OPC ua read/write functions in order to better organise the code + """ + + def __init__(self, node, dim_x, dim_y, ua_type): + self.node = node + self.dim_y = dim_y + self.dim_x = dim_x + self.ua_type = ua_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/test_client.py b/devices/clients/test_client.py index d3e63930a0354ed0e061d9ee54d6f8a9e661fda8..662bd692b462953414208c4cb1fc346474a2827b 100644 --- a/devices/clients/test_client.py +++ b/devices/clients/test_client.py @@ -3,104 +3,104 @@ from src.comms_client import * # <class 'numpy.bool_'> class example_client(CommClient): - """ - this class provides an example implementation of a comms_client. - Durirng initialisation it creates a correctly shaped zero filled value. on read that value is returned and on write its modified. - """ + """ + this class provides an example implementation of a comms_client. + Durirng initialisation it creates a correctly shaped zero filled value. on read that value is returned and on write its modified. + """ - def start(self): - super().start() + def start(self): + super().start() - def __init__(self, standby_func, fault_func, streams, try_interval=2): - """ - initialises the class and tries to connect to the client. - """ - super().__init__(standby_func, fault_func, streams, try_interval) + def __init__(self, standby_func, fault_func, streams, try_interval=2): + """ + initialises the class and tries to connect to the client. + """ + super().__init__(standby_func, fault_func, streams, try_interval) - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - fault_func() - return + # Explicitly connect + if not self.connect(): + # hardware or infra is down -- needs fixing first + fault_func() + return - def connect(self): - """ - this function provides a location for the code neccecary to connect to the client - """ + def connect(self): + """ + this function provides a location for the code neccecary to connect to the client + """ - self.streams.debug_stream("the example client doesn't actually connect to anything silly") + self.streams.debug_stream("the example client doesn't actually connect to anything silly") - self.connected = True # set connected to true - return True # if succesfull, return true. otherwise return false + self.connected = True # set connected to true + return True # if succesfull, return true. otherwise return false - def disconnect(self): - self.connected = False # always force a reconnect, regardless of a successful disconnect - self.streams.debug_stream("disconnected from the 'client' ") + def disconnect(self): + self.connected = False # always force a reconnect, regardless of a successful disconnect + self.streams.debug_stream("disconnected from the 'client' ") - def _setup_annotation(self, annotation): - """ - this function gives the client access to the comm client annotation data given to the attribute wrapper. - The annotation data can be used to provide whatever extra data is necessary in order to find/access the monitor/control point. + def _setup_annotation(self, annotation): + """ + this function gives the client access to the comm client annotation data given to the attribute wrapper. + The annotation data can be used to provide whatever extra data is necessary in order to find/access the monitor/control point. - the annotation can be in whatever format may be required. it is up to the user to handle its content - example annotation may include: - - a file path and file line/location - - COM object path - """ + the annotation can be in whatever format may be required. it is up to the user to handle its content + example annotation may include: + - a file path and file line/location + - COM object path + """ - # as this is an example, just print the annotation - self.streams.debug_stream("annotation: {}".format(annotation)) + # as this is an example, just print the annotation + self.streams.debug_stream("annotation: {}".format(annotation)) - def _setup_value_conversion(self, attribute): - """ - gives the client access to the attribute_wrapper object in order to access all - necessary data such as dimensionality and data type - """ + def _setup_value_conversion(self, attribute): + """ + gives the client access to the attribute_wrapper object in order to access all + necessary data such as dimensionality and data type + """ - if attribute.dim_y > 1: - dims = (attribute.dim_y, attribute.dim_x) - else: - dims = (attribute.dim_x,) + if attribute.dim_y > 1: + dims = (attribute.dim_y, attribute.dim_x) + else: + dims = (attribute.dim_x,) - dtype = attribute.numpy_type + dtype = attribute.numpy_type - return dims, dtype + return dims, dtype - def _setup_mapping(self, dims, dtype): - """ - takes all gathered data to configure and return the correct read and write functions - """ + def _setup_mapping(self, dims, dtype): + """ + takes all gathered data to configure and return the correct read and write functions + """ - value = numpy.zeros(dims, dtype) + value = numpy.zeros(dims, dtype) - def read_function(): - self.streams.debug_stream("from read_function, reading {} array of type {}".format(dims, dtype)) - return value + def read_function(): + self.streams.debug_stream("from read_function, reading {} array of type {}".format(dims, dtype)) + return value - def write_function(write_value): - self.streams.debug_stream("from write_function, writing {} array of type {}".format(dims, dtype)) - value = write_value + def write_function(write_value): + self.streams.debug_stream("from write_function, writing {} array of type {}".format(dims, dtype)) + value = write_value - self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object") - return read_function, write_function + self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object") + return read_function, write_function - def setup_attribute(self, annotation=None, attribute=None): - """ - MANDATORY function: is used by the attribute wrapper to get read/write functions. - must return the read and write functions - """ + def setup_attribute(self, annotation=None, attribute=None): + """ + MANDATORY function: is used by the attribute wrapper to get read/write functions. + must return the read and write functions + """ - # process the comms_annotation - self._setup_annotation(annotation) + # process the comms_annotation + self._setup_annotation(annotation) - # get all the necessary data to set up the read/write functions from the attribute_wrapper - dims, dtype = self._setup_value_conversion(attribute) + # get all the necessary data to set up the read/write functions from the attribute_wrapper + dims, dtype = self._setup_value_conversion(attribute) - # configure and return the read/write functions - read_function, write_function = self._setup_mapping(dims, dtype) + # configure and return the read/write functions + read_function, write_function = self._setup_mapping(dims, dtype) - # return the read/write functions - return read_function, write_function + # return the read/write functions + return read_function, write_function diff --git a/devices/src/attribute_wrapper.py b/devices/src/attribute_wrapper.py index bb5a3475159e633d8904c3060759739cee538293..bdf457df41ba76c037de64faf4e99ecf4940ca70 100644 --- a/devices/src/attribute_wrapper.py +++ b/devices/src/attribute_wrapper.py @@ -9,133 +9,133 @@ logger = logging.getLogger() 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,), 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. - """ - - # ensure the type is a numpy array - if "numpy" not in str(datatype) and type(datatype) != str: - raise TypeError("Attribute needs to be a Tango-supported numpy or str type, but has type \"%s\"" % (datatype,)) + """ + 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,), 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. + """ + + # ensure the type is a numpy array + if "numpy" not in str(datatype) and type(datatype) != str: + raise TypeError("Attribute needs to be a Tango-supported numpy or str type, but has type \"%s\"" % (datatype,)) - - 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") - - 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 - # NOTE: discuss, idk if this is an important detail somewhere else - if datatype is numpy.str_: - datatype = str - - # check if not scalar - if isinstance(dims, tuple): - - # get first dimension - max_dim_x = dims[0] - - # single dimension/spectrum requires the datatype to be wrapped in a tuple - datatype = (datatype,) - - if len(dims) == 2: - # get second dimension - max_dim_y = dims[1] - # wrap the datatype tuple in another tuple for 2d arrays/images - datatype = (datatype,) - else: - # scalar, just set the single dimension - max_dim_x = 1 - - - if access == AttrWriteType.READ_WRITE: - """ if the attribute is of READ_WRITE type, assign the RW and write function to it""" - - @only_when_on - @fault_on_error - def read_RW(device): - # print("read_RW {}, {}x{}, {}, {}".format(me.name, me.dim_x, me.dim_y, me.attr_type, me.value)) - """ - read_RW returns the value that was last written to the attribute - """ - try: - return device.value_dict[self] - except Exception as e: - raise Exception("Attribute read_RW function error, attempted to read value_dict with key: `%s`, are you sure this exists?", self) from e - - - @only_when_on - @fault_on_error - def write_RW(device, value): - """ - _write_RW writes a value to this attribute - """ - self.write_function(value) - device.value_dict[self] = value - - self.fget = read_RW - self.fset = write_RW - - - else: - """ if the attribute is of READ type, assign the read function to it""" - - @only_when_on - @fault_on_error - def read_R(device): - """ - _read_R reads the attribute value, stores it and returns it" - """ - 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, access=access, **kwargs) - - return - - def initial_value(self): - """ - returns a numpy array filled with zeroes fit to the size of the attribute. Or if init_value is not the default None, return that value - """ - if self.init_value is not None: - return self.init_value - - if self.dim_y > 1: - dims = (self.dim_x, self.dim_y) - else: - dims = (self.dim_x,) - - # x and y are swapped for numpy and Tango. to maintain tango conventions, x and y are swapped for numpy - 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): - """ - takes a communications client as input arguments This client should be of a class containing a "get_mapping" function - and return a read and write function that the wrapper will use to get/set data. - """ - try: - self.read_function, self.write_function = client.setup_attribute(self.comms_annotation, self) - except Exception as e: - def pass_func(value=None): - pass - logger.error("setting comm_client failed. using pass function instead") - - self.read_function = pass_func - self.write_function = pass_func - - raise Exception("Exception while setting comm_client read/write functions. using pass function instead. %s") from e + + 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") + + 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 + # NOTE: discuss, idk if this is an important detail somewhere else + if datatype is numpy.str_: + datatype = str + + # check if not scalar + if isinstance(dims, tuple): + + # get first dimension + max_dim_x = dims[0] + + # single dimension/spectrum requires the datatype to be wrapped in a tuple + datatype = (datatype,) + + if len(dims) == 2: + # get second dimension + max_dim_y = dims[1] + # wrap the datatype tuple in another tuple for 2d arrays/images + datatype = (datatype,) + else: + # scalar, just set the single dimension + max_dim_x = 1 + + + if access == AttrWriteType.READ_WRITE: + """ if the attribute is of READ_WRITE type, assign the RW and write function to it""" + + @only_when_on + @fault_on_error + def read_RW(device): + # print("read_RW {}, {}x{}, {}, {}".format(me.name, me.dim_x, me.dim_y, me.attr_type, me.value)) + """ + read_RW returns the value that was last written to the attribute + """ + try: + return device.value_dict[self] + except Exception as e: + raise Exception("Attribute read_RW function error, attempted to read value_dict with key: `%s`, are you sure this exists?", self) from e + + + @only_when_on + @fault_on_error + def write_RW(device, value): + """ + _write_RW writes a value to this attribute + """ + self.write_function(value) + device.value_dict[self] = value + + self.fget = read_RW + self.fset = write_RW + + + else: + """ if the attribute is of READ type, assign the read function to it""" + + @only_when_on + @fault_on_error + def read_R(device): + """ + _read_R reads the attribute value, stores it and returns it" + """ + 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, access=access, **kwargs) + + return + + def initial_value(self): + """ + returns a numpy array filled with zeroes fit to the size of the attribute. Or if init_value is not the default None, return that value + """ + if self.init_value is not None: + return self.init_value + + if self.dim_y > 1: + dims = (self.dim_x, self.dim_y) + else: + dims = (self.dim_x,) + + # x and y are swapped for numpy and Tango. to maintain tango conventions, x and y are swapped for numpy + 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): + """ + takes a communications client as input arguments This client should be of a class containing a "get_mapping" function + and return a read and write function that the wrapper will use to get/set data. + """ + try: + self.read_function, self.write_function = client.setup_attribute(self.comms_annotation, self) + except Exception as e: + def pass_func(value=None): + pass + logger.error("setting comm_client failed. using pass function instead") + + self.read_function = pass_func + self.write_function = pass_func + + raise Exception("Exception while setting comm_client read/write functions. using pass function instead. %s") from e diff --git a/devices/src/comms_client.py b/devices/src/comms_client.py index 789e9362382d70f657711b9724ae8fbee5df4038..6454ddffb14a5ab041ab8a3045ccf97e0226b0df 100644 --- a/devices/src/comms_client.py +++ b/devices/src/comms_client.py @@ -7,130 +7,130 @@ from tango import DevState class CommClient(Thread): - """ - The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world - """ - - def __init__(self, standby_func, fault_func, streams, try_interval=2): - """ - - """ - self.standby_func = standby_func - self.fault_func = fault_func - self.try_interval = try_interval - self.streams = streams - self.stopping = False - self.connected = False - - super().__init__(daemon=True) - - def connect(self): - """ - Function used to connect to the client. - """ - self.connected = True - return True - - def disconnect(self): - """ - Function used to connect to the client. - """ - self.connected = False - - def run(self): - - # Explicitly connect - if not self.connect(): - # hardware or infra is down -- needs fixing first - self.fault_func() - return - - self.standby_func() - - self.stopping = False - while not self.stopping: - # keep trying to connect - if not self.connected: - if self.connect(): - self.standby_func() - else: - # we retry only once, to catch exotic network issues. if the infra or hardware is down, - # our device cannot help, and must be reinitialised after the infra or hardware is fixed. - self.fault_func() - return - - # keep checking if the connection is still alive - try: - while not self.stopping: - self.ping() - time.sleep(self.try_interval) - except Exception as e: - self.streams.error_stream("Fault condition in communication detected.", e) - - # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect. - self.disconnect() - - # signal that we're disconnected - self.fault_func() - - def ping(self): - pass - - def stop(self): - """ - Stop connecting & disconnect. Can take a few seconds for the timeouts to hit. - """ - - if not self.ident: - # have not yet been started, so nothing to do - return - - self.stopping = True - self.join() - - self.disconnect() - - def setup_attribute(self, annotation, attribute): - """ - This function is responsible for providing the attribute_wrapper with a read/write function - How this is done is implementation specific. - The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client - as well as a reference to the attribute itself. - - It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions. - It should then return the read and write functions to the attribute. - - MANDATORY: - annotation_outputs = _setup_annotation(annotation) - attribute_outputs = _setup_annotation(attribute) - (note: outputs are up to the user) - - REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided, - except that the read function takes a single input value and the write function returns a single value - - MANDATORY: - return read_function, write_function - - Examples: - - File system: get_mapping returns functions that read/write a fixed - number of bytes at a fixed location in a file. (SEEK) - - OPC-UA: traverse the OPC-UA tree until the node is found. - Then return the read/write functions for that node which automatically - convert values between Python and OPC-UA. - """ - raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute") - - def _setup_annotation(self, annotation): - """ - This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide. - This function should be called by setup_attribute - """ - raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user") - - def setup_value_conversion(self, attribute): - """ - this function is responsible for setting up the value conversion between the client and the attribute. - This function should be called by setup_attribute - """ - raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user") + """ + The ProtocolHandler class is the generic interface class between the tango attribute_wrapper and the outside world + """ + + def __init__(self, standby_func, fault_func, streams, try_interval=2): + """ + + """ + self.standby_func = standby_func + self.fault_func = fault_func + self.try_interval = try_interval + self.streams = streams + self.stopping = False + self.connected = False + + super().__init__(daemon=True) + + def connect(self): + """ + Function used to connect to the client. + """ + self.connected = True + return True + + def disconnect(self): + """ + Function used to connect to the client. + """ + self.connected = False + + def run(self): + + # Explicitly connect + if not self.connect(): + # hardware or infra is down -- needs fixing first + self.fault_func() + return + + self.standby_func() + + self.stopping = False + while not self.stopping: + # keep trying to connect + if not self.connected: + if self.connect(): + self.standby_func() + else: + # we retry only once, to catch exotic network issues. if the infra or hardware is down, + # our device cannot help, and must be reinitialised after the infra or hardware is fixed. + self.fault_func() + return + + # keep checking if the connection is still alive + try: + while not self.stopping: + self.ping() + time.sleep(self.try_interval) + except Exception as e: + self.streams.error_stream("Fault condition in communication detected.", e) + + # technically, we may not have dropped the connection, but encounter a different error. so explicitly disconnect. + self.disconnect() + + # signal that we're disconnected + self.fault_func() + + def ping(self): + pass + + def stop(self): + """ + Stop connecting & disconnect. Can take a few seconds for the timeouts to hit. + """ + + if not self.ident: + # have not yet been started, so nothing to do + return + + self.stopping = True + self.join() + + self.disconnect() + + def setup_attribute(self, annotation, attribute): + """ + This function is responsible for providing the attribute_wrapper with a read/write function + How this is done is implementation specific. + The setup-attribute has access to the comms_annotation provided to the attribute wrapper to pass along to the comms client + as well as a reference to the attribute itself. + + It should do this by first calling: _setup_annotation and setup_value_conversion to get all data necceacry to configure the read/write functions. + It should then return the read and write functions to the attribute. + + MANDATORY: + annotation_outputs = _setup_annotation(annotation) + attribute_outputs = _setup_annotation(attribute) + (note: outputs are up to the user) + + REQUIRED: provide read and write functions to return, there are no restrictions on how these should be provided, + except that the read function takes a single input value and the write function returns a single value + + MANDATORY: + return read_function, write_function + + Examples: + - File system: get_mapping returns functions that read/write a fixed + number of bytes at a fixed location in a file. (SEEK) + - OPC-UA: traverse the OPC-UA tree until the node is found. + Then return the read/write functions for that node which automatically + convert values between Python and OPC-UA. + """ + raise NotImplementedError("the setup_attribute must be implemented and provide return a valid read/write function for the attribute") + + def _setup_annotation(self, annotation): + """ + This function is responsible for handling the annotation data provided by the attribute to configure the read/write function the client must provide. + This function should be called by setup_attribute + """ + raise NotImplementedError("the _setup_annotation must be implemented, content and outputs are up to the user") + + def setup_value_conversion(self, attribute): + """ + this function is responsible for setting up the value conversion between the client and the attribute. + This function should be called by setup_attribute + """ + raise NotImplementedError("the setup_value_conversion must be implemented, content and outputs are up to the user") diff --git a/devices/src/hardware_device.py b/devices/src/hardware_device.py index 99e86b0c5db611507aa9138e222721c8ec720736..a65f91287027608057657254693c01c2765270bc 100644 --- a/devices/src/hardware_device.py +++ b/devices/src/hardware_device.py @@ -26,153 +26,153 @@ from src.wrappers import only_in_states class hardware_device(Device): - """ - - **Properties:** - - States are as follows: - INIT = Device is initialising. - STANDBY = Device is initialised, but pends external configuration and an explicit turning on, - ON = Device is fully configured, functional, controls the hardware, and is possibly actively running, - FAULT = Device detected an unrecoverable error, and is thus malfunctional, - OFF = Device is turned off, drops connection to the hardware, - - The following state transitions are implemented: - boot -> OFF: Triggered by tango. Device will be instantiated, - OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), - INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, - STANDBY -> ON: Triggered by user. Device reports to be functional, - * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, - * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, - * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, - FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, - - The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()). - """ - - @classmethod - def attr_list(cls): - """ 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()} - - @log_exceptions() - def init_device(self): - """ Instantiates the device in the OFF state. """ - - # NOTE: Will delete_device first, if necessary - Device.init_device(self) - - self.set_state(DevState.OFF) - - # -------- - # Commands - # -------- - - @command() - @only_in_states([DevState.FAULT, DevState.OFF]) - @DebugIt() - def Initialise(self): - """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. - - :return:None - """ - self.set_state(DevState.INIT) - self.setup_value_dict() - self.initialise() - self.standby() - self.set_state(DevState.STANDBY) - - @only_in_states([DevState.INIT]) - def Standby(self): - """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. - - :return:None - """ - - self.standby() - self.set_state(DevState.STANDBY) - - @command() - @only_in_states([DevState.STANDBY]) - @DebugIt() - def On(self): - """ - Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. - - :return:None - """ - self.on() - self.set_state(DevState.ON) - - @command() - @DebugIt() - def Off(self): - """ - Command to ask for shutdown of this device. - - :return:None - """ - if self.get_state() == DevState.OFF: - # Already off. Don't complain. - return - - # Turn off - self.set_state(DevState.OFF) - - self.off() - - # Turn off again, in case of race conditions through reconnecting - self.set_state(DevState.OFF) - - @command() - @only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY]) - @DebugIt() - def Fault(self): - """ - FAULT state is used to indicate our connection with the OPC-UA server is down. - - This device will try to reconnect once, and transition to the ON state on success. - - If reconnecting fails, the user needs to call Initialise() to retry to restart this device. - - :return:None - """ - self.fault() - self.set_state(DevState.FAULT) - - - # functions that can be overloaded - def fault(self): - pass - def off(self): - pass - def on(self): - pass - def standby(self): - pass - def initialise(self): - pass - - def always_executed_hook(self): - """Method always executed before any TANGO command is executed.""" - pass - - @log_exceptions() - 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.") + """ + + **Properties:** + + States are as follows: + INIT = Device is initialising. + STANDBY = Device is initialised, but pends external configuration and an explicit turning on, + ON = Device is fully configured, functional, controls the hardware, and is possibly actively running, + FAULT = Device detected an unrecoverable error, and is thus malfunctional, + OFF = Device is turned off, drops connection to the hardware, + + The following state transitions are implemented: + boot -> OFF: Triggered by tango. Device will be instantiated, + OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), + INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, + STANDBY -> ON: Triggered by user. Device reports to be functional, + * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, + * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, + * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, + FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, + + The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()). + """ + + @classmethod + def attr_list(cls): + """ 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()} + + @log_exceptions() + def init_device(self): + """ Instantiates the device in the OFF state. """ + + # NOTE: Will delete_device first, if necessary + Device.init_device(self) + + self.set_state(DevState.OFF) + + # -------- + # Commands + # -------- + + @command() + @only_in_states([DevState.FAULT, DevState.OFF]) + @DebugIt() + def Initialise(self): + """ + Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + + :return:None + """ + self.set_state(DevState.INIT) + self.setup_value_dict() + self.initialise() + self.standby() + self.set_state(DevState.STANDBY) + + @only_in_states([DevState.INIT]) + def Standby(self): + """ + Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + + :return:None + """ + + self.standby() + self.set_state(DevState.STANDBY) + + @command() + @only_in_states([DevState.STANDBY]) + @DebugIt() + def On(self): + """ + Command to ask for initialisation of this device. Can only be called in FAULT or OFF state. + + :return:None + """ + self.on() + self.set_state(DevState.ON) + + @command() + @DebugIt() + def Off(self): + """ + Command to ask for shutdown of this device. + + :return:None + """ + if self.get_state() == DevState.OFF: + # Already off. Don't complain. + return + + # Turn off + self.set_state(DevState.OFF) + + self.off() + + # Turn off again, in case of race conditions through reconnecting + self.set_state(DevState.OFF) + + @command() + @only_in_states([DevState.ON, DevState.INIT, DevState.STANDBY]) + @DebugIt() + def Fault(self): + """ + FAULT state is used to indicate our connection with the OPC-UA server is down. + + This device will try to reconnect once, and transition to the ON state on success. + + If reconnecting fails, the user needs to call Initialise() to retry to restart this device. + + :return:None + """ + self.fault() + self.set_state(DevState.FAULT) + + + # functions that can be overloaded + def fault(self): + pass + def off(self): + pass + def on(self): + pass + def standby(self): + pass + def initialise(self): + pass + + def always_executed_hook(self): + """Method always executed before any TANGO command is executed.""" + pass + + @log_exceptions() + 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.") diff --git a/devices/test_device.py b/devices/test_device.py index 77044f3bd8c2d38bb803244a44d06a6b11a28233..ea7c333512b41d0dcb25efd1b1cbb556010f1881 100644 --- a/devices/test_device.py +++ b/devices/test_device.py @@ -26,66 +26,66 @@ __all__ = ["test_device", "main"] class test_device(hardware_device): - # ----------------- - # Device Properties - # ----------------- + # ----------------- + # Device Properties + # ----------------- - OPC_Server_Name = device_property( - dtype='DevString', - ) + OPC_Server_Name = device_property( + dtype='DevString', + ) - OPC_Server_Port = device_property( - dtype='DevULong', - ) + OPC_Server_Port = device_property( + dtype='DevULong', + ) - OPC_Time_Out = device_property( - dtype='DevDouble', - ) + OPC_Time_Out = device_property( + dtype='DevDouble', + ) - # ---------- - # Attributes - # ---------- - bool_scalar_R = attribute_wrapper(comms_annotation="numpy.bool_ type read scalar", datatype=numpy.bool_) - bool_scalar_RW = attribute_wrapper(comms_annotation="numpy.bool_ type read/write scalar", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) + # ---------- + # Attributes + # ---------- + bool_scalar_R = attribute_wrapper(comms_annotation="numpy.bool_ type read scalar", datatype=numpy.bool_) + bool_scalar_RW = attribute_wrapper(comms_annotation="numpy.bool_ type read/write scalar", datatype=numpy.bool_, access=AttrWriteType.READ_WRITE) - int64_spectrum_R = attribute_wrapper(comms_annotation="numpy.int64 type read spectrum (len = 8)", datatype=numpy.int64, dims=(8,)) - str_spectrum_RW = attribute_wrapper(comms_annotation="numpy.str type read/write spectrum (len = 8)", datatype=numpy.str_, dims=(8,), access=AttrWriteType.READ_WRITE) + int64_spectrum_R = attribute_wrapper(comms_annotation="numpy.int64 type read spectrum (len = 8)", datatype=numpy.int64, dims=(8,)) + str_spectrum_RW = attribute_wrapper(comms_annotation="numpy.str type read/write spectrum (len = 8)", datatype=numpy.str_, dims=(8,), access=AttrWriteType.READ_WRITE) - double_image_R = attribute_wrapper(comms_annotation="numpy.double type read image (dims = 2x8)", datatype=numpy.double, dims=(2, 8)) - double_image_RW = attribute_wrapper(comms_annotation="numpy.double type read/write image (dims = 8x2)", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE) + double_image_R = attribute_wrapper(comms_annotation="numpy.double type read image (dims = 2x8)", datatype=numpy.double, dims=(2, 8)) + double_image_RW = attribute_wrapper(comms_annotation="numpy.double type read/write image (dims = 8x2)", datatype=numpy.double, dims=(8, 2), access=AttrWriteType.READ_WRITE) - int32_scalar_R = attribute_wrapper(comms_annotation="numpy.int32 type read scalar", datatype=numpy.int32) - uint16_spectrum_RW = attribute_wrapper(comms_annotation="numpy.uint16 type read/write spectrum (len = 8)", datatype=numpy.uint16, dims=(8,), access=AttrWriteType.READ_WRITE) - 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) + int32_scalar_R = attribute_wrapper(comms_annotation="numpy.int32 type read scalar", datatype=numpy.int32) + uint16_spectrum_RW = attribute_wrapper(comms_annotation="numpy.uint16 type read/write spectrum (len = 8)", datatype=numpy.uint16, dims=(8,), access=AttrWriteType.READ_WRITE) + 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) - # -------- - # overloaded functions - # -------- - def initialise(self): - """ user code here. is called when the sate is set to INIT """ - """Initialises the attributes and properties of the PCC.""" + # -------- + # overloaded functions + # -------- + def initialise(self): + """ user code here. is called when the sate is set to INIT """ + """Initialises the attributes and properties of the PCC.""" - self.set_state(DevState.INIT) + self.set_state(DevState.INIT) - #set up the OPC ua client - self.example_client = example_client(self.Standby, self.Fault, self) + #set up the OPC ua client + self.example_client = example_client(self.Standby, self.Fault, self) - # map an access helper class - for i in self.attr_list(): - i.set_comm_client(self.example_client) + # map an access helper class + for i in self.attr_list(): + i.set_comm_client(self.example_client) - self.example_client.start() + self.example_client.start() # ---------- # Run server # ---------- def main(args=None, **kwargs): - """Main function of the example module.""" - return run((test_device,), args=args, **kwargs) + """Main function of the example module.""" + return run((test_device,), args=args, **kwargs) if __name__ == '__main__': - main() + main()