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()