diff --git a/README.md b/README.md index 738ff76832c6a64a20c60fe2f6bbd7bb765a7654..eb7c1df8e6016ef29bd1e4f0358382f02eb43adc 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,9 @@ tox -e debug tests.requests.test_prometheus ``` ## Releasenotes +- 0.15.6 - Represent BSTs in 488x2 arrays + - l2ss-statistics-writer: Fix syntax error in printing help + - More resilience against errors when interfacing with Tango - 0.15.5 - Fix rounding of dataset timestamps, dropped timezone - 0.15.4 - Fix parsing of BST packets - 0.15.3 - BSTs: added digital pointing direction, and subband selection diff --git a/VERSION b/VERSION index 1282fff53bfabc38e1d779d4eec991b2285f59da..c619394525b5035a6ad50f29d726abf895325a56 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.15.5 +0.15.6 diff --git a/lofar_station_client/statistics/collector.py b/lofar_station_client/statistics/collector.py index 13a40d5a2b658ece30ecb6e7a8133a3ec75deaee..27e191fa996417509f45951202aaf0193012ab84 100644 --- a/lofar_station_client/statistics/collector.py +++ b/lofar_station_client/statistics/collector.py @@ -452,10 +452,6 @@ class BSTCollector(StatisticsCollector): # beamlets = 488 * 2 for the x and y polarisations MAX_BEAMLETS = 488 - # We support only one beamset for now, to avoid - # unnecessary explosion of the matrices - MAX_BEAMSETS = 1 - def _default_parameters(self): defaults = super()._default_parameters() @@ -474,14 +470,15 @@ class BSTCollector(StatisticsCollector): ), # Last value array we've constructed out of the packets "bst_values": numpy.zeros( - (self.MAX_BEAMSETS, self.MAX_BEAMLETS, N_pol), + (self.MAX_BEAMLETS, N_pol), dtype=numpy.uint64, ), "bst_timestamps": numpy.zeros( - (self.MAX_BEAMSETS,), dtype=numpy.float64 + (self.MAX_FPGAS,), + dtype=numpy.float64, ), "integration_intervals": numpy.zeros( - (self.MAX_BEAMSETS,), + (self.MAX_FPGAS,), dtype=numpy.float32, ), } @@ -494,15 +491,18 @@ class BSTCollector(StatisticsCollector): fpga_nr = self.gn_index_to_fpga_nr(fields.gn_index) - # To get the block_index we floor divide this beamlet_index by the max amount - # of beamlets per block - beamset_index = fields.beamlet_index // self.MAX_BEAMLETS + # number of beamlets in the packet + beamlets = fields.payload() + nr_beamlets = beamlets.shape[0] + first_beamlet = fields.beamlet_index + last_beamlet = first_beamlet + nr_beamlets # determine which input this packet contains data for - if beamset_index >= self.MAX_BEAMSETS: + if last_beamlet > self.MAX_BEAMLETS: # packet describes an input that is out of bounds for us raise ValueError( - f"Packet describes beamlet {fields.beamlet_index}, but we are limited " + f"Packet describes {nr_beamlets} beamlets starting at " + f"{fields.beamlet_index}, but we are limited " f"to describing MAX_BEAMLETS={self.MAX_BEAMLETS}" ) @@ -515,12 +515,10 @@ class BSTCollector(StatisticsCollector): # process the packet self.parameters["nof_valid_payloads"][fpga_nr] += numpy.uint64(1) - self.parameters["bst_values"][beamset_index][ - : self.MAX_BEAMLETS : - ] = fields.payload() - self.parameters["bst_timestamps"][beamset_index] = numpy.float64( + self.parameters["bst_values"][first_beamlet:last_beamlet] = beamlets + self.parameters["bst_timestamps"][fpga_nr] = numpy.float64( fields.timestamp().timestamp() ) self.parameters["integration_intervals"][ - beamset_index + fpga_nr ] = fields.integration_interval() diff --git a/lofar_station_client/statistics/writer/entry.py b/lofar_station_client/statistics/writer/entry.py index 5380f59038997f2a7a0d124c4074cf872a9421d3..3b15ca9c22c882d5891c0d8396af3926724fee96 100644 --- a/lofar_station_client/statistics/writer/entry.py +++ b/lofar_station_client/statistics/writer/entry.py @@ -107,7 +107,7 @@ def _create_parser(): type=str, choices=["LBA", "HBA", "HBA0", "HBA1"], default="LBA", - help="Antenna field to collect data for (default: %(default))", + help="Antenna field to collect data for (default: %(default)s)", ) return parser @@ -273,6 +273,11 @@ def main(): ) devices["tilebeam"] = tilebeam_device + sdpfirmware_device = _get_tango_device( + tango_disabled, host, f"STAT/SDPFirmware/{args.antennafield}" + ) + devices["sdpfirmware"] = sdpfirmware_device + sdp_device = _get_tango_device( tango_disabled, host, f"STAT/SDP/{args.antennafield}" ) diff --git a/lofar_station_client/statistics/writer/hdf5.py b/lofar_station_client/statistics/writer/hdf5.py index ad3858a2d7d7cc902d55864089b45dbf845be898..f8dadd3c0fd7d0e0ce7fce881efd27d00ff44042 100644 --- a/lofar_station_client/statistics/writer/hdf5.py +++ b/lofar_station_client/statistics/writer/hdf5.py @@ -167,6 +167,7 @@ class HDF5Writer(ABC): # Set devices if any, defaults to None self.antennafield_device = devices["antennafield"] + self.sdpfirmware_device = devices["sdpfirmware"] self.sdp_device = devices["sdp"] self.tilebeam_device = devices["tilebeam"] self.digitalbeam_device = devices["digitalbeam"] @@ -188,7 +189,7 @@ class HDF5Writer(ABC): self.antenna_sdp_mapping = ( self.antennafield_device.Antenna_to_SDP_Mapping_R ) - except DevFailed: + except (DevFailed, AttributeError): logger.exception( "Failed to read from %s", devices["antennafield"].name() ) @@ -237,20 +238,20 @@ class HDF5Writer(ABC): self.antennafield_device.Antenna_Reference_ITRF_R ) # noqa self.file.frequency_band = self.antennafield_device.Frequency_Band_RW - except DevFailed: + except (DevFailed, AttributeError): logger.exception( "Failed to read from %s", self.antennafield_device.name() ) - if self.sdp_device: + if self.sdpfirmware_device: try: self.file.fpga_firmware_version = ( - self.sdp_device.FPGA_firmware_version_R + self.sdpfirmware_device.FPGA_firmware_version_R ) self.file.fpga_hardware_version = ( - self.sdp_device.FPGA_hardware_version_R + self.sdpfirmware_device.FPGA_hardware_version_R ) - except DevFailed: + except (DevFailed, AttributeError): logger.exception("Failed to read from %s", self.sdp_device.name()) @abstractmethod @@ -400,7 +401,7 @@ class HDF5Writer(ABC): try: matrix.hbat_pwr_on = self.antennafield_device.HBAT_PWR_on_R matrix.frequency_band = self.antennafield_device.Frequency_Band_R - except DevFailed: + except (DevFailed, AttributeError): logger.exception( "Failed to read from %s", self.antennafield_device.name() ) @@ -410,7 +411,7 @@ class HDF5Writer(ABC): nyquist_zones = self.sdp_device.nyquist_zone_RW fpga_spectral_inversion = self.sdp_device.FPGA_spectral_inversion_R subband_frequencies = self.sdp_device.subband_frequency_R - except DevFailed: + except (DevFailed, AttributeError): logger.exception( "Failed to read from %s", self.antennafield_device.name() ) @@ -459,7 +460,7 @@ class HDF5Writer(ABC): matrix.tile_beam_tracking_enabled = ( self.tilebeam_device.Tracking_enabled_R ) - except DevFailed: + except (DevFailed, AttributeError): logger.exception("Failed to read from %s", self.tilebeam_device.name()) return matrix @@ -615,7 +616,7 @@ class BstHdf5Writer(HDF5Writer): self.digitalbeam_device.Tracking_enabled_R ) matrix.subbands = self.digitalbeam_device.subband_select_RW - except DevFailed: + except (DevFailed, AttributeError): logger.exception( "Failed to read from %s", self.digitalbeam_device.name() ) diff --git a/tests/statistics/test_collector.py b/tests/statistics/test_collector.py index d8d799e2784d6d1e03105627e01d56091f015016..c9644a5579e285103b0e8a3c117ddb2f288bb8ea 100644 --- a/tests/statistics/test_collector.py +++ b/tests/statistics/test_collector.py @@ -383,7 +383,7 @@ class TestBSTCollector(base.TestCase): ) numpy.testing.assert_array_equal( - numpy.zeros((1, 488, 2), dtype=numpy.uint64), + numpy.zeros((488, 2), dtype=numpy.uint64), collector.parameters["bst_values"], ) @@ -419,7 +419,8 @@ class TestBSTCollector(base.TestCase): # a valid packet with a payload error packet = ( b"B\x05\x00\x00\x00\x00\x03\x87\x14\x00\x00\x02\xfa\xf0\x00\x00\x00\x00" - b"\x00\x08\x03\xd0\x14\x00\x00\x01\xa3\xc7\xb1\x9e\x8e" + 1952 * b"\0\0\0\0" + b"\x00\x08\x03\xd0\x14\x00\x00\x01%\xa3\xc7\xb1\x9e\x8e" + + 1952 * b"\0\0\0\0" ) # parse it ourselves to extract info nicely diff --git a/tests/statistics/test_writer.py b/tests/statistics/test_writer.py index d9ec91ecd92e91cf1dfe835f098e2384a2957776..45da4c054c5330763d55b8431dc2bab326cd1896 100644 --- a/tests/statistics/test_writer.py +++ b/tests/statistics/test_writer.py @@ -22,6 +22,7 @@ from tests.test_devices import ( FakeDigitalBeamDeviceProxy, FakeTileBeamDeviceProxy, FakeSDPDeviceProxy, + FakeSDPFirmwareDeviceProxy, FakeStationManagerDeviceProxy, ) from tests import base @@ -42,6 +43,8 @@ class TestStatisticsReaderWriter(base.TestCase): return FakeStationManagerDeviceProxy if device_name == "STAT/SDP/HBA": return FakeSDPDeviceProxy(device_name) + if device_name == "STAT/SDPFirmware/HBA": + return FakeSDPFirmwareDeviceProxy(device_name) raise ValueError( f"Device not mocked, and thus not available in this test: {device_name}" ) @@ -58,6 +61,8 @@ class TestStatisticsReaderWriter(base.TestCase): return FakeStationManagerDeviceProxy if device_name == "STAT/SDP/HBA": return FakeSDPDeviceProxy(device_name) + if device_name == "STAT/SDPFirmware/HBA": + return FakeSDPFirmwareDeviceProxy(device_name) raise ValueError( f"Device not mocked, and thus not available in this test: {device_name}" ) diff --git a/tests/test_devices.py b/tests/test_devices.py index fc755728686925a8c3cbe23babeada325428489d..d0e0741623642adebf4b0568592e36eb5aebc42e 100644 --- a/tests/test_devices.py +++ b/tests/test_devices.py @@ -326,11 +326,25 @@ class FakeOffAntennaFieldDeviceProxy: raise DevFailed("Device is off") -class FakeSDPDeviceProxy: - """DeviceProxy that mocks access to an DigitalBeam device.""" +class FakeSDPFirmwareDeviceProxy: + """DeviceProxy that mocks access to an SDPFirmware device.""" FPGA_firmware_version_R = ["firmware version"] * N_pn FPGA_hardware_version_R = ["hardware version"] * N_pn + + def __init__(self, name): + self._name = name + + def name(self): + return self._name + + def __getattr__(self, attrname): + return getattr(self, attrname) + + +class FakeSDPDeviceProxy: + """DeviceProxy that mocks access to an SDP device.""" + nyquist_zone_RW = numpy.array([[0] * S_pn] * N_pn, dtype=numpy.int64) FPGA_spectral_inversion_R = numpy.array([[False] * S_pn] * N_pn, dtype=bool) subband_frequency_R = numpy.array(