diff --git a/CDB/stations/DTS_ConfigDb.json b/CDB/stations/DTS_ConfigDb.json index 69700d582a3e2366242da3a92c60244810e88ca7..c13acba95332eabc1dd26dc426eee896518d3688 100644 --- a/CDB/stations/DTS_ConfigDb.json +++ b/CDB/stations/DTS_ConfigDb.json @@ -147,6 +147,9 @@ "AntennaField": { "STAT/AntennaField/1": { "properties": { + "Antenna_Type": [ + "HBA" + ], "RECV_devices": [ "STAT/RECV/1" ], diff --git a/CDB/stations/DTS_Outside_ConfigDb.json b/CDB/stations/DTS_Outside_ConfigDb.json index baf9d0af986c343d90c68e7613a1933b32b531ca..8fbedd5ce131e0eefb661c0a1e2be78286a39a00 100644 --- a/CDB/stations/DTS_Outside_ConfigDb.json +++ b/CDB/stations/DTS_Outside_ConfigDb.json @@ -172,6 +172,9 @@ "AntennaField": { "STAT/AntennaField/2": { "properties": { + "Antenna_Type": [ + "HBA" + ], "RECV_devices": [ "STAT/RECV/1" ], @@ -229,6 +232,9 @@ }, "STAT/AntennaField/1": { "properties": { + "Antenna_Type": [ + "LBA" + ], "RECV_devices": [ "STAT/RECV/1" ], @@ -344,9 +350,6 @@ "SDP": { "STAT/SDP/1": { "properties": { - "AntennaType": [ - "LBA" - ], "OPC_Server_Name": [ "10.99.0.250" ], diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py index 33591565a7940375c1777a49ae69af6ed92560f9..2e76235e2151cd49d4bb5e03b2b5ffffae89714e 100644 --- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -117,6 +117,13 @@ class AntennaField(lofar_device): # ----- Antenna properties + Antenna_Type = device_property( + doc="Type of antenna in this field (LBA or HBA)", + dtype='DevString', + mandatory=False, + default_value = "LBA" + ) + Antenna_Needs_Power = device_property( doc="Whether to provide power to each antenna (False for noise sources)", dtype='DevVarBooleanArray', @@ -225,6 +232,9 @@ class AntennaField(lofar_device): default_value = [] ) + Antenna_Type_R = attribute(doc='The type of antenna in this field (LBA or HBA).', + dtype=str) + Antenna_Names_R = attribute(access=AttrWriteType.READ, dtype=(str,), max_dim_x=MAX_NUMBER_OF_HBAT) Antenna_Quality_R = attribute(doc='The quality of each antenna. 0=OK, 1=SUSPICIOUS, 2=BROKEN, 3=BEYOND_REPAIR.', @@ -289,6 +299,9 @@ class AntennaField(lofar_device): doc='Number of Antennas in this field', dtype=numpy.int32) + def read_Antenna_Type_R(self): + return self.Antenna_Type + def read_Antenna_Names_R(self): return self.Antenna_Names @@ -451,6 +464,10 @@ class AntennaField(lofar_device): def _prepare_hardware(self): usage_mask = self.read_attribute('Antenna_Usage_Mask_R') + # ----------------------------------------------------------- + # Configure RECV + # ----------------------------------------------------------- + # 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')) @@ -458,9 +475,31 @@ class AntennaField(lofar_device): # 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) + # ----------------------------------------------------------- + # Configure SDP + # ----------------------------------------------------------- + + self.configure_sdp() + # -------- # Commands # -------- + @command() + def configure_sdp(self): + """ Configure SDP to process our antennas. """ + + # upload which antenna type we're using + + # read-modify-write on [fpga][(input, polarisation)] + sdp_antenna_type = self.sdp_proxy.antenna_type_RW + for fpga_nr, input_nr in self.read_attribute("Antenna_to_SDP_Mapping_R"): + # set for x polarisation + sdp_antenna_type[fpga_nr, input_nr * 2 + 0] = self.Antenna_Type + # set for y polarisation + sdp_antenna_type[fpga_nr, input_nr * 2 + 1] = self.Antenna_Type + + self.sdp_proxy.antenna_type_RW = sdp_antenna_type + @command(dtype_in=DevVarFloatArray, dtype_out=DevVarLongArray) def calculate_HBAT_bf_delay_steps(self, delays: numpy.ndarray): num_tiles = self.read_nr_antennas_R() diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py index 88191f1cc552d2576853bdfa04bf2b98364bb5a7..d897a36168756f44c919e2b113ab1fd4e659c28b 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -32,13 +32,6 @@ class SDP(opcua_device): # Device Properties # ----------------- - AntennaType = device_property( - doc='Antenna type (LBA or HBA) we control', - dtype='DevString', - mandatory=False, - default_value = "HBA" - ) - TR_fpga_mask_RW_default = device_property( dtype='DevVarBooleanArray', mandatory=False, @@ -186,8 +179,9 @@ class SDP(opcua_device): FPGA_bst_offload_bsn_R = attribute_wrapper(comms_annotation=["FPGA_bst_offload_bsn_R"], datatype=numpy.int64, dims=(N_pn, N_beamsets_ctrl)) - antenna_type_R = attribute(doc='Type of antenna (LBA or HBA) attached to the FPGAs', - dtype=str, fget=lambda self: self.AntennaType) + antenna_type_RW = attribute(doc='Type of antenna (LBA or HBA) attached to each input of the FPGAs', + dtype=(str,), max_dim_y=N_pn, max_dim_x=S_pn, + access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") nyquist_zone_R = attribute(doc='Nyquist zone of the input frequencies', dtype=numpy.uint32, fisallowed="is_attribute_access_allowed", polling_period=1000, abs_change=1) @@ -195,6 +189,19 @@ class SDP(opcua_device): dtype=numpy.uint32, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed", polling_period=1000, abs_change=1) + def read_antenna_type_RW(self): + return self._antenna_type + + def write_antenna_type_RW(self, value): + if value.shape != (self.N_pn, self.S_pn): + raise ValueError(f"Dimension mismatch. Expected ({self.N_pn}, {self.S_pn}), got {value.shape}.") + + for val in value.flatten(): + if val not in ["LBA", "HBA"]: + raise ValueError(f"Unsupported antenna type: {val}. Must be one of [LBA, HBA].") + + self._antenna_type = value + def _nyquist_zone(self, clock): """ Return the Nyquist zone for the given clock (in Hz). @@ -212,7 +219,8 @@ class SDP(opcua_device): } try: - return nyquist_zones[(self.AntennaType), clock // 1000000] + # support only one AntennaType for now. TODO: expose nyquist zones as an array + return nyquist_zones[(self._antenna_type[0][0], clock // 1000000)] except KeyError: raise ValueError(f"Could not determine Nyquist zone for antenna type {self.AntennaType} with clock {clock} Hz") @@ -274,6 +282,14 @@ class SDP(opcua_device): # overloaded functions # -------- + def configure_for_initialise(self): + super().configure_for_initialise() + + # Store which type of antenna is connected to each input. + # + # We need to be told this by AntennaField, through configure_for_antennafield. + self._antenna_type = numpy.array([["???"] * self.S_pn] * self.N_pn, dtype=str) + def _prepare_hardware(self): # FPGAs that are actually reachable and we care about wait_for = ~(self.read_attribute("TR_fpga_communication_error_R")) & self.read_attribute("TR_fpga_mask_R")