diff --git a/README.md b/README.md index e6c922d7948da1fd6e16c36acce980aadb0cd9b6..d4f408b4ea3fc7ebfad35dcef303669acc49b063 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Next change the version in the following places: # Release Notes +* 0.12.0 Add `Calibration_SDP_Subband_Weights_<XXX>MHz_R` attributes to implement HDF5 calibration tables * 0.11.2 Fix sleep duration in archiver test * 0.11.1 Fix event unsubscription in TemperatureManager * 0.11.0 Added StationManager device diff --git a/docker-compose/jupyterlab/requirements.txt b/docker-compose/jupyterlab/requirements.txt index 70494373dff1a7d5010f4a939362c7bfc87998c8..52a55ba3aa9987585fe130a34271d9ef840b946e 100644 --- a/docker-compose/jupyterlab/requirements.txt +++ b/docker-compose/jupyterlab/requirements.txt @@ -17,6 +17,7 @@ pyvisa-py opcua lofarantpos >= 0.5.0 # Apache 2 python-geohash >= 0.8.5 # Apache 2 / MIT +asyncua >= 0.9.90 # LGPLv3 numpy scipy diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION index bc859cbd6d998ecfd13ee4c49cd5da054afb36ad..ac454c6a1fc3f05f60d3772b45f0b1a5db4b9f87 100644 --- a/tangostationcontrol/VERSION +++ b/tangostationcontrol/VERSION @@ -1 +1 @@ -0.11.2 +0.12.0 diff --git a/tangostationcontrol/docs/source/calibration.rst b/tangostationcontrol/docs/source/calibration.rst index 84ca470e73be81a439492afb6d01ef675c195baa..6e5aa3e3ed618e2df8957e4e319d83ad39385e5c 100644 --- a/tangostationcontrol/docs/source/calibration.rst +++ b/tangostationcontrol/docs/source/calibration.rst @@ -126,11 +126,7 @@ The ``AntennaField`` device provides the following attributes that provide the r :type: ``float[N_ant][3]`` -:Calibration_SDP_Subband_Weights_Default_R: The computed remaining delay & loss compensation, per subband, as (real, imag) pairs representing complex weights. This array represents ``Calibration_SDP_Fine_Calibration_Default_R`` transformed into complex weights for every subband. - - :type: ``float[N_ant][N_subbands][2]`` - -:Calibration_SDP_Subband_Weights_R: The remaining delay & loss compensation to be applied. Returns rows from the preconfigured calibration table if available, falling back to rows from the computed ``Calibration_SDP_Subband_Weights_Default_R`` table otherwise. +:Calibration_SDP_Subband_Weights_R: The remaining delay & loss compensation to be applied. Returns rows from the preconfigured calibration table if available. :type: ``float[N_ant][N_subbands][2]`` diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt index 57f840d8d720a4d7f4b30d73b11b87ae6f9a8221..9b0aae99639cd91190d184066a90ccbf00872363 100644 --- a/tangostationcontrol/requirements.txt +++ b/tangostationcontrol/requirements.txt @@ -3,7 +3,7 @@ # integration process, which may cause wedges in the gate later. importlib-metadata<2.0.0,>=0.12;python_version<"3.8" -lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client@0.12 +lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client numpy mock asyncua >= 0.9.90 # LGPLv3 diff --git a/tangostationcontrol/tangostationcontrol/common/calibration.py b/tangostationcontrol/tangostationcontrol/common/calibration.py index 31fd0491f67f608257ae7cfe54b23fad9f0453e6..970781c70a8ba0e5470725fe6bce5bf498babec7 100644 --- a/tangostationcontrol/tangostationcontrol/common/calibration.py +++ b/tangostationcontrol/tangostationcontrol/common/calibration.py @@ -2,6 +2,15 @@ # SPDX-License-Identifier: Apache-2.0 import numpy +from lofar_station_client.file_access import read_hdf5 + +from tangostationcontrol.common.calibration_table import ( + CalibrationTable as Hdf5CalibrationTable, +) +from tangostationcontrol.common.constants import ( + N_pol, + N_subbands, +) def delay_compensation(delays_seconds: numpy.ndarray, clock: int): @@ -80,3 +89,42 @@ def loss_compensation(losses_dB: numpy.ndarray): input_attenuation_remainder_factor = dB_to_factor(input_attenuation_remainder_dB) return (input_attenuation_integer_dB, input_attenuation_remainder_factor) + + +class CalibrationTable: + """A class to represent calibration tables, and to retrieve calibration data""" + + def __init__(self, filename: str): + # open the file and fill the calibration with actual data + with read_hdf5(filename, Hdf5CalibrationTable) as table: + self.table = table + + @staticmethod + def complex_to_float(caltable: numpy.ndarray) -> numpy.ndarray: + """Numpy array conversion from + (antenna_list, N_pol, N_subbands),dtype=complex128 to + (antenna_list, N_pol, N_subbands, VALUES_PER_COMPLEX),dtype=float64 + """ + caltable_float = caltable.view(dtype=numpy.float64) + return caltable_float.reshape(caltable.shape + (2,)) + + def get_antenna_data(self, antenna_list: list) -> numpy.ndarray: + """ + Returns a multi-dimensional array of the calibration data. + The shape is (antenna_list, N_pol, N_subbands), type is numpy.complex128 + """ + data = [] + + for antenna in antenna_list: + if antenna in self.table._data_reader.data.keys(): + # load the calibration for this antenna + x_data = self.table._data_reader.data[antenna]["x"] + y_data = self.table._data_reader.data[antenna]["y"] + x = numpy.array(x_data, dtype=numpy.complex128) + y = numpy.array(y_data, dtype=numpy.complex128) + pol = numpy.array([x, y]) + data.append(pol) + else: + # append data with default 1.0 value + data.append([[1.0] * N_subbands] * N_pol) + return numpy.array(data, dtype=numpy.complex128) diff --git a/tangostationcontrol/tangostationcontrol/common/calibration_table.py b/tangostationcontrol/tangostationcontrol/common/calibration_table.py index 7aad3afbcf1c82dddc02fa233df98ce7429c0fee..19d4f809c32291502c50a4274b96fbe61f0dd0fa 100644 --- a/tangostationcontrol/tangostationcontrol/common/calibration_table.py +++ b/tangostationcontrol/tangostationcontrol/common/calibration_table.py @@ -21,4 +21,4 @@ class CalibrationTable: calibration_version: int = attribute() calibration_name: str = attribute() calibration_date: str = attribute() - antennas: Dict[str, CalibrationData] = attribute() + antennas: Dict[str, CalibrationData] = member() diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py index 6eac0d1420522174fbe5dad558a304b51331e8a6..e34a51568dd7596926779bde271e05bead5a98b6 100644 --- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -8,6 +8,7 @@ import logging from enum import IntEnum from math import pi from typing import List +from pathlib import Path import numpy @@ -46,6 +47,7 @@ from tangostationcontrol.common.constants import ( ) from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.frequency_bands import bands, Band +from tangostationcontrol.common.calibration import CalibrationTable from tangostationcontrol.common.lofar_logging import ( device_logging_to_python, log_exceptions, @@ -60,6 +62,10 @@ from tangostationcontrol.devices.sdp.common import ( from tangostationcontrol.devices.sdp.sdp import SDP logger = logging.getLogger() +ABS_PATH = str(Path().absolute()).replace("\\", "/") +CALIBRATION_PATH = ( + f"{ABS_PATH}/tangostationcontrol/tangostationcontrol/devices/calibrations" +) __all__ = ["AntennaField", "AntennaToRecvMapper", "AntennaToSdpMapper", "main"] @@ -212,42 +218,6 @@ class AntennaField(LOFARDevice): default_value=0.0, ) - Calibration_SDP_Subband_Weights_50MHz = device_property( - doc="Measured calibration values for the sdp.FPGA_subband_weights_RW " - "columns of each polarisation of each antenna, at 50 MHz. Each " - "polarisation is represented by a (real, imag) pair for every " - "subband.", - dtype="DevVarFloatArray", - mandatory=False, - ) - - Calibration_SDP_Subband_Weights_150MHz = device_property( - doc="Measured calibration values for the sdp.FPGA_subband_weights_RW " - "columns of each polarisation of each antenna, at 150 MHz. Each " - "polarisation is represented by a (real, imag) pair for every " - "subband.", - dtype="DevVarFloatArray", - mandatory=False, - ) - - Calibration_SDP_Subband_Weights_200MHz = device_property( - doc="Measured calibration values for the sdp.FPGA_subband_weights_RW " - "columns of each polarisation of each antenna, at 200 MHz. Each " - "polarisation is represented by a (real, imag) pair for every " - "subband.", - dtype="DevVarFloatArray", - mandatory=False, - ) - - Calibration_SDP_Subband_Weights_250MHz = device_property( - doc="Measured calibration values for the sdp.FPGA_subband_weights_RW " - "columns of each polarisation of each antenna, at 250 MHz. Each " - "polarisation is represented by a (real, imag) pair for every " - "subband.", - dtype="DevVarFloatArray", - mandatory=False, - ) - # ----- Position information Antenna_Field_Reference_ITRF = device_property( @@ -406,6 +376,7 @@ class AntennaField(LOFARDevice): max_dim_x=N_pol, max_dim_y=MAX_ANTENNA, ) + Frequency_Band_RW = attribute( doc="The selected frequency band of each antenna.", dtype=(str,), @@ -459,15 +430,6 @@ class AntennaField(LOFARDevice): max_dim_y=MAX_ANTENNA * N_pol, max_dim_x=3, ) - Calibration_SDP_Subband_Weights_Default_R = attribute( - doc="Calibration values for the rows in sdp.FPGA_subband_weights_RW " - "relevant for our antennas, as computed. Each subband of each " - "polarisation of each antenna is represented by a real_imag number " - "(real, imag).", - dtype=((numpy.float64,),), - max_dim_y=MAX_ANTENNA * N_pol, - max_dim_x=N_subbands * VALUES_PER_COMPLEX, - ) Calibration_SDP_Subband_Weights_R = attribute( doc="Calibration values for the rows in sdp.FPGA_subband_weights_RW " "relevant for our antennas. Each subband of each polarisation of " @@ -480,6 +442,29 @@ class AntennaField(LOFARDevice): max_dim_x=N_subbands * VALUES_PER_COMPLEX, ) + # calibration loading + + Calibration_SDP_Subband_Weights_50MHz_R = attribute( + dtype=((numpy.float64,),), + max_dim_y=MAX_ANTENNA, + max_dim_x=(N_pol * N_subbands * VALUES_PER_COMPLEX), + ) + Calibration_SDP_Subband_Weights_150MHz_R = attribute( + dtype=((numpy.float64,),), + max_dim_y=MAX_ANTENNA, + max_dim_x=(N_pol * N_subbands * VALUES_PER_COMPLEX), + ) + Calibration_SDP_Subband_Weights_200MHz_R = attribute( + dtype=((numpy.float64,),), + max_dim_y=MAX_ANTENNA, + max_dim_x=(N_pol * N_subbands * VALUES_PER_COMPLEX), + ) + Calibration_SDP_Subband_Weights_250MHz_R = attribute( + dtype=((numpy.float64,),), + max_dim_y=MAX_ANTENNA, + max_dim_x=(N_pol * N_subbands * VALUES_PER_COMPLEX), + ) + # ----- Quality and usage information Antenna_Quality_R = attribute( @@ -860,6 +845,56 @@ class AntennaField(LOFARDevice): self.calibrate_recv() self.calibrate_sdp() + def _create_calibration_table_filename(self, reference_frequency: str) -> str: + """Create a valid calibration table filename given a mode""" + antennafield_name = self.get_name().split("/")[2] + return f"{CALIBRATION_PATH}/CalTable-{self.station}-{antennafield_name}-{reference_frequency}MHz.h5" + + def _get_calibration_table(self, reference_frequency: str) -> numpy.ndarray: + """Returns a calibration table of shape + (MAX_ANTENNA, N_pol, N_subbands, VALUES_PER_COMPLEX)""" + filename = self._create_calibration_table_filename(reference_frequency) + # Initialise table from relative HDF5 file + try: + calibration_data = CalibrationTable(filename) + # Retrieve data and convert them in the correct Tango attr shape + caltable = CalibrationTable.complex_to_float( + calibration_data.get_antenna_data(self.Antenna_Names) + ) + except FileNotFoundError: + caltable = numpy.full( + ( + self.read_attribute("nr_antennas_R"), + N_pol * N_subbands * VALUES_PER_COMPLEX, + ), + 1.0, + ) + return caltable + + def read_Calibration_SDP_Subband_Weights_50MHz_R(self): + subband_weights = self._get_calibration_table(reference_frequency="50") + return numpy.reshape( + subband_weights, (self.read_attribute("nr_antennas_R"), -1) + ) + + def read_Calibration_SDP_Subband_Weights_150MHz_R(self): + subband_weights = self._get_calibration_table(reference_frequency="150") + return numpy.reshape( + subband_weights, (self.read_attribute("nr_antennas_R"), -1) + ) + + def read_Calibration_SDP_Subband_Weights_200MHz_R(self): + subband_weights = self._get_calibration_table(reference_frequency="200") + return numpy.reshape( + subband_weights, (self.read_attribute("nr_antennas_R"), -1) + ) + + def read_Calibration_SDP_Subband_Weights_250MHz_R(self): + subband_weights = self._get_calibration_table(reference_frequency="250") + return numpy.reshape( + subband_weights, (self.read_attribute("nr_antennas_R"), -1) + ) + def read_Antenna_Cables_R(self): return self.Antenna_Cables @@ -946,73 +981,39 @@ class AntennaField(LOFARDevice): axis=1, ) - def read_Calibration_SDP_Subband_Weights_Default_R(self): - delay_phase_amplitude = self.read_attribute( - "Calibration_SDP_Fine_Calibration_Default_R" - ) - - subband_frequencies = self.sdp_proxy.subband_frequency_R.reshape( - N_pn, S_pn, N_subbands - ) # NB: for all inputs, unmapped, not just ours - nr_antennas = self.read_attribute("nr_antennas_R") - antenna_to_sdp_mapping = self.read_attribute("Antenna_to_SDP_Mapping_R") - - subband_weights = numpy.zeros( - (nr_antennas, N_pol, N_subbands, VALUES_PER_COMPLEX), dtype=numpy.float64 - ) - - # compute real_imag weight for each subband - for antenna_nr, (fpga_nr, input_nr) in enumerate(antenna_to_sdp_mapping): - if input_nr == -1: - continue - - for pol_nr in range(N_pol): - delay, phase_offset, amplitude = delay_phase_amplitude[ - antenna_nr * N_pol + pol_nr, : - ] - - for subband_nr in range(N_subbands): - frequency = subband_frequencies[fpga_nr, input_nr, subband_nr] - - # turn signal backwards to compensate for the provided delay - # and offset - phase_shift = -(2 * numpy.pi * frequency * delay + phase_offset) - - real = numpy.cos(phase_shift) * amplitude - imag = numpy.sin(phase_shift) * amplitude - - subband_weights[antenna_nr, pol_nr, subband_nr, :] = (real, imag) - - return subband_weights.reshape( - nr_antennas * N_pol, N_subbands * VALUES_PER_COMPLEX - ) - def _rcu_band_to_calibration_table(self) -> dict: """ Returns the SDP subband weights to apply per RCU band. """ nr_antennas = self.read_attribute("nr_antennas_R") - # default table to use if no calibration table is available - default_table = self.read_attribute("Calibration_SDP_Subband_Weights_Default_R") - # construct selector for the right calibration table if self.Antenna_Type == "LBA": rcu_band_to_caltable = { - 1: self.Calibration_SDP_Subband_Weights_50MHz or default_table, - 2: self.Calibration_SDP_Subband_Weights_50MHz or default_table, + 1: self.read_attribute( + "Calibration_SDP_Subband_Weights_50MHz_R" + ).tolist(), + 2: self.read_attribute( + "Calibration_SDP_Subband_Weights_50MHz_R" + ).tolist(), } else: # HBA rcu_band_to_caltable = { - 2: self.Calibration_SDP_Subband_Weights_150MHz or default_table, - 1: self.Calibration_SDP_Subband_Weights_200MHz or default_table, - 4: self.Calibration_SDP_Subband_Weights_250MHz or default_table, + 2: self.read_attribute( + "Calibration_SDP_Subband_Weights_150MHz_R" + ).tolist(), + 1: self.read_attribute( + "Calibration_SDP_Subband_Weights_200MHz_R" + ).tolist(), + 4: self.read_attribute( + "Calibration_SDP_Subband_Weights_250MHz_R" + ).tolist(), } # reshape them into their actual form for band, caltable in rcu_band_to_caltable.items(): rcu_band_to_caltable[band] = numpy.array(caltable).reshape( - nr_antennas, N_pol, N_subbands, 2 + nr_antennas, N_pol, N_subbands, VALUES_PER_COMPLEX ) return rcu_band_to_caltable @@ -1258,6 +1259,10 @@ class AntennaField(LOFARDevice): @log_exceptions() def configure_for_initialise(self): super().configure_for_initialise() + stationmanager_proxy = DeviceProxy("STAT/StationManager/1") + stationmanager_proxy.set_source(DevSource.DEV) + self.station = stationmanager_proxy.station_name_R + self.__setup_all_proxies() self.__setup_recv_mapper() self.__setup_sdp_mapper() diff --git a/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-150MHz.h5 b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-150MHz.h5 new file mode 100644 index 0000000000000000000000000000000000000000..f2beb4b078b240d5dc6d52804999707d98b5524e Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-150MHz.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-200MHz.h5 b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-200MHz.h5 new file mode 100644 index 0000000000000000000000000000000000000000..d560a6f71dd2c935bd993ad947503e864e129bc6 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-200MHz.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-250MHz.h5 b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-250MHz.h5 new file mode 100644 index 0000000000000000000000000000000000000000..868cf35e8677d1070f048e4b6150d1a41291033e Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-HBA-250MHz.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-LBA-50MHz.h5 b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-LBA-50MHz.h5 new file mode 100644 index 0000000000000000000000000000000000000000..6637b2019a02e3d02335b5393b908bc20146fa44 Binary files /dev/null and b/tangostationcontrol/tangostationcontrol/devices/calibrations/CalTable-DevStation-LBA-50MHz.h5 differ diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py index 68acd7a310b40dd442d521de1eb653016bbf8e36..fafcc330804084ab4d187d1c3aef8f8428a1c468 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 import numpy -from tango._tango import DevState +import time +from tango import DevState from tangostationcontrol.common.constants import ( N_elements, MAX_ANTENNA, @@ -12,21 +13,71 @@ from tangostationcontrol.common.constants import ( DEFAULT_N_HBA_TILES, CLK_200_MHZ, N_pn, - A_pn, S_pn, N_subbands, + VALUES_PER_COMPLEX, ) from tangostationcontrol.common.frequency_bands import bands from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse -from tangostationcontrol.devices.sdp.common import weight_to_complex -from tangostationcontrol.devices.sdp.sdp import SDP from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy from .base import AbstractTestBases class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): + HBA_ANTENNA_NAMES = [ + "H0", + "H1", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9", + "H10", + "H11", + "H12", + "H13", + "H14", + "H15", + "H16", + "H17", + "H18", + "H19", + "H20", + "H21", + "H22", + "H23", + "H24", + "H25", + "H26", + "H27", + "H28", + "H29", + "H30", + "H31", + "H32", + "H33", + "H34", + "H35", + "H36", + "H37", + "H38", + "H39", + "H40", + "H41", + "H42", + "H43", + "H44", + "H45", + "H46", + "H47", + ] + def setUp(self): + self.stationmanager_proxy = self.setup_stationmanager_proxy() super().setUp("STAT/AntennaField/HBA") self.proxy.put_property( { @@ -80,6 +131,14 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): sdp_proxy.warm_boot() return sdp_proxy + def setup_stationmanager_proxy(self): + """Setup StationManager""" + stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1") + stationmanager_proxy.off() + stationmanager_proxy.boot() + self.assertEqual(stationmanager_proxy.state(), DevState.ON) + return stationmanager_proxy + def test_property_recv_devices_has_one_receiver(self): result = self.proxy.get_property("RECV_devices") self.assertSequenceEqual(result["RECV_devices"], ["STAT/RECV/1"]) @@ -332,8 +391,9 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): def test_frequency_band(self): # Test whether changes in frequency band propagate properly. + VALID_MODI = ["HBA_110_190", "HBA_170_230", "HBA_210_250"] - for antenna_type in ("LBA", "HBA"): + for antenna_type in ["HBA"]: properties = { "Antenna_Type": [antenna_type], "Control_to_RECV_mapping": [1, 1, 1, 0] @@ -347,16 +407,18 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): antennafield_proxy.put_property(properties) antennafield_proxy.boot() - # clear downstream settings - self.recv_proxy.write_attribute( - "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu - ) - self.sdp_proxy.write_attribute("nyquist_zone_RW", [[0] * S_pn] * N_pn) + for band in [b for b in bands.values() if b.name in VALID_MODI]: + # clear downstream settings + self.recv_proxy.write_attribute( + "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu + ) + self.sdp_proxy.write_attribute("nyquist_zone_RW", [[0] * S_pn] * N_pn) - for band in [b for b in bands.values() if b.name.startswith(antenna_type)]: antennafield_proxy.write_attribute( "Frequency_Band_RW", [band.name] * DEFAULT_N_HBA_TILES ) + # Wait for Tango attributes updating + time.sleep(1) # test whether clock propagated correctly numpy.testing.assert_equal( @@ -429,7 +491,8 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): def test_calibrate_sdp(self): calibration_properties = { - "Antenna_Type": ["LBA"], + "Antenna_Type": ["HBA"], + "Antenna_Names": self.HBA_ANTENNA_NAMES, "Antenna_Cables": ["50m", "80m"] * (DEFAULT_N_HBA_TILES // 2), "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + [-1, -1] * (DEFAULT_N_HBA_TILES - 2), @@ -468,39 +531,28 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): # antenna #1 is longest, so should have delay 0 self.assertEqual(0, signal_input_samples_delay[0, 0]) - # the subband weights depend on the frequency of the subband, - # and on the exact delay and loss differences between the cables. - # rather than repeating the computations from the code, - # we implement this as a regression test. - subband_weights = self.sdp_proxy.FPGA_subband_weights_RW.reshape( - N_pn, A_pn, N_pol, N_subbands - ) + def test_calibration_table(self): + """Test whether calibration table are correctly retrieved and reshaped""" + calibration_properties = { + "Antenna_Type": ["HBA"], + "Antenna_Names": self.HBA_ANTENNA_NAMES, + "Control_to_RECV_mapping": [1, 1, 1, 0] + + [-1, -1] * (DEFAULT_N_HBA_TILES - 2), + "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + + [-1, -1] * (DEFAULT_N_HBA_TILES - 2), + } - def to_complex(weight): - return weight_to_complex(weight, SDP.SUBBAND_UNIT_WEIGHT) + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(calibration_properties) + antennafield_proxy.warm_boot() - # weight should be equal for both polarisations, different per antenna - self.assertAlmostEqual( - 0.929 + 0j, to_complex(subband_weights[0, 0, 0, 0]), places=3 - ) - self.assertAlmostEqual( - 0.309 + 0.876j, to_complex(subband_weights[0, 0, 0, 511]), places=3 - ) - self.assertAlmostEqual( - 0.929 + 0j, to_complex(subband_weights[0, 0, 1, 0]), places=3 - ) - self.assertAlmostEqual( - 0.309 + 0.876j, to_complex(subband_weights[0, 0, 1, 511]), places=3 - ) - self.assertAlmostEqual( - 0.989 + 0j, to_complex(subband_weights[0, 1, 0, 0]), places=3 - ) - self.assertAlmostEqual( - 0.883 - 0.444j, to_complex(subband_weights[0, 1, 0, 511]), places=3 - ) - self.assertAlmostEqual( - 0.989 + 0j, to_complex(subband_weights[0, 1, 1, 0]), places=3 - ) - self.assertAlmostEqual( - 0.883 - 0.444j, to_complex(subband_weights[0, 1, 1, 511]), places=3 + calibration_table = antennafield_proxy.Calibration_SDP_Subband_Weights_250MHz_R + + # test whether the shape is correct + shape = calibration_table.shape + self.assertEqual( + shape, + (antennafield_proxy.nr_antennas_R, N_pol * N_subbands * VALUES_PER_COMPLEX), + f"Wrong shape, got {shape}", ) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py index d9b88a20c69e5edae4cf5ce7f0d918a5b8e65c93..af4982431e3a50b5dc01f765fc679b7f4f738962 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py @@ -195,6 +195,14 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase): tilebeam_proxy.set_defaults() return tilebeam_proxy + def setup_stationmanager_proxy(self): + """Setup StationManager""" + stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1") + stationmanager_proxy.off() + stationmanager_proxy.boot() + self.assertEqual(stationmanager_proxy.state(), DevState.ON) + return stationmanager_proxy + def test_init_valid(self): """Initialize an observation with valid JSON""" @@ -266,6 +274,7 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase): def test_apply_antennafield_settings(self): """Test that attributes antenna_mask and filter are correctly applied""" + self.setup_stationmanager_proxy() self.setup_recv_proxy() antennafield_proxy = self.setup_antennafield_proxy() antennafield_proxy.ANT_mask_RW = [ diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py index b4a0467d9ca09652904b206a285aff3246290838..62850a0cb53e7908f56d40eee19b2f77fecc48a0 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py @@ -6,6 +6,7 @@ import json import time import numpy +from tango import DevState from tangostationcontrol.common.constants import ( DEFAULT_N_HBA_TILES, MAX_ANTENNA, @@ -31,18 +32,27 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): ).flatten() def setUp(self): + """Setup Tilebeam""" super().setUp("STAT/TileBeam/HBA") def setup_recv_proxy(self): - # setup RECV + """Setup RECV""" recv_proxy = TestDeviceProxy("STAT/RECV/1") recv_proxy.off() recv_proxy.warm_boot() recv_proxy.set_defaults() return recv_proxy + def setup_stationmanager_proxy(self): + """Setup StationManager""" + stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1") + stationmanager_proxy.off() + stationmanager_proxy.boot() + self.assertEqual(stationmanager_proxy.state(), DevState.ON) + return stationmanager_proxy + def setup_antennafield_proxy(self): - # setup AntennaFieldy + """Setup AntennaField""" antennafield_proxy = TestDeviceProxy("STAT/AntennaField/HBA") control_mapping = [[1, i] for i in range(DEFAULT_N_HBA_TILES)] antenna_qualities = numpy.array([AntennaQuality.OK] * MAX_ANTENNA) @@ -64,6 +74,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): def test_delays_dims(self): """Verify delays are retrieved with correct dimensions""" + self.setup_stationmanager_proxy() self.setup_recv_proxy() self.setup_antennafield_proxy() @@ -76,6 +87,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): def test_set_pointing(self): """Verify if set pointing procedure is correctly executed""" + self.setup_stationmanager_proxy() antennafield_proxy = self.setup_antennafield_proxy() # setup BEAM @@ -102,6 +114,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): # self.assertFalse((delays_r1==delays_r2).all()) def test_pointing_to_zenith(self): + self.setup_stationmanager_proxy() self.setup_recv_proxy() # setup AntennaField as well antennafield_proxy = self.setup_antennafield_proxy() @@ -127,6 +140,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): ) def test_pointing_across_horizon(self): + self.setup_stationmanager_proxy() self.setup_recv_proxy() # setup AntennaField as well antennafield_proxy = self.setup_antennafield_proxy() @@ -168,6 +182,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): ) def test_delays_same_as_LOFAR_ref_pointing(self): + self.setup_stationmanager_proxy() self.setup_recv_proxy() # setup AntennaField as well antennafield_proxy = self.setup_antennafield_proxy() @@ -212,6 +227,7 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): ) def test_tilebeam_tracking(self): + self.setup_stationmanager_proxy() self.setup_recv_proxy() self.setup_antennafield_proxy() self.proxy.warm_boot() diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py index dc16f6e3c9a35a2aa355e4a2efc770c6d860b1d3..be82434b550e934009bdc8ea0922826aee705e82 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py @@ -15,6 +15,7 @@ from tangostationcontrol.test.devices.test_observation_base import TestObservati class TestObservation(base.IntegrationTestCase): def setUp(self): + self.setup_stationmanager_proxy() self.observation_control_proxy = TestDeviceProxy("STAT/ObservationControl/1") self.observation_control_proxy.off() self.observation_control_proxy.warm_boot() @@ -32,6 +33,14 @@ class TestObservation(base.IntegrationTestCase): proxy.off() proxy.warm_boot() + def setup_stationmanager_proxy(self): + """Setup StationManager""" + stationmanager_proxy = TestDeviceProxy("STAT/StationManager/1") + stationmanager_proxy.off() + stationmanager_proxy.boot() + self.assertEqual(stationmanager_proxy.state(), DevState.ON) + return stationmanager_proxy + def test_observation(self): """Test of the observation_wrapper class basic functionality""" diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/prometheus/test_tango_prometheus_client.py b/tangostationcontrol/tangostationcontrol/integration_test/default/prometheus/test_tango_prometheus_client.py index cc439356a316b5400fd235a9f7b254f0ca316942..a872f1c51d579069b610ad655425cfc13638e652 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/prometheus/test_tango_prometheus_client.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/prometheus/test_tango_prometheus_client.py @@ -150,4 +150,4 @@ class TestPrometheusClient(BaseIntegrationTestCase): numpy.testing.assert_equal(samples_values, expected_attr_values.flatten()) # Test scraping metrics total_scraping_time = scraping_metrics.samples[-1].value - self.assertLess(total_scraping_time, 10) # Set acceptable scraping time ? + self.assertLess(total_scraping_time, 11) # Set acceptable scraping time ?