diff --git a/tangostationcontrol/docs/source/devices/recv.rst b/tangostationcontrol/docs/source/devices/recv.rst index 847f8bb51f7f52850e66b0d25c01269b9961e726..00bbd89ef2e748b120e34143c8e6cc4cb5763887 100644 --- a/tangostationcontrol/docs/source/devices/recv.rst +++ b/tangostationcontrol/docs/source/devices/recv.rst @@ -13,3 +13,28 @@ The ``recv == DeviceProxy("STAT/RECV/1")`` device controls the RCUs, the LBA ant Typically, ``N_RCUs == 32``, and ``N_antennas_per_RCU == 3``. +Error information +--------------------- + +These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors: + +:RCU_error_R: Whether the RCUs appear usable. + + :type: ``bool[N_RCUs]`` + +:ANT_error_R: Whether the antennas appear usable. + + :type: ``bool[N_RCUs][N_antennas_per_RCU]`` + +:RCU_IOUT_error_R: Whether there are alarms on any of the amplitudes in the measured currents. + + :type: ``bool[N_RCUs]`` + +:RCU_VOUT_error_R: Whether there are alarms on any of the voltages in the measured currents. + + :type: ``bool[N_RCUs]`` + +:RCU_TEMP_error_R: Whether there are alarms on any of the temperatures. NB: These values are also exposed for unused RCUs (the ``RCU_mask_RW`` is ignored). + + :type: ``bool[N_RCUs]`` + diff --git a/tangostationcontrol/docs/source/devices/sdp.rst b/tangostationcontrol/docs/source/devices/sdp.rst index 2ca1ea6fa95e7295a21041d12242e15cec3b8001..6386dd851b178ce44b0b1b53968ba0f219fe5140 100644 --- a/tangostationcontrol/docs/source/devices/sdp.rst +++ b/tangostationcontrol/docs/source/devices/sdp.rst @@ -30,16 +30,29 @@ The following points are significant for the operations of this device: Data-quality information --------------------------- -The following fields describe the data quality: +The following fields describe the data quality (see also :doc:`../signal_chain`): :FPGA_signal_input_mean_R: Mean value of the last second of input (in ADC quantisation units). Should be close to 0. :type: ``double[N_fpgas][N_ants_per_fpga]`` -:FPGA_signal_input_rms_R: Root means square value of the last second of input (in ADC quantisation units). ``rms^2 = mean^2 + std^2``. Values above 2048 indicate strong RFI. +:FPGA_signal_input_rms_R: Root means square value of the last second of input (in ADC quantisation units). ``rms^2 = mean^2 + std^2``. Values above 2048 indicate strong RFI. Values of 0 indicate a lack of signal input. :type: ``double[N_fpgas][N_ants_per_fpga]`` +Error information +--------------------- + +These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors: + +:FPGA_error_R: Whether the FPGAs appear usable. + + :type: ``bool[N_fpgas]`` + +:FPGA_procesing_error_R: Whether the FPGAs are processing their input from the RCUs. NB: This will also raise an error if the Waveform Generator is enabled. + + :type: ``bool[N_fpgas]`` + Version Information --------------------- diff --git a/tangostationcontrol/docs/source/devices/using.rst b/tangostationcontrol/docs/source/devices/using.rst index 7aef380071836e7cd6ece9d7202f04658e68a99a..825ee74d83b5a7ad8a8e18c8813ee1ace81f5459 100644 --- a/tangostationcontrol/docs/source/devices/using.rst +++ b/tangostationcontrol/docs/source/devices/using.rst @@ -59,13 +59,12 @@ typically involves the following sequence of commands:: # turn the device off completely first. device.off() - # setup any connections and threads - device.initialise() + # turn on the device and fully reinitialise it + # alternatively, device.warm_boot() can be used, + # in which case no hardware is reinitialised. + device.boot() - # turn on the device - device.on() - -Of course, the device could go into ``FAULT`` again, even during the ``initialise()`` command, for example because the hardware it manages is unreachable. To debug the fault condition, check the :doc:`../interfaces/logs` of the device in question. +Of course, the device could go into ``FAULT`` again, even during the ``boot()`` command, for example because the hardware it manages is unreachable. To debug the fault condition, check the :doc:`../interfaces/logs` of the device in question. Initialise hardware ```````````````````` @@ -74,6 +73,10 @@ Most devices provide the following commands, in order to configure the hardware :initialise(): Initialise the device (connect to the hardware). Moves from ``OFF`` to ``STANDBY``. +:set_translator_defaults(): Select the hardware to configure and monitor. + +:prepare_hardware(): For devices that control hardware, this command prepares the hardware to accept commands (f.e. load firmware). + :set_defaults(): Upload default attribute settings from the TangoDB to the hardware. :initialise_hardware(): For devices that control hardware, this command runs the hardware initialisation procedure. @@ -145,4 +148,3 @@ For example, the ``RCU_mask_RW`` array is the RCU mask in the ``recv`` device. I # <--- only LED0 on RCU3 is now on # recv.RCU_LED0_R should show this, # if you have the RCU hardware installed. - diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index a53fdfcfd8b61167789a4ed2a05728379d026e61..82991eb261a1f397cff582d240f0ae6b499e3049 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -62,7 +62,10 @@ class lofar_device(Device, metaclass=DeviceMeta): version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version()) - # list of property names too be set first by set_defaults + # list of translator property names to be set by set_translator_defaults + translator_default_settings = [] + + # list of hardware property names to be set first by set_defaults first_default_settings = [] @classmethod @@ -224,6 +227,27 @@ class lofar_device(Device, metaclass=DeviceMeta): """Method always executed before any TANGO command is executed.""" pass + def _set_defaults(self, attribute_names: list): + """ Set hardware points to their default value. + + attribute_names: The names of the attributes to set to their default value. + + A hardware point XXX is set to the value of the object member named XXX_default, if it exists. + XXX_default can be f.e. a constant, or a device_property. + """ + + # set them all + for name in attribute_names: + try: + default_value = getattr(self, f"{name}_default") + + # set the attribute to the configured default + logger.debug(f"Setting attribute {name} to {default_value}") + self.proxy.write_attribute(name, default_value) + except Exception as e: + # log which attribute we're addressing + raise Exception(f"Cannot assign default to attribute {name}") from e + def properties_changed(self): pass @@ -240,7 +264,7 @@ class lofar_device(Device, metaclass=DeviceMeta): The points are set in the following order: 1) The python class property 'first_default_settings' is read, as an array of strings denoting property names. Each property is set in that order. - 2) Any remaining default properties are set. + 2) Any remaining default properties are set, except the translators (those in 'translator_default_settings'). """ # collect all attributes for which defaults are provided @@ -248,22 +272,33 @@ class lofar_device(Device, metaclass=DeviceMeta): # collect all attribute members if isinstance(getattr(self, name), Attribute) # with a default set - and hasattr(self, f"{name}_default")] + and hasattr(self, f"{name}_default") + and name not in self.translator_default_settings] # determine the order: first do the ones mentioned in default_settings_order attributes_to_set = self.first_default_settings + [name for name in attributes_with_defaults if name not in self.first_default_settings] - # set them all - for name in attributes_to_set: - try: - default_value = getattr(self, f"{name}_default") + # set them + self._set_defaults(attributes_to_set) - # set the attribute to the configured default - logger.debug(f"Setting attribute {name} to {default_value}") - self.proxy.write_attribute(name, default_value) - except Exception as e: - # log which attribute we're addressing - raise Exception(f"Cannot assign default to attribute {name}") from e + + @only_in_states([DevState.STANDBY, DevState.INIT, DevState.ON]) + @fault_on_error() + @command() + def set_translator_defaults(self): + """ Initialise the translator translators to their configured settings. """ + + # This is just the command version of _set_translator_defaults(). + self._set_translator_defaults() + + @only_in_states([DevState.STANDBY, DevState.INIT, DevState.ON]) + @fault_on_error() + @command() + def prepare_hardware(self): + """ Load firmware required before configuring anything. """ + + # This is just the command version of _prepare_hardware(). + self._prepare_hardware() @only_in_states([DevState.STANDBY, DevState.INIT, DevState.ON]) @fault_on_error() @@ -278,7 +313,12 @@ class lofar_device(Device, metaclass=DeviceMeta): # setup connections self.Initialise() + self.set_translator_defaults() + if initialise_hardware: + # prepare hardware to accept settings + self.prepare_hardware() + # initialise settings self.set_defaults() @@ -288,7 +328,6 @@ class lofar_device(Device, metaclass=DeviceMeta): # make device available self.On() - @only_in_states([DevState.OFF]) @command() def boot(self): @@ -299,6 +338,15 @@ class lofar_device(Device, metaclass=DeviceMeta): def warm_boot(self): self._boot(initialise_hardware=False) + def _set_translator_defaults(self): + """ Initialise any translators to their default settings. """ + + self._set_defaults(self.translator_default_settings) + + def _prepare_hardware(self): + """ Override this method to load any firmware before configuring the hardware. """ + pass + def _initialise_hardware(self): """ Override this method to initialise any hardware after configuring it. """ pass @@ -309,13 +357,21 @@ class lofar_device(Device, metaclass=DeviceMeta): Raises an Exception if it has not after the timeout. + value: The value that needs to be matched, or a function + that needs to evaluate to True given the attribute. timeout: time until an Exception is raised, in seconds. pollperiod: how often to check the attribute, in seconds. """ + if type(value) == type(lambda x: True): + # evaluate function + is_correct = value + else: + # compare to value + is_correct = lambda x: x == value # Poll every half a second for _ in range(math.ceil(timeout/pollperiod)): - if getattr(self.proxy, attr_name) != value: + if is_correct(getattr(self.proxy, attr_name)): return time.sleep(pollperiod) @@ -335,7 +391,7 @@ class lofar_device(Device, metaclass=DeviceMeta): # fetch attribute value as an array value = self.proxy.read_attribute(attr_name).value if is_scalar: - value = numpy.array(value) + value = numpy.array(value) # this stays a scalar in numpy # construct alarm state, in the same shape as the attribute alarm_state = numpy.zeros(value.shape, dtype=bool) @@ -346,9 +402,6 @@ class lofar_device(Device, metaclass=DeviceMeta): if alarms.min_alarm != 'Not specified': alarm_state |= value <= value.dtype.type(alarms.min_alarm) - # return alarm state, as the same type as the attribute - if is_scalar: - return alarm_state[0] - else: - return alarm_state + # return alarm state, with the same dimensions as the attribute + return alarm_state diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index f2acb0794d294eece593ad247b5b15b19c0ee4ad..db29fe62c791a6b75744954f1f45090b7f777414 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -109,8 +109,7 @@ class RECV(opcua_device): default_value = 2015.5 ) - first_default_settings = [ - # set the masks first, as those filter any subsequent settings + translator_default_settings = [ 'ANT_mask_RW', 'RCU_mask_RW' ] diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py index 93e9077f07580f79a3f06e89d81a4e5fe42ae7c8..6cacb14de48f067836bf7b480624995ae1667247 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -89,8 +89,7 @@ class SDP(opcua_device): default_value=[[8192] * 12 * 512] * 16 ) - first_default_settings = [ - # set the masks first, as those filter any subsequent settings + translator_default_settings = [ 'TR_fpga_mask_RW' ] @@ -109,8 +108,8 @@ class SDP(opcua_device): FPGA_beamlet_output_scale_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_R"], datatype=numpy.uint32, dims=(16,)) FPGA_beamlet_output_scale_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_firmware_version_R = attribute_wrapper(comms_annotation=["FPGA_firmware_version_R"], datatype=numpy.str, dims=(16,)) - FPGA_reboot_R = attribute_wrapper(comms_annotation=["FPGA_reboot_R"], datatype=numpy.uint32, dims=(16,), doc="Active FPGA image (0=factory, 1=user)") - FPGA_reboot_RW = attribute_wrapper(comms_annotation=["FPGA_reboot_R"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_boot_image_R = attribute_wrapper(comms_annotation=["FPGA_reboot_R"], datatype=numpy.uint32, dims=(16,), doc="Active FPGA image (0=factory, 1=user)") + FPGA_boot_image_RW = attribute_wrapper(comms_annotation=["FPGA_reboot_R"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) FPGA_global_node_index_R = attribute_wrapper(comms_annotation=["FPGA_global_node_index_R"], datatype=numpy.uint32, dims=(16,)) FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["FPGA_hardware_version_R"], datatype=numpy.str, dims=(16,)) FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["FPGA_processing_enable_R"], datatype=numpy.bool_, dims=(16,)) @@ -223,14 +222,23 @@ class SDP(opcua_device): def read_FPGA_processing_error_R(self): return self.proxy.TR_fpga_mask_RW & ( ~self.proxy.FPGA_processing_enable_R - | (self.proxy.FPGA_reboot_R == 0) - | ~self.proxy.FPGA_wg_enable_R.any(axis=1) + | (self.proxy.FPGA_boot_image_R == 0) + | self.proxy.FPGA_wg_enable_R.any(axis=1) + | (self.proxy.FPGA_signal_input_rms_R == 0) ) # -------- # overloaded functions # -------- + def _prepare_hardware(self): + # FPGAs need the correct firmware loaded + self.FPGA_boot_image_RW = [1] * N_pn + + # wait for the firmware to be loaded (ignoring masked out elements) + mask = self.proxy.TR_fpga_mask_RW + self.wait_attribute("FPGA_boot_image_R", lambda attr: (attr == 1) | ~mask, 10) + # -------- # Commands # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py index a49b120bc6f3502b7d836b86ca68e8ec0b2df9b8..0b2d5c48256405a83b6de67e6e39103f76d044bd 100644 --- a/tangostationcontrol/tangostationcontrol/devices/unb2.py +++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py @@ -59,8 +59,7 @@ class UNB2(opcua_device): default_value=[True] * 2 ) - first_default_settings = [ - # set the masks first, as those filter any subsequent settings + translator_default_settings = [ 'UNB2_mask_RW' ]