Skip to content
Snippets Groups Projects
Commit 4c0cb091 authored by Jan David Mol's avatar Jan David Mol
Browse files

Merge branch 'L2SS-916-antenna_usage_mask_bugfixes' into 'master'

L2SS-916: Minor fixes wrt antenna_usage_mask

Closes L2SS-916

See merge request !403
parents 8a283381 ea62b95f
No related branches found
No related tags found
1 merge request!403L2SS-916: Minor fixes wrt antenna_usage_mask
Showing
with 161 additions and 81 deletions
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.
...@@ -46,13 +46,19 @@ The antennas represented by the antenna field are selected by the following prop ...@@ -46,13 +46,19 @@ The antennas represented by the antenna field are selected by the following prop
:type: ``str[]`` :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[]`` :type: ``int32[]``
:shape: ``int32[][2]`` :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. :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 Positions
"""""""""""""""""""" """"""""""""""""""""
......
...@@ -109,12 +109,10 @@ Most devices provide the following commands, in order to configure the hardware ...@@ -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. :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. :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``. :on(): Mark the device as operational. Moves from ``STANDBY`` to ``ON``.
See also :ref:`boot`, which provides functionality to initialise all the devices. See also :ref:`boot`, which provides functionality to initialise all the devices.
......
...@@ -34,6 +34,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st ...@@ -34,6 +34,7 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st
devices/configure devices/configure
configure_station configure_station
signal_chain signal_chain
broken_hardware
developer developer
faq faq
......
...@@ -110,6 +110,15 @@ class AntennaField(lofar_device): ...@@ -110,6 +110,15 @@ class AntennaField(lofar_device):
default_value = numpy.array([AntennaUse.AUTO] * MAX_NUMBER_OF_HBAT) 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 # ----- Position information
Antenna_Field_Reference_ITRF = device_property( Antenna_Field_Reference_ITRF = device_property(
...@@ -149,7 +158,6 @@ class AntennaField(lofar_device): ...@@ -149,7 +158,6 @@ class AntennaField(lofar_device):
mandatory=False, mandatory=False,
default_value = 2015.5 default_value = 2015.5
) )
HBAT_PQR_rotation_angles_deg = device_property( HBAT_PQR_rotation_angles_deg = device_property(
doc='Rotation of each tile in the PQ plane ("horizontal") in degrees.', doc='Rotation of each tile in the PQ plane ("horizontal") in degrees.',
dtype='DevVarFloatArray', dtype='DevVarFloatArray',
...@@ -206,14 +214,21 @@ class AntennaField(lofar_device): ...@@ -206,14 +214,21 @@ class AntennaField(lofar_device):
Antenna_Names_R = attribute(access=AttrWriteType.READ, Antenna_Names_R = attribute(access=AttrWriteType.READ,
dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT) dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT)
Antenna_Quality_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.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) dtype=(numpy.uint32,), max_dim_x=MAX_NUMBER_OF_HBAT)
Antenna_Use_R = attribute(access=AttrWriteType.READ, 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.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) dtype=(numpy.uint32,), max_dim_x=MAX_NUMBER_OF_HBAT)
Antenna_Usage_Mask_R = attribute(access=AttrWriteType.READ, 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) 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_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_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) 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)
...@@ -267,18 +282,28 @@ class AntennaField(lofar_device): ...@@ -267,18 +282,28 @@ class AntennaField(lofar_device):
def read_Antenna_Quality_R(self): def read_Antenna_Quality_R(self):
return self.Antenna_Quality 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): def read_Antenna_Usage_Mask_R(self):
antenna_usage = numpy.zeros(MAX_NUMBER_OF_HBAT, dtype=bool) use = numpy.array(self.Antenna_Use)
for n in range(0, MAX_NUMBER_OF_HBAT): quality = numpy.array(self.Antenna_Quality)
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)) antennas_forced_on = use == AntennaUse.ON
return antenna_usage 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): def read_nr_antennas_R(self):
# The number of antennas should be equal to: # 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 Control_to_RECV_mapping (after reshaping),
# * the number of elements in the Power_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 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. # Parsing a property here is quickest, so we chose that.
return len(self.Control_to_RECV_mapping) // 2 return len(self.Control_to_RECV_mapping) // 2
...@@ -388,31 +413,15 @@ class AntennaField(lofar_device): ...@@ -388,31 +413,15 @@ class AntennaField(lofar_device):
@log_exceptions() @log_exceptions()
def _prepare_hardware(self): def _prepare_hardware(self):
# Initialise the RCU hardware. usage_mask = self.read_attribute('Antenna_Usage_Mask_R')
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()
# 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 # 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 # 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')) 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 # Commands
# -------- # --------
...@@ -452,6 +461,8 @@ class AntennaToRecvMapper(object): ...@@ -452,6 +461,8 @@ class AntennaToRecvMapper(object):
self.__number_of_receivers = number_of_receivers self.__number_of_receivers = number_of_receivers
self.__default_value_mapping_read = { self.__default_value_mapping_read = {
"ANT_mask_RW": numpy.full(number_of_antennas, False), "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_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_BF_delay_steps_RW": numpy.zeros([number_of_antennas,32], dtype=numpy.int64),
"HBAT_LED_on_R": numpy.full((number_of_antennas,32), False), "HBAT_LED_on_R": numpy.full((number_of_antennas,32), False),
...@@ -464,6 +475,7 @@ class AntennaToRecvMapper(object): ...@@ -464,6 +475,7 @@ class AntennaToRecvMapper(object):
} }
self.__default_value_mapping_write = { self.__default_value_mapping_write = {
"ANT_mask_RW": numpy.full(96, False), "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_BF_delay_steps_RW": numpy.zeros([96,32], dtype=numpy.int64),
"HBAT_LED_on_RW": numpy.full((96,32), False), "HBAT_LED_on_RW": numpy.full((96,32), False),
"HBAT_PWR_LNA_on_RW": numpy.full((96,32), False), "HBAT_PWR_LNA_on_RW": numpy.full((96,32), False),
......
...@@ -52,6 +52,10 @@ class APSCT(opcua_device): ...@@ -52,6 +52,10 @@ class APSCT(opcua_device):
default_value=10.0 default_value=10.0
) )
TRANSLATOR_DEFAULT_SETTINGS = [
'APSCTTR_monitor_rate_RW'
]
# ---------- # ----------
# Attributes # Attributes
# ---------- # ----------
...@@ -118,7 +122,7 @@ class APSCT(opcua_device): ...@@ -118,7 +122,7 @@ class APSCT(opcua_device):
# overloaded functions # overloaded functions
# -------- # --------
def _initialise_hardware(self): def _prepare_hardware(self):
""" Initialise the APSCT hardware. """ """ Initialise the APSCT hardware. """
# Cycle clock # Cycle clock
......
...@@ -37,6 +37,10 @@ class APSPU(opcua_device): ...@@ -37,6 +37,10 @@ class APSPU(opcua_device):
default_value=1 default_value=1
) )
TRANSLATOR_DEFAULT_SETTINGS = [
'APSPUTR_monitor_rate_RW'
]
# ---------- # ----------
# Attributes # Attributes
# ---------- # ----------
......
...@@ -343,16 +343,6 @@ class lofar_device(Device, metaclass=DeviceMeta): ...@@ -343,16 +343,6 @@ class lofar_device(Device, metaclass=DeviceMeta):
# This is just the command version of _prepare_hardware(). # This is just the command version of _prepare_hardware().
self._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) @only_in_states(INITIALISED_STATES)
@fault_on_error() @fault_on_error()
@command() @command()
...@@ -391,9 +381,6 @@ class lofar_device(Device, metaclass=DeviceMeta): ...@@ -391,9 +381,6 @@ class lofar_device(Device, metaclass=DeviceMeta):
# initialise settings # initialise settings
self.set_defaults() self.set_defaults()
# powercycle backing hardware
self.initialise_hardware()
# make device available # make device available
self.On() self.On()
...@@ -413,11 +400,7 @@ class lofar_device(Device, metaclass=DeviceMeta): ...@@ -413,11 +400,7 @@ class lofar_device(Device, metaclass=DeviceMeta):
self._set_defaults(self.TRANSLATOR_DEFAULT_SETTINGS) self._set_defaults(self.TRANSLATOR_DEFAULT_SETTINGS)
def _prepare_hardware(self): def _prepare_hardware(self):
""" Override this method to load any firmware before configuring the hardware. """ """ Override this method to load any firmware or power cycle components before configuring the hardware. """
pass
def _initialise_hardware(self):
""" Override this method to initialise any hardware after configuring it. """
pass pass
def _disable_hardware(self): def _disable_hardware(self):
......
...@@ -42,7 +42,7 @@ class Observation(lofar_device): ...@@ -42,7 +42,7 @@ class Observation(lofar_device):
""" """
NUM_MAX_HBAT = MAX_NUMBER_OF_HBAT NUM_MAX_HBAT = MAX_NUMBER_OF_HBAT
NUM_INPUTS = DigitalBeam.NUM_INPUTS MAX_INPUTS = DigitalBeam.MAX_INPUTS
NUM_BEAMLETS = DigitalBeam.NUM_BEAMLETS NUM_BEAMLETS = DigitalBeam.NUM_BEAMLETS
# Attributes # Attributes
...@@ -52,7 +52,7 @@ class Observation(lofar_device): ...@@ -52,7 +52,7 @@ class Observation(lofar_device):
stop_time_R = attribute(dtype=numpy.float64, access=AttrWriteType.READ) 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) 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) 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) 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) tile_beam_R = attribute(dtype=(numpy.str,), max_dim_x=3, access=AttrWriteType.READ)
first_beamlet_R = attribute(dtype=numpy.int64, access=AttrWriteType.READ) first_beamlet_R = attribute(dtype=numpy.int64, access=AttrWriteType.READ)
...@@ -67,6 +67,8 @@ class Observation(lofar_device): ...@@ -67,6 +67,8 @@ class Observation(lofar_device):
self._observation_id = -1 self._observation_id = -1
self._stop_time = datetime.now() self._stop_time = datetime.now()
self._num_inputs = 0
def configure_for_initialise(self): def configure_for_initialise(self):
"""Load the JSON from the attribute and configure member variables""" """Load the JSON from the attribute and configure member variables"""
...@@ -113,6 +115,9 @@ class Observation(lofar_device): ...@@ -113,6 +115,9 @@ class Observation(lofar_device):
self.tilebeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Tilebeam/1") self.tilebeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Tilebeam/1")
self.tilebeam_proxy.set_source(DevSource.DEV) self.tilebeam_proxy.set_source(DevSource.DEV)
# Collect static information
self._num_inputs = self.digitalbeam_proxy.antenna_select_RW.shape[0]
logger.info( logger.info(
f"The observation with ID={self._observation_id} is " f"The observation with ID={self._observation_id} is "
"configured. It will begin as soon as On() is called and it is" "configured. It will begin as soon as On() is called and it is"
...@@ -268,7 +273,7 @@ class Observation(lofar_device): ...@@ -268,7 +273,7 @@ class Observation(lofar_device):
def _apply_saps_antenna_select(self, antenna_mask:list): def _apply_saps_antenna_select(self, antenna_mask:list):
""" Convert an array of antenna indexes into a boolean select array""" """ 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) first_beamlet = numpy.array(self.read_first_beamlet_R(), dtype=numpy.int64)
for a in antenna_mask: for a in antenna_mask:
for i in range(first_beamlet, self.NUM_BEAMLETS): for i in range(first_beamlet, self.NUM_BEAMLETS):
......
...@@ -70,7 +70,7 @@ class RECV(opcua_device): ...@@ -70,7 +70,7 @@ class RECV(opcua_device):
) )
RCU_PWR_ANT_on_RW = device_property( RCU_PWR_ANT_on_RW = device_property(
dtype='DevVarLong64Array', dtype='DevVarBooleanArray',
mandatory=False, mandatory=False,
default_value=[False] * 96 # turn power off by default in test setups, f.e. to prevent blowing up the noise sources 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): ...@@ -83,7 +83,8 @@ class RECV(opcua_device):
TRANSLATOR_DEFAULT_SETTINGS = [ TRANSLATOR_DEFAULT_SETTINGS = [
'ANT_mask_RW', 'ANT_mask_RW',
'RCU_mask_RW' 'RCU_mask_RW',
'RECVTR_monitor_rate_RW'
] ]
# ----- Timing values # ----- Timing values
...@@ -236,7 +237,7 @@ class RECV(opcua_device): ...@@ -236,7 +237,7 @@ class RECV(opcua_device):
# in positive delays regardless of the pointing direction. # in positive delays regardless of the pointing direction.
self.HBAT_bf_delay_offset = numpy.mean(self.HBAT_bf_delay_step_delays) 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. """ """ Initialise the RCU hardware. """
# Cycle RCUs # Cycle RCUs
......
...@@ -37,9 +37,13 @@ class DigitalBeam(beam_device): ...@@ -37,9 +37,13 @@ class DigitalBeam(beam_device):
* antennas: the elements in the AntennaField that feeds the FPGA, * antennas: the elements in the AntennaField that feeds the FPGA,
* inputs: the antenna slots of the FPGAs (covering both X and Y as one), * 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). * 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 NUM_BEAMLETS = 488
# ----------------- # -----------------
...@@ -64,7 +68,7 @@ class DigitalBeam(beam_device): ...@@ -64,7 +68,7 @@ class DigitalBeam(beam_device):
dtype=(numpy.int32,), dtype=(numpy.int32,),
doc='Which antenna of the antennafield is connected to each input. -1 if no antenna is present.', doc='Which antenna of the antennafield is connected to each input. -1 if no antenna is present.',
mandatory=False, mandatory=False,
default_value = [-1] * NUM_INPUTS default_value = [-1] * MAX_INPUTS
) )
# ---------- # ----------
...@@ -74,12 +78,19 @@ class DigitalBeam(beam_device): ...@@ -74,12 +78,19 @@ class DigitalBeam(beam_device):
Duration_delays_R = attribute(access=AttrWriteType.READ, Duration_delays_R = attribute(access=AttrWriteType.READ,
dtype=numpy.float64, fget=lambda self: self._delays.statistics["last"] or 0) 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', 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=NUM_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") 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)', nr_inputs_R = attribute(doc='Number of configured inputs from the associated antenna field.',
dtype=((bool,),), max_dim_x=NUM_BEAMLETS, max_dim_y=NUM_INPUTS, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") 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): def read_input_select_RW(self):
return self._input_select return self._input_select
...@@ -89,7 +100,7 @@ class DigitalBeam(beam_device): ...@@ -89,7 +100,7 @@ class DigitalBeam(beam_device):
def read_antenna_select_RW(self): def read_antenna_select_RW(self):
# select only the rows from self.__input_select for which a mapping onto antennas is defined. # 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): for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping):
if antenna_nr >= 0: if antenna_nr >= 0:
...@@ -98,9 +109,18 @@ class DigitalBeam(beam_device): ...@@ -98,9 +109,18 @@ class DigitalBeam(beam_device):
return antenna_select return antenna_select
def write_antenna_select_RW(self, antennas): 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): 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: 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] self._input_select[input_nr] = antennas[antenna_nr]
else:
# do not use antenna for any beamlet
self._input_select[input_nr] = False
# ---------- # ----------
# Summarising Attributes # Summarising Attributes
...@@ -120,7 +140,6 @@ class DigitalBeam(beam_device): ...@@ -120,7 +140,6 @@ class DigitalBeam(beam_device):
self.antennafield_proxy = DeviceProxy(self.AntennaField_Device) self.antennafield_proxy = DeviceProxy(self.AntennaField_Device)
self.antennafield_proxy.set_source(DevSource.DEV) 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 = DeviceProxy(self.Beamlet_Device)
self.beamlet_proxy.set_source(DevSource.DEV) self.beamlet_proxy.set_source(DevSource.DEV)
...@@ -131,9 +150,9 @@ class DigitalBeam(beam_device): ...@@ -131,9 +150,9 @@ class DigitalBeam(beam_device):
# Generate positions for all FPGA inputs. # Generate positions for all FPGA inputs.
# Use reference position for any missing antennas so they always get a delay of 0 # 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): 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] input_itrf[input_nr] = antenna_itrf[antenna_nr]
# a delay calculator # a delay calculator
...@@ -143,9 +162,8 @@ class DigitalBeam(beam_device): ...@@ -143,9 +162,8 @@ class DigitalBeam(beam_device):
self.relative_input_positions = input_itrf - reference_itrf self.relative_input_positions = input_itrf - reference_itrf
# use all antennas in the mapping for all beamlets, unless specified otherwise # 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) self.write_input_select_RW(numpy.zeros((self.MAX_INPUTS, self.NUM_BEAMLETS), dtype=bool))
input_select = numpy.repeat(input_select_mask, self.NUM_BEAMLETS).reshape(self.NUM_INPUTS, self.NUM_BEAMLETS) self.write_antenna_select_RW(numpy.ones((self.nr_inputs(), self.NUM_BEAMLETS), dtype=bool))
self.write_input_select_RW(input_select)
# -------- # --------
# internal functions # internal functions
...@@ -159,7 +177,7 @@ class DigitalBeam(beam_device): ...@@ -159,7 +177,7 @@ class DigitalBeam(beam_device):
Returns delays[antenna][beamlet] 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 = self.delay_calculator
d.set_measure_time(timestamp) d.set_measure_time(timestamp)
...@@ -173,7 +191,7 @@ class DigitalBeam(beam_device): ...@@ -173,7 +191,7 @@ class DigitalBeam(beam_device):
""" Converts an array with dimensions [antenna][beamlet] -> [fpga_nr][input_nr][pol_nr][beamlet] """ Converts an array with dimensions [antenna][beamlet] -> [fpga_nr][input_nr][pol_nr][beamlet]
by repeating the values for both polarisations. """ 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 # 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 # with [antenna][beamlet], so we have to interleave copies of the input array per beamlet
...@@ -206,7 +224,7 @@ class DigitalBeam(beam_device): ...@@ -206,7 +224,7 @@ class DigitalBeam(beam_device):
beam_weights = self.beamlet_proxy.calculate_bf_weights(fpga_delays.flatten()) 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)) 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) beam_weights *= self._map_inputs_on_polarised_inputs(self._input_select)
return beam_weights return beam_weights
......
...@@ -70,7 +70,8 @@ class UNB2(opcua_device): ...@@ -70,7 +70,8 @@ class UNB2(opcua_device):
) )
TRANSLATOR_DEFAULT_SETTINGS = [ 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)) 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): ...@@ -198,7 +199,7 @@ class UNB2(opcua_device):
# overloaded functions # overloaded functions
# -------- # --------
def _initialise_hardware(self): def _prepare_hardware(self):
""" Initialise the UNB2 hardware. """ """ Initialise the UNB2 hardware. """
# Cycle UNB2s # Cycle UNB2s
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment