diff --git a/tangostationcontrol/docs/source/broken_hardware.rst b/tangostationcontrol/docs/source/broken_hardware.rst new file mode 100644 index 0000000000000000000000000000000000000000..7047d324ed7085122225a6e07ca67e830835b876 --- /dev/null +++ b/tangostationcontrol/docs/source/broken_hardware.rst @@ -0,0 +1,47 @@ +Broken Hardware +-------------------- + +Not all hardware is always functional. Broken hardware must be excluded from the signal chain, and in some cases prevented from powering up. + +Disabling antennas +`````````````````````````` + +Not all antennas present in the field are to be used. The AntennaField device exposes the following properties for each of its antennas: + +:Antenna_Quality: The condition of the antenna: 0=OK, 1=SUSPICIOUS, 2=BROKEN, 3=BEYOND_REPAIR. + + :type: ``int32[]`` + +:Antenna_Use: Whether each antenna should be used: 0=AUTO, 1=ON, 2=OFF. In AUTO mode, an antenna is used if its quality is OK or SUSPICIOUS. In ON mode, it is always used. In OFF mode, never. + + :type: ``int32[]`` + +which can also be queried as ``Antenna_Quality_R`` and ``Antenna_Use_R``. + +.. note:: If these properties are updated, you should restart both the AntennaField and DigitalBeam device to propagate their effects. + +The above settings result in a subset of the antennas in the AntennaField to be marked as usable. The following property exposes this conclusion: + +:Antenna_Usage_Mask_R: Whether antennas will be used, according to their configured state and quality. Antennas which are configured to be BROKEN, BEYOND_REPAIR, or OFF, are not used. + + :type: ``bool[N_tiles]`` + +Effect on signal chain +"""""""""""""""""""""""""" + +The DigitalBeam device will only beamform inputs that are enabled in the ``AntennaField.Antenna_Usage_Mask_R`` attribute. + + +Power distribution +-------------------------- + +At boot, during hardware initialisation, the following devices toggle power: + +* The RECV device turns all RCUs enabled in ``RCU_mask_RW`` OFF and ON, +* The RECV device powers its antennas according to its ``RCU_PWR_ANT_on_RW_default`` property, +* The AntennaField device powers its antennas, if they are: + * Enabled in ``Antenna_Usage_Mask_R`` attribute, that is, not marked as BROKEN, BEYOND_REPAIR, or OFF, + * Enabled in the ``Antenna_Needs_Power`` property. + +.. note:: Exotic inputs like a noise source must not receive power, even when used. Use the ``Antenna_Needs_Power`` property to configure which antennas should be powered on. + diff --git a/tangostationcontrol/docs/source/devices/antennafield.rst b/tangostationcontrol/docs/source/devices/antennafield.rst index bcf93f62e9d40aa4d48e24c68eb357433216542d..faede195aec38828f2c1d1d6b71d2bdb928ef6eb 100644 --- a/tangostationcontrol/docs/source/devices/antennafield.rst +++ b/tangostationcontrol/docs/source/devices/antennafield.rst @@ -46,13 +46,19 @@ The antennas represented by the antenna field are selected by the following prop :type: ``str[]`` -:Power_to_RECV_mapping: Pairs of numbers ``(recv_idx, ant_idx)`` describing the inputs on which the Antenna *power* is connected. The ``recv_idx`` is the index in ``RECV_devices``, starting at 1. The ``ant_idx`` is the absolute index of the antenna in the ``RECV`` device. A value of ``-1`` means the antenna is not connected at all. +Antenna mapping +"""""""""""""""""""" + +These properties configure which inputs in RECV represent the power and control for each antenna: + +:HBAT_Power_to_RECV_mapping: Pairs of numbers ``(recv_idx, ant_idx)`` describing the inputs on which the HBAT *power* is connected. The ``recv_idx`` is the index in ``RECV_devices``, starting at 1. The ``ant_idx`` is the absolute index of the antenna in the ``RECV`` device. A value of ``-1`` means the antenna is not connected at all. :type: ``int32[]`` :shape: ``int32[][2]`` :Control_to_RECV_mapping: Pairs of numbers ``(recv_idx, ant_idx)`` describing the inputs on which the Antenna *control* is connected. The ``recv_idx`` is the index in ``RECV_devices``, starting at 1. The ``ant_idx`` is the absolute index of the antenna in the ``RECV`` device. A value of ``-1`` means the antenna is not connected at all. + Positions """""""""""""""""""" diff --git a/tangostationcontrol/docs/source/devices/using.rst b/tangostationcontrol/docs/source/devices/using.rst index 053d73bda33703c187f6bd9de9d7d84194647254..57d2f255ed2fdd673e8996492a8b2ff434960387 100644 --- a/tangostationcontrol/docs/source/devices/using.rst +++ b/tangostationcontrol/docs/source/devices/using.rst @@ -109,12 +109,10 @@ Most devices provide the following commands, in order to configure the hardware :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). +:prepare_hardware(): For devices that control hardware, this command prepares the hardware to accept commands (f.e. power cycle, 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. - :on(): Mark the device as operational. Moves from ``STANDBY`` to ``ON``. See also :ref:`boot`, which provides functionality to initialise all the devices. diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst index 6bd156c2b7f9d3cda99ad3af8a7e01bbc33c45c8..4c56206dfce3ab37b07f103a963d06846ccd8f12 100644 --- a/tangostationcontrol/docs/source/index.rst +++ b/tangostationcontrol/docs/source/index.rst @@ -34,6 +34,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st devices/configure configure_station signal_chain + broken_hardware developer faq diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py index 25b58719c287c4a6fe6e4b690f0228187ac17989..565f979539a3a70f538f39f880cd7efeae0102e4 100644 --- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -110,6 +110,15 @@ class AntennaField(lofar_device): default_value = numpy.array([AntennaUse.AUTO] * MAX_NUMBER_OF_HBAT) ) + # ----- Antenna properties + + Antenna_Needs_Power = device_property( + doc="Whether to provide power to each antenna (False for noise sources)", + dtype='DevVarBooleanArray', + mandatory=False, + default_value = numpy.array([False] * MAX_NUMBER_OF_HBAT) + ) + # ----- Position information Antenna_Field_Reference_ITRF = device_property( @@ -149,7 +158,6 @@ class AntennaField(lofar_device): mandatory=False, default_value = 2015.5 ) - HBAT_PQR_rotation_angles_deg = device_property( doc='Rotation of each tile in the PQ plane ("horizontal") in degrees.', dtype='DevVarFloatArray', @@ -206,14 +214,21 @@ class AntennaField(lofar_device): Antenna_Names_R = attribute(access=AttrWriteType.READ, dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT) - Antenna_Quality_R = attribute(access=AttrWriteType.READ, - dtype=(numpy.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) - Antenna_Use_R = attribute(access=AttrWriteType.READ, - dtype=(numpy.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) - Antenna_Usage_Mask_R = attribute(access=AttrWriteType.READ, + Antenna_Quality_R = attribute(doc='The quality of each antenna. 0=OK, 1=SUSPICIOUS, 2=BROKEN, 3=BEYOND_REPAIR.', + dtype=(numpy.uint32,), max_dim_x=MAX_NUMBER_OF_HBAT) + Antenna_Use_R = attribute(doc='Whether each antenna should be used. 0=AUTO, 1=ON, 2=OFF. In AUTO mode, the antenna is used if it is not BROKEN or BEYOND_REPAIR.', + dtype=(numpy.uint32,), max_dim_x=MAX_NUMBER_OF_HBAT) + Antenna_Quality_str_R = attribute(doc='The quality of each antenna, as a string.', + dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT) + Antenna_Use_str_R = attribute(doc='Whether each antenna should be used, as a string.', + dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT) + + Antenna_Usage_Mask_R = attribute(doc='Whether each antenna will be used.', dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT) - ANT_mask_RW = mapped_attribute("ANT_mask_RW", dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + ANT_mask_RW = mapped_attribute("ANT_mask_RW", dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) + RCU_PWR_ANT_on_R = mapped_attribute("RCU_PWR_ANT_on_R", dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT) + RCU_PWR_ANT_on_RW = mapped_attribute("RCU_PWR_ANT_on_RW", dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) HBAT_BF_delay_steps_R = mapped_attribute("HBAT_BF_delay_steps_R", dtype=((numpy.int64,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=MAX_NUMBER_OF_HBAT) HBAT_BF_delay_steps_RW = mapped_attribute("HBAT_BF_delay_steps_RW", dtype=((numpy.int64,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=MAX_NUMBER_OF_HBAT, access=AttrWriteType.READ_WRITE) HBAT_LED_on_R = mapped_attribute("HBAT_LED_on_R", dtype=((bool,),), max_dim_x=NUMBER_OF_ELEMENTS_PER_TILE * 2, max_dim_y=MAX_NUMBER_OF_HBAT) @@ -266,19 +281,29 @@ class AntennaField(lofar_device): def read_Antenna_Quality_R(self): return self.Antenna_Quality + + def read_Antenna_Use_str_R(self): + return [AntennaUse(x).name for x in self.Antenna_Use] + def read_Antenna_Quality_str_R(self): + return [AntennaQuality(x).name for x in self.Antenna_Quality] + def read_Antenna_Usage_Mask_R(self): - antenna_usage = numpy.zeros(MAX_NUMBER_OF_HBAT, dtype=bool) - for n in range(0, MAX_NUMBER_OF_HBAT): - antenna_usage[n] = (self.read_attribute('Antenna_Use_R')[n] == AntennaUse.ON - or (self.read_attribute('Antenna_Use_R')[n] == AntennaUse.AUTO and self.read_attribute('Antenna_Quality_R')[n] <= AntennaQuality.SUSPICIOUS)) - return antenna_usage + use = numpy.array(self.Antenna_Use) + quality = numpy.array(self.Antenna_Quality) + + antennas_forced_on = use == AntennaUse.ON + antennas_auto_on = numpy.logical_and(use == AntennaUse.AUTO, quality <= AntennaQuality.SUSPICIOUS) + + return numpy.logical_or(antennas_forced_on, antennas_auto_on) def read_nr_antennas_R(self): # The number of antennas should be equal to: # * the number of elements in the Control_to_RECV_mapping (after reshaping), # * the number of elements in the Power_to_RECV_mapping (after reshaping), # * the number of antennas exposed through Antenna_Reference_ITRF_R. + # * the number of elements in Antenna_Use + # * the number of elements in Antenna_Quality # # Parsing a property here is quickest, so we chose that. return len(self.Control_to_RECV_mapping) // 2 @@ -388,31 +413,15 @@ class AntennaField(lofar_device): @log_exceptions() def _prepare_hardware(self): - # Initialise the RCU hardware. - for recv_proxy in self.recv_proxies: - RCU_mask = recv_proxy.RCU_mask_RW - # Set the mask to all Trues - recv_proxy.RCU_mask_RW = [True] * 32 - # Turn off the RCUs - recv_proxy.RCU_off() - - # TODO(Stefano): restore wait attribute - #recv_proxy.wait_attribute("RECVTR_translator_busy_R", False, recv_proxy.RCU_On_Off_timeout) - - # Restore the mask - recv_proxy.RCU_mask_RW = RCU_mask - # Turn on the RCUs - recv_proxy.RCU_on() + usage_mask = self.read_attribute('Antenna_Usage_Mask_R') - # TODO(Stefano): restore wait attribute - #recv_proxy.wait_attribute("RECVTR_translator_busy_R", False, recv_proxy.RCU_On_Off_timeout) - - @log_exceptions() - def _initialise_hardware(self): # Disable controlling the tiles that fall outside the mask # WARN: Needed in configure_for_initialise but Tango does not allow to write attributes in INIT state self.proxy.write_attribute('ANT_mask_RW', self.read_attribute('Antenna_Usage_Mask_R')) + # Turn on power to antennas that need it (and due to the ANT_mask, that we're using) + self.proxy.write_attribute('RCU_PWR_ANT_on_RW', self.Antenna_Needs_Power) + # -------- # Commands # -------- @@ -452,6 +461,8 @@ class AntennaToRecvMapper(object): self.__number_of_receivers = number_of_receivers self.__default_value_mapping_read = { "ANT_mask_RW": numpy.full(number_of_antennas, False), + "RCU_PWR_ANT_on_R": numpy.full(number_of_antennas, False), + "RCU_PWR_ANT_on_RW": numpy.full(number_of_antennas, False), "HBAT_BF_delay_steps_R": numpy.zeros([number_of_antennas,32], dtype=numpy.int64), "HBAT_BF_delay_steps_RW": numpy.zeros([number_of_antennas,32], dtype=numpy.int64), "HBAT_LED_on_R": numpy.full((number_of_antennas,32), False), @@ -464,6 +475,7 @@ class AntennaToRecvMapper(object): } self.__default_value_mapping_write = { "ANT_mask_RW": numpy.full(96, False), + "RCU_PWR_ANT_on_RW": numpy.full(96, False), "HBAT_BF_delay_steps_RW": numpy.zeros([96,32], dtype=numpy.int64), "HBAT_LED_on_RW": numpy.full((96,32), False), "HBAT_PWR_LNA_on_RW": numpy.full((96,32), False), diff --git a/tangostationcontrol/tangostationcontrol/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py index 60563f1138c125c79274b1c2f5342b7df68c02e8..f2bf1fa4d7af436807fd74c9649627ffa4593bb3 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apsct.py +++ b/tangostationcontrol/tangostationcontrol/devices/apsct.py @@ -52,6 +52,10 @@ class APSCT(opcua_device): default_value=10.0 ) + TRANSLATOR_DEFAULT_SETTINGS = [ + 'APSCTTR_monitor_rate_RW' + ] + # ---------- # Attributes # ---------- @@ -118,7 +122,7 @@ class APSCT(opcua_device): # overloaded functions # -------- - def _initialise_hardware(self): + def _prepare_hardware(self): """ Initialise the APSCT hardware. """ # Cycle clock diff --git a/tangostationcontrol/tangostationcontrol/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py index 08c43f5a4362b22bd15ab0065e243f741add98ae..70536471bf6238e009c258444956b440b2d6cf23 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apspu.py +++ b/tangostationcontrol/tangostationcontrol/devices/apspu.py @@ -37,6 +37,10 @@ class APSPU(opcua_device): default_value=1 ) + TRANSLATOR_DEFAULT_SETTINGS = [ + 'APSPUTR_monitor_rate_RW' + ] + # ---------- # Attributes # ---------- diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index 665caca7a4d0439e0a2195d9939210229b71cdb0..c04033f343e5ffb118fb31931b60262a6279c9d1 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -342,16 +342,6 @@ class lofar_device(Device, metaclass=DeviceMeta): # This is just the command version of _prepare_hardware(). self._prepare_hardware() - - @only_in_states(DEFAULT_COMMAND_STATES) - @fault_on_error() - @command() - @DebugIt() - def initialise_hardware(self): - """ Initialise the hardware after configuring it. """ - - # This is just the command version of _initialise_hardware(). - self._initialise_hardware() @only_in_states(INITIALISED_STATES) @fault_on_error() @@ -391,9 +381,6 @@ class lofar_device(Device, metaclass=DeviceMeta): # initialise settings self.set_defaults() - # powercycle backing hardware - self.initialise_hardware() - # make device available self.On() @@ -413,11 +400,7 @@ class lofar_device(Device, metaclass=DeviceMeta): 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. """ + """ Override this method to load any firmware or power cycle components before configuring the hardware. """ pass def _disable_hardware(self): diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py index 05d225a4b9d62f7f7190615f4d71f5e34639ab4c..d2b22f4a91b211792b1bc9a08f32c590db6292ef 100644 --- a/tangostationcontrol/tangostationcontrol/devices/observation.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation.py @@ -42,7 +42,7 @@ class Observation(lofar_device): """ NUM_MAX_HBAT = MAX_NUMBER_OF_HBAT - NUM_INPUTS = DigitalBeam.NUM_INPUTS + MAX_INPUTS = DigitalBeam.MAX_INPUTS NUM_BEAMLETS = DigitalBeam.NUM_BEAMLETS # Attributes @@ -52,7 +52,7 @@ class Observation(lofar_device): stop_time_R = attribute(dtype=numpy.float64, access=AttrWriteType.READ) antenna_mask_R = attribute(dtype=(numpy.int64,), max_dim_x=NUM_MAX_HBAT, access=AttrWriteType.READ) filter_R = attribute(dtype=numpy.str, access=AttrWriteType.READ) - saps_subband_R = attribute(dtype=((numpy.uint32,),), max_dim_x=NUM_INPUTS, max_dim_y=NUM_BEAMLETS, access=AttrWriteType.READ) + saps_subband_R = attribute(dtype=((numpy.uint32,),), max_dim_x=MAX_INPUTS, max_dim_y=NUM_BEAMLETS, access=AttrWriteType.READ) saps_pointing_R = attribute(dtype=((numpy.str,),), max_dim_x=3, max_dim_y=NUM_BEAMLETS, access=AttrWriteType.READ) tile_beam_R = attribute(dtype=(numpy.str,), max_dim_x=3, access=AttrWriteType.READ) first_beamlet_R = attribute(dtype=numpy.int64, access=AttrWriteType.READ) @@ -67,6 +67,8 @@ class Observation(lofar_device): self._observation_id = -1 self._stop_time = datetime.now() + self._num_inputs = 0 + def configure_for_initialise(self): """Load the JSON from the attribute and configure member variables""" @@ -113,6 +115,9 @@ class Observation(lofar_device): self.tilebeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Tilebeam/1") self.tilebeam_proxy.set_source(DevSource.DEV) + # Collect static information + self._num_inputs = self.digitalbeam_proxy.antenna_select_RW.shape[0] + logger.info( f"The observation with ID={self._observation_id} is " "configured. It will begin as soon as On() is called and it is" @@ -268,7 +273,7 @@ class Observation(lofar_device): def _apply_saps_antenna_select(self, antenna_mask:list): """ Convert an array of antenna indexes into a boolean select array""" - antenna_select = numpy.array([[False] * self.NUM_BEAMLETS] * self.NUM_INPUTS) + antenna_select = numpy.array([[False] * self.NUM_BEAMLETS] * self._num_inputs) first_beamlet = numpy.array(self.read_first_beamlet_R(), dtype=numpy.int64) for a in antenna_mask: for i in range(first_beamlet, self.NUM_BEAMLETS): diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index 037b8adb69fa2515f5a3e2dbaa4ed00c8a2ab43b..aeaa506c3d0cca422426db7316dd095bc34e7de6 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -70,7 +70,7 @@ class RECV(opcua_device): ) RCU_PWR_ANT_on_RW = device_property( - dtype='DevVarLong64Array', + dtype='DevVarBooleanArray', mandatory=False, default_value=[False] * 96 # turn power off by default in test setups, f.e. to prevent blowing up the noise sources ) @@ -83,7 +83,8 @@ class RECV(opcua_device): TRANSLATOR_DEFAULT_SETTINGS = [ 'ANT_mask_RW', - 'RCU_mask_RW' + 'RCU_mask_RW', + 'RECVTR_monitor_rate_RW' ] # ----- Timing values @@ -236,7 +237,7 @@ class RECV(opcua_device): # in positive delays regardless of the pointing direction. self.HBAT_bf_delay_offset = numpy.mean(self.HBAT_bf_delay_step_delays) - def _initialise_hardware(self): + def _prepare_hardware(self): """ Initialise the RCU hardware. """ # Cycle RCUs diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py index 8c190ddfbb48388dcc6ac10fb4dcccb97ed1ec26..93c91ef791fff255aa2d56e1e1e5c34cdd0f3dbf 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py @@ -37,9 +37,13 @@ class DigitalBeam(beam_device): * antennas: the elements in the AntennaField that feeds the FPGA, * inputs: the antenna slots of the FPGAs (covering both X and Y as one), * polarised_inputs: all the input slots of the FPGAs (separating X and Y). + + The antennas are drawn from an AntennaField device. Only the antennas enabled + in the AntennaField's Antenna_Usage_Mask will be used in beamforming. Those + disabled in the mask will get a weight of 0. """ - NUM_INPUTS = 96 + MAX_INPUTS = 96 NUM_BEAMLETS = 488 # ----------------- @@ -64,7 +68,7 @@ class DigitalBeam(beam_device): dtype=(numpy.int32,), doc='Which antenna of the antennafield is connected to each input. -1 if no antenna is present.', mandatory=False, - default_value = [-1] * NUM_INPUTS + default_value = [-1] * MAX_INPUTS ) # ---------- @@ -74,12 +78,19 @@ class DigitalBeam(beam_device): Duration_delays_R = attribute(access=AttrWriteType.READ, dtype=numpy.float64, fget=lambda self: self._delays.statistics["last"] or 0) - input_select_RW = attribute(doc='Selection of inputs to use for forming each beamlet', - dtype=((bool,),), max_dim_x=NUM_BEAMLETS, max_dim_y=NUM_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") + input_select_RW = attribute(doc='Selection of inputs to use for forming each beamlet. Allows selecting broken antennas.', + dtype=((bool,),), max_dim_x=NUM_BEAMLETS, max_dim_y=MAX_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") + + antenna_select_RW = attribute(doc='Selection of antennas desired to use for forming each beamlet (= a subset of input_select of the configured antennas). Unselects broken antennas.', + dtype=((bool,),), max_dim_x=NUM_BEAMLETS, max_dim_y=MAX_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") - antenna_select_RW = attribute(doc='Selection of antennas to use for forming each beamlet (= a subset of input_select of the configured antennas)', - dtype=((bool,),), max_dim_x=NUM_BEAMLETS, max_dim_y=NUM_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") + nr_inputs_R = attribute(doc='Number of configured inputs from the associated antenna field.', + dtype=numpy.uint32, fget="nr_inputs") + def nr_inputs(self): + """ Return the number of configured inputs. """ + + return max(self.Input_to_Antenna_Mapping) + 1 def read_input_select_RW(self): return self._input_select @@ -89,7 +100,7 @@ class DigitalBeam(beam_device): def read_antenna_select_RW(self): # select only the rows from self.__input_select for which a mapping onto antennas is defined. - antenna_select = [[False] * self.NUM_BEAMLETS] * (max(self.Input_to_Antenna_Mapping) + 1) + antenna_select = [[False] * self.NUM_BEAMLETS] * self.nr_inputs() for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping): if antenna_nr >= 0: @@ -98,9 +109,18 @@ class DigitalBeam(beam_device): return antenna_select def write_antenna_select_RW(self, antennas): + # Do not use any broken antennas, even if the user requests it. This allows the user + # to select the antennas they would like to use. + antenna_usage_mask = self.antennafield_proxy.Antenna_Usage_Mask_R + for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping): - if self.antennafield_proxy.Antenna_Usage_Mask_R[input_nr] and antenna_nr >= 0: - self._input_select[input_nr] = antennas[antenna_nr] + if antenna_nr >= 0: + if antenna_usage_mask[antenna_nr]: + # use antenna for the beamlets as supplied by the client + self._input_select[input_nr] = antennas[antenna_nr] + else: + # do not use antenna for any beamlet + self._input_select[input_nr] = False # ---------- # Summarising Attributes @@ -120,7 +140,6 @@ class DigitalBeam(beam_device): self.antennafield_proxy = DeviceProxy(self.AntennaField_Device) self.antennafield_proxy.set_source(DevSource.DEV) - antenna_usage_mask = self.antennafield_proxy.Antenna_Usage_Mask_R self.beamlet_proxy = DeviceProxy(self.Beamlet_Device) self.beamlet_proxy.set_source(DevSource.DEV) @@ -131,9 +150,9 @@ class DigitalBeam(beam_device): # Generate positions for all FPGA inputs. # Use reference position for any missing antennas so they always get a delay of 0 - input_itrf = numpy.array([reference_itrf] * self.NUM_INPUTS) + input_itrf = numpy.array([reference_itrf] * self.MAX_INPUTS) for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping): - if antenna_usage_mask[input_nr] and antenna_nr >= 0: + if antenna_nr >= 0: input_itrf[input_nr] = antenna_itrf[antenna_nr] # a delay calculator @@ -143,9 +162,8 @@ class DigitalBeam(beam_device): self.relative_input_positions = input_itrf - reference_itrf # use all antennas in the mapping for all beamlets, unless specified otherwise - input_select_mask = numpy.logical_and(numpy.array(self.Input_to_Antenna_Mapping) >= 0, antenna_usage_mask) - input_select = numpy.repeat(input_select_mask, self.NUM_BEAMLETS).reshape(self.NUM_INPUTS, self.NUM_BEAMLETS) - self.write_input_select_RW(input_select) + self.write_input_select_RW(numpy.zeros((self.MAX_INPUTS, self.NUM_BEAMLETS), dtype=bool)) + self.write_antenna_select_RW(numpy.ones((self.nr_inputs(), self.NUM_BEAMLETS), dtype=bool)) # -------- # internal functions @@ -159,7 +177,7 @@ class DigitalBeam(beam_device): Returns delays[antenna][beamlet] """ - delays = numpy.zeros((self.NUM_INPUTS, self.NUM_BEAMLETS), dtype=numpy.float64) + delays = numpy.zeros((self.MAX_INPUTS, self.NUM_BEAMLETS), dtype=numpy.float64) d = self.delay_calculator d.set_measure_time(timestamp) @@ -173,7 +191,7 @@ class DigitalBeam(beam_device): """ Converts an array with dimensions [antenna][beamlet] -> [fpga_nr][input_nr][pol_nr][beamlet] by repeating the values for both polarisations. """ - assert arr.shape == (self.NUM_INPUTS, self.NUM_BEAMLETS) + assert arr.shape == (self.MAX_INPUTS, self.NUM_BEAMLETS) # Each antenna maps on [fpga_nr][input_nr][0] and [fpga_nr][input_nr][1], and we work # with [antenna][beamlet], so we have to interleave copies of the input array per beamlet @@ -206,7 +224,7 @@ class DigitalBeam(beam_device): beam_weights = self.beamlet_proxy.calculate_bf_weights(fpga_delays.flatten()) beam_weights = beam_weights.reshape((Beamlet.N_PN, Beamlet.A_PN * Beamlet.N_POL * Beamlet.N_BEAMLETS_CTRL)) - # Filter out unwanted antennas + # Filter out unwanted antennas (they get a weight of 0) beam_weights *= self._map_inputs_on_polarised_inputs(self._input_select) return beam_weights diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py index 8ec767588ae0e9fe333411b763d47c765da20649..ba0c1364efe3af800ca0a3cf3ae7be313d1a38ac 100644 --- a/tangostationcontrol/tangostationcontrol/devices/unb2.py +++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py @@ -70,7 +70,8 @@ class UNB2(opcua_device): ) TRANSLATOR_DEFAULT_SETTINGS = [ - 'UNB2_mask_RW' + 'UNB2_mask_RW', + 'UNB2TR_monitor_rate_RW' ] UNB2TR_I2C_bus_DDR4_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_DDR4_error_R"],datatype=numpy.int64 , dims=(2,4)) @@ -198,7 +199,7 @@ class UNB2(opcua_device): # overloaded functions # -------- - def _initialise_hardware(self): + def _prepare_hardware(self): """ Initialise the UNB2 hardware. """ # Cycle UNB2s