diff --git a/README.md b/README.md index b811fa3dbeb7eb07e3f741e6a86a656055b3e9b5..9dc7c53a6fe28374b864a5b103c053a3326d51db 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,8 @@ Next change the version in the following places: # Release Notes +* 0.30.2 Add XST/SST settings to Observation specifications + Fixed dimensionality of xst.FPGA_subband_select_R(W) * 0.30.1 Remove deprecated FPGA_beamlet_output_nof_beamlets Fix prometheus metric for counting calibrations Removed ALARM and DISABLED states diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION index 1a44cad74d6555dd4d4557871257966853237a86..0f72177373685866f1681484947dc95375e4d750 100644 --- a/tangostationcontrol/VERSION +++ b/tangostationcontrol/VERSION @@ -1 +1 @@ -0.30.1 +0.30.2 diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py index 40c06aab247df55a7a145591cde251e1d4fff946..11f915d7ff4486dec16f13f8c68acabb95ee1e6c 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py @@ -111,6 +111,8 @@ class TestDeviceObservationField(AbstractTestBases.TestDeviceBase): self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True) self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0") self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0") + self.sst_proxy = self.setup_proxy("STAT/SST/HBA0") + self.xst_proxy = self.setup_proxy("STAT/XST/HBA0") self.antennafield_proxy = self.setup_proxy( self.antennafield_name, cb=self.antennafield_configure ) diff --git a/tangostationcontrol/integration_test/default/devices/test_observation_client.py b/tangostationcontrol/integration_test/default/devices/test_observation_client.py index 3b46b8319110f5acc9404af44e3f5e1765e21ad1..58576f3c24f14ef26f98535e77465f5e894b3656 100644 --- a/tangostationcontrol/integration_test/default/devices/test_observation_client.py +++ b/tangostationcontrol/integration_test/default/devices/test_observation_client.py @@ -36,6 +36,8 @@ class TestObservation(base.IntegrationTestCase): "STAT/DigitalBeam/HBA0", "STAT/TileBeam/HBA0", "STAT/AFH/HBA0", + "STAT/SST/HBA0", + "STAT/XST/HBA0", ]: proxy = TestDeviceProxy(device) proxy.off() diff --git a/tangostationcontrol/tangostationcontrol/common/constants.py b/tangostationcontrol/tangostationcontrol/common/constants.py index 0331b8ee0057f8f95dceab1fb68462d71d5994a0..45684b65576b69da4972bdc098bfcfe1e3a70451 100644 --- a/tangostationcontrol/tangostationcontrol/common/constants.py +++ b/tangostationcontrol/tangostationcontrol/common/constants.py @@ -66,7 +66,7 @@ CLK_200_MHZ = 200_000_000 CLK_160_MHZ = 160_000_000 # Maximum number of subbands for which we collect XSTs simultaneously -MAX_PARALLEL_SUBBANDS = 8 +MAX_PARALLEL_SUBBANDS = 7 # Expected block for XST's BLOCK_LENGTH = 12 # Complex values are (real, imag). diff --git a/tangostationcontrol/tangostationcontrol/configuration/__init__.py b/tangostationcontrol/tangostationcontrol/configuration/__init__.py index b567fc35750358442fa60da4e845980744541f72..8b7cbce6a59b3e648cd50e82697dff0f8cb21fb7 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/__init__.py +++ b/tangostationcontrol/tangostationcontrol/configuration/__init__.py @@ -8,6 +8,8 @@ from .observation_settings import ObservationSettings from .observation_field_settings import ObservationFieldSettings from .pointing import Pointing from .sap import Sap +from .sst import SST +from .xst import XST __all__ = [ "ObservationSettings", @@ -17,4 +19,6 @@ __all__ = [ "HBA", "Dithering", "REGISTRY", + "SST", + "XST", ] diff --git a/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py b/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py index 92daf15a46e8bc444263c2380936c7f072ab56b3..c3327ca36041acd21319a748648b4eb9a2366cca 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py +++ b/tangostationcontrol/tangostationcontrol/configuration/_json_parser.py @@ -14,6 +14,8 @@ def _from_json_hook_t(primary: Type): ObservationFieldSettings, HBA, Dithering, + SST, + XST, ) def actual_hook(json_dct): @@ -21,6 +23,8 @@ def _from_json_hook_t(primary: Type): primary_ex = None # Order is critical, must match inheritance, deepest layers first for t in [ + SST, + XST, Pointing, Sap, HBA, diff --git a/tangostationcontrol/tangostationcontrol/configuration/_schemas.py b/tangostationcontrol/tangostationcontrol/configuration/_schemas.py index 6a6678e0876fca671519552d76a20f5ee61e352e..ebfa2d0f057f29ec1398e9ee727824a842f604a6 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/_schemas.py +++ b/tangostationcontrol/tangostationcontrol/configuration/_schemas.py @@ -14,8 +14,12 @@ from referencing.jsonschema import SchemaRegistry as _SchemaRegistry def _schemas(): - for schema in files(__package__).joinpath("schemas").iterdir(): - contents = json.loads(schema.read_text(encoding="utf-8")) + for schema in files(__package__).joinpath("schemas").glob("*.json"): + try: + contents = json.loads(schema.read_text(encoding="utf-8")) + except json.decoder.JSONDecodeError as ex: + raise ValueError(f"Error decoding JSON schema {schema}") from ex + yield Resource.from_contents(contents) diff --git a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py index a8e94dc8a140765fe32b7463d6f6e8a5c7e64ea8..18c06e82f66645867dd724622cb9ec96aaf8aaf9 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py +++ b/tangostationcontrol/tangostationcontrol/configuration/configuration_base.py @@ -33,6 +33,8 @@ class _ConfigurationBase(ABC): """Class name to json schema file conversion name""" cls_name = cls_name.replace("HBA", "hba") cls_name = cls_name.replace("LBA", "lba") + cls_name = cls_name.replace("SST", "sst") + cls_name = cls_name.replace("XST", "xst") return re.sub(r"(?<!^)(?=[A-Z])", "-", cls_name).lower() @classmethod @@ -53,7 +55,11 @@ class _ConfigurationBase(ABC): pass def __str__(self): - return json.dumps(dict(self), ensure_ascii=False) + try: + return json.dumps(dict(self), ensure_ascii=False) + except TypeError: + # This happens if we want to dump a type that cannot be represented in JSON + return str(dict(self)) def __repr__(self): return self.__str__() @@ -76,10 +82,14 @@ class _ConfigurationBase(ABC): @classmethod def from_json(cls: Type[T], data: str) -> T: - s = json.loads(data, object_hook=_from_json_hook_t(cls)) + try: + s = json.loads(data, object_hook=_from_json_hook_t(cls)) + except json.decoder.JSONDecodeError as ex: + raise ValueError(f"Error decoding JSON {data}") from ex + if not isinstance(s, cls): raise ValidationError( f"Unexpected type: expected <{cls.__class__.__name__}>, got " - f"<{type(s).__name__}>" + f"<{type(s).__name__}>: {s}" ) return s diff --git a/tangostationcontrol/tangostationcontrol/configuration/hba.py b/tangostationcontrol/tangostationcontrol/configuration/hba.py index ecd65aecf06c074edf124a194fc0ba7d2f5c91eb..274fd9c126458c1b3af3b881ee5aee40506cf3ec 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/hba.py +++ b/tangostationcontrol/tangostationcontrol/configuration/hba.py @@ -9,7 +9,7 @@ class HBA(_ConfigurationBase): def __init__( self, tile_beam: Pointing, - DAB_filter: bool | None, + DAB_filter: bool | None = None, element_selection: str | None = "ALL", ): self.tile_beam = tile_beam diff --git a/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py b/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py index d862468ac193d8184bbdf12b1b29b7e6a10e86c7..51349377d9859aa8ecba6c33ecc6f0ed8b442766 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py +++ b/tangostationcontrol/tangostationcontrol/configuration/observation_field_settings.py @@ -8,6 +8,8 @@ from tangostationcontrol.configuration.configuration_base import _ConfigurationB from tangostationcontrol.configuration.dithering import Dithering from tangostationcontrol.configuration.hba import HBA from tangostationcontrol.configuration.sap import Sap +from tangostationcontrol.configuration.sst import SST +from tangostationcontrol.configuration.xst import XST class ObservationFieldSettings(_ConfigurationBase): @@ -24,6 +26,8 @@ class ObservationFieldSettings(_ConfigurationBase): first_beamlet: int = 0, lead_time: float | None = None, dithering: Dithering | None = None, + SST: SST | None = None, + XST: XST | None = None, ): self.observation_id = observation_id self.start_time = self._parse_and_convert_datetime(start_time) @@ -36,6 +40,8 @@ class ObservationFieldSettings(_ConfigurationBase): self.first_beamlet = first_beamlet self.lead_time = lead_time self.dithering = dithering + self.SST = SST + self.XST = XST @staticmethod def _parse_and_convert_datetime(time: str | datetime | None): @@ -68,6 +74,10 @@ class ObservationFieldSettings(_ConfigurationBase): yield "lead_time", self.lead_time if self.dithering is not None: yield "dithering", dict(self.dithering) + if self.SST is not None: + yield "SST", dict(self.SST) + if self.XST is not None: + yield "SST", dict(self.XST) @staticmethod def to_object(json_dct) -> "ObservationFieldSettings": @@ -83,4 +93,6 @@ class ObservationFieldSettings(_ConfigurationBase): json_dct.get("first_beamlet", 0), json_dct.get("lead_time"), json_dct.get("dithering"), + json_dct.get("SST"), + json_dct.get("XST"), ) diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json index 98d90868c0f863a5b4f355211d3eb7f12e531ea4..bfa84e83d2291edec23c2e00f5e1f42c7d53a6cf 100644 --- a/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json +++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/observation-field-settings.json @@ -82,6 +82,12 @@ }, "HBA": { "$ref": "hba" + }, + "sst": { + "$ref": "sst" + }, + "xst": { + "$ref": "xst" } } } diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/sst.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/sst.json new file mode 100644 index 0000000000000000000000000000000000000000..7cd50724c6c30e7917216929a08033756b24b20c --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/sst.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "sst", + "type": "object", + "required": [ + "subbands_calibrated" + ], + "properties": { + "subbands_calibrated": { + "type": "boolean", + "default": true + } + } +} diff --git a/tangostationcontrol/tangostationcontrol/configuration/schemas/xst.json b/tangostationcontrol/tangostationcontrol/configuration/schemas/xst.json new file mode 100644 index 0000000000000000000000000000000000000000..27cf2ec7849e36aa84b53a56e60a9bae3224126d --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/configuration/schemas/xst.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "xst", + "type": "object", + "required": [ + "subbands", + "integration_interval" + ], + "properties": { + "subbands": { + "type": "array", + "minItems": 0, + "maxItems": 7, + "items": { + "type": "number", + "minimum": 0, + "maximum": 488 + } + }, + "subbands_step": { + "type": "number", + "default": 0, + "minimum": 0, + "maximum": 488 + }, + "integration_interval": { + "type": "number", + "default": 1.0, + "minimum": 0.1, + "maximum": 10.7 + } + } +} diff --git a/tangostationcontrol/tangostationcontrol/configuration/sst.py b/tangostationcontrol/tangostationcontrol/configuration/sst.py new file mode 100644 index 0000000000000000000000000000000000000000..cfd1237df19286d03c1628ea082e6475e3def73f --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/configuration/sst.py @@ -0,0 +1,21 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from tangostationcontrol.configuration.configuration_base import _ConfigurationBase + + +class SST(_ConfigurationBase): + def __init__( + self, + subbands_calibrated: bool, + ): + self.subbands_calibrated = subbands_calibrated + + def __iter__(self): + yield "subbands_calibrated", self.subbands_calibrated + + @staticmethod + def to_object(json_dct) -> "SST": + return SST( + json_dct["subbands_calibrated"], + ) diff --git a/tangostationcontrol/tangostationcontrol/configuration/xst.py b/tangostationcontrol/tangostationcontrol/configuration/xst.py new file mode 100644 index 0000000000000000000000000000000000000000..7d14bccef81fb47b82e1872a53428a17c4f91a64 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/configuration/xst.py @@ -0,0 +1,33 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from typing import List + +from tangostationcontrol.configuration.configuration_base import _ConfigurationBase + + +class XST(_ConfigurationBase): + def __init__( + self, + integration_interval: float, + subbands: List[int], + subbands_step: int | None = None, + ): + self.subbands = subbands + self.subbands_step = subbands_step + self.integration_interval = integration_interval + + def __iter__(self): + yield "integration_interval", self.integration_interval + yield "subbands", self.subbands + + if self.subbands_step is not None: + yield "subbands_step", self.subbands_step + + @staticmethod + def to_object(json_dct) -> "XST": + return XST( + json_dct["integration_interval"], + json_dct["subbands"], + json_dct.get("subbands_step"), + ) diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_field.py b/tangostationcontrol/tangostationcontrol/devices/observation_field.py index df970322aa075ff9522aa8d8fccac13217ea6ba5..870f14b9685b42209cc531d0fec3ac01debe9b1a 100644 --- a/tangostationcontrol/tangostationcontrol/devices/observation_field.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation_field.py @@ -6,7 +6,7 @@ from datetime import datetime import logging from itertools import chain from time import time -from typing import Optional +from typing import Optional, List import numpy from jsonschema.exceptions import ValidationError @@ -18,8 +18,10 @@ from tangostationcontrol.common.constants import ( DEFAULT_METRICS_POLLING_PERIOD, DEFAULT_POLLING_PERIOD, MAX_ANTENNA, + MAX_PARALLEL_SUBBANDS, N_beamlets_ctrl, N_elements, + N_pn, N_point_prop, N_pol, ) @@ -254,6 +256,52 @@ class ObservationField(LOFARDevice): except AttributeError: return "ALL" + @attribute( + doc="", + dtype=bool, + fisallowed="is_attribute_access_allowed", + ) + def SST_subbands_calibrated_R(self): + try: + return self._observation_field_settings.SST.subbands_calibrated + except AttributeError: + return True + + @attribute( + doc="The indices of the subbands to emit every integration interval.", + dtype=(numpy.uint32,), + max_dim_x=MAX_PARALLEL_SUBBANDS, + ) + def XST_subbands_R(self): + try: + return numpy.array( + self._observation_field_settings.XST.subbands, + dtype=numpy.uint32, + ) + except AttributeError: + return numpy.array([], dtype=numpy.uint32) + + @attribute( + doc="The subband indices are increased with this index every interval, causing different subbands to be emitted.", + dtype=numpy.uint32, + ) + def XST_subbands_step_R(self): + try: + return self._observation_field_settings.XST.subbands_step + except AttributeError: + return 0 + + @attribute( + doc="", + dtype=numpy.float64, + fisallowed="is_attribute_access_allowed", + ) + def XST_integration_interval_R(self): + try: + return self._observation_field_settings.XST.integration_interval + except AttributeError: + return 1.0 + observation_field_settings_RW = attribute( dtype=str, access=AttrWriteType.READ_WRITE ) @@ -372,6 +420,14 @@ class ObservationField(LOFARDevice): f"{util.get_ds_inst_name()}/TileBeam/{antennafield}" ) + self.sst_proxy = create_device_proxy( + f"{util.get_ds_inst_name()}/SST/{antennafield}" + ) + + self.xst_proxy = create_device_proxy( + f"{util.get_ds_inst_name()}/XST/{antennafield}" + ) + @log_exceptions() def _start_observation(self): """Configure the station for this observation and antenna field.""" @@ -438,6 +494,31 @@ class ObservationField(LOFARDevice): element_selection ) + # Configure SST + subbands_calibrated = self.read_attribute("SST_subbands_calibrated_R") + self.sst_proxy.FPGA_sst_offload_weighted_subbands_RW = [ + subbands_calibrated + ] * N_pn + + # Toggle statistics to activate new settings + self.sst_proxy.power_hardware_off() + self.sst_proxy.power_hardware_on() + + # Configure XST + subbands = self.read_attribute("XST_subbands_R") + subbands_step = self.read_attribute("XST_subbands_step_R") + integration_interval = self.read_attribute("XST_integration_interval_R") + + self.xst_proxy.FPGA_xst_subband_select_RW = self._apply_xst_subband_select( + subbands.tolist(), + subbands_step, + ) + self.xst_proxy.FPGA_xst_integration_interval_RW = [integration_interval] * N_pn + + # Toggle statistics to activate new settings + self.xst_proxy.power_hardware_off() + self.xst_proxy.power_hardware_on() + @log_exceptions() def _stop_observation(self): """Tear down station resources we used.""" @@ -470,6 +551,21 @@ class ObservationField(LOFARDevice): """Return whether this observation should control a TileBeam device.""" return self._observation_field_settings.antenna_field.startswith("HBA") + def _apply_xst_subband_select( + self, subbands: List[int], subbands_step: int + ) -> numpy.ndarray: + + if len(subbands) > MAX_PARALLEL_SUBBANDS: + raise ValueError( + f"Requested more than {MAX_PARALLEL_SUBBANDS} subbands for the XSTs: {subbands}" + ) + + return numpy.array( + [[subbands_step] + subbands + [0] * (MAX_PARALLEL_SUBBANDS - len(subbands))] + * N_pn, + dtype=numpy.uint32, + ) + def _apply_antennafield_settings(self, filter_name: str): """Retrieve the RCU band from filter name, returning the correct format for AntennaField device diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py index 1e9215d5d9b8939b200de775827ed8152965e6f7..256b082b895ad7f840992c183297f8ed4dafe758 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py @@ -195,6 +195,14 @@ class BST(Statistics): # Overloaded functions # -------- + def _power_hardware_on(self): + self.proxy.write_attribute( + "FPGA_bst_offload_enable_RW", self.FPGA_bst_offload_enable_RW_default + ) + + def _power_hardware_off(self): + self.proxy.write_attribute("FPGA_bst_offload_enable_RW", [False] * N_pn) + # -------- # Commands # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py index 73cd1ea4a298c19c8fe918adc1a5a730ae119eab..63d05a5cb838eaacba01c79118ba62ac93ebacf8 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py @@ -222,6 +222,14 @@ class SST(Statistics): # Overloaded functions # -------- + def _power_hardware_on(self): + self.proxy.write_attribute( + "FPGA_sst_offload_enable_RW", self.FPGA_sst_offload_enable_RW_default + ) + + def _power_hardware_off(self): + self.proxy.write_attribute("FPGA_sst_offload_enable_RW", [False] * N_pn) + # -------- # Commands # -------- diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py index 94e55aa3a7ad74da5c6d98d98c333ebc9e72319c..45d7cbe3c510f47f809ce5c874c570296261fc83 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py @@ -58,7 +58,7 @@ class XST(Statistics): first_signal_index = self.control.read_parent_attribute( "first_signal_input_index_R" ) - return XSTCollector(nr_signal_inputs, first_signal_index) + return XSTCollector(nr_signal_inputs, first_signal_index, MAX_PARALLEL_SUBBANDS) # ----------------- # Device Properties @@ -210,17 +210,19 @@ class XST(Statistics): dims=(N_pn,), ) FPGA_xst_subband_select_RW = AttributeWrapper( + doc="Which subbands to emit XSTs for. The first element is the step size, allowing different subbands to be emitted every interval.", comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_subband_select_RW"], datatype=numpy.uint32, - dims=(MAX_PARALLEL_SUBBANDS, N_pn), + dims=(N_pn, MAX_PARALLEL_SUBBANDS + 1), access=AttrWriteType.READ_WRITE, ) FPGA_xst_subband_select_R = AttributeWrapper( + doc="Which subbands to emit XSTs for. The first element is the step size, allowing different subbands to be emitted every interval.", comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_subband_select_R"], datatype=numpy.uint32, - dims=(MAX_PARALLEL_SUBBANDS, N_pn), + dims=(N_pn, MAX_PARALLEL_SUBBANDS + 1), ) FPGA_xst_start_time_RW = AttributeWrapper( doc="Start generating XSTs at this timestamp, or as soon as possible. Use this to synchronise the XSTs from different FPGAs.", @@ -689,31 +691,6 @@ class XST(Statistics): fget=lambda self: self.read_xst_N_phase_R(6), ) - xst_7_real_R = attribute( - max_dim_x=MAX_INPUTS, - max_dim_y=MAX_INPUTS, - dtype=((numpy.float32,),), - fget=lambda self: self.read_xst_N_real_R(7), - ) - xst_7_imag_R = attribute( - max_dim_x=MAX_INPUTS, - max_dim_y=MAX_INPUTS, - dtype=((numpy.float32,),), - fget=lambda self: self.read_xst_N_imag_R(7), - ) - xst_7_power_R = attribute( - max_dim_x=MAX_INPUTS, - max_dim_y=MAX_INPUTS, - dtype=((numpy.float32,),), - fget=lambda self: self.read_xst_N_power_R(7), - ) - xst_7_phase_R = attribute( - max_dim_x=MAX_INPUTS, - max_dim_y=MAX_INPUTS, - dtype=((numpy.float32,),), - fget=lambda self: self.read_xst_N_phase_R(7), - ) - def read_xst_N_real_R(self, subband_idx): return numpy.real(self.statistics_client.collector.xst_values([subband_idx])[0]) @@ -745,6 +722,18 @@ class XST(Statistics): # Overloaded functions # -------- + def _power_hardware_on(self): + self.proxy.write_attribute( + "FPGA_xst_processing_enable_RW", self.FPGA_xst_processing_enable_RW_default + ) + self.proxy.write_attribute( + "FPGA_xst_offload_enable_RW", self.FPGA_xst_offload_enable_RW_default + ) + + def _power_hardware_off(self): + self.proxy.write_attribute("FPGA_xst_offload_enable_RW", [False] * N_pn) + self.proxy.write_attribute("FPGA_xst_processing_enable_RW", [False] * N_pn) + # -------- # Commands # -------- diff --git a/tangostationcontrol/tangostationcontrol/observation/observation_controller.py b/tangostationcontrol/tangostationcontrol/observation/observation_controller.py index 4c8f74c666219a34e4bec125696dd4b0fb1edf70..a062cf0b0a92f568f5cbb0dbba12af7fa3560210 100644 --- a/tangostationcontrol/tangostationcontrol/observation/observation_controller.py +++ b/tangostationcontrol/tangostationcontrol/observation/observation_controller.py @@ -8,7 +8,6 @@ from typing import Callable, Type from tango import DevFailed, Util, Database from tangostationcontrol.configuration import ObservationSettings from tangostationcontrol.observation.observation import Observation -from tangostationcontrol.devices.observation_field import ObservationField logger = logging.getLogger() @@ -133,6 +132,10 @@ class ObservationController(dict[int, Observation]): @staticmethod def _destroy_all_observation_field_devices(): """Prevent any lingering observation field devices, remove all from database""" + + # import here to avoid circular imports + from tangostationcontrol.devices.observation_field import ObservationField + db = Database() devices = db.get_device_exported_for_class(ObservationField.__name__) for device in devices: diff --git a/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py index ab8a8c626cda8c8f734d39727be2c95a4b9b94c3..b0b1a2f29487e1443f1c066d556a8b890541ff0e 100644 --- a/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py +++ b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py @@ -9,6 +9,8 @@ from tangostationcontrol.configuration import ( Pointing, Sap, HBA, + SST, + XST, ) SETTINGS_TWO_FIELDS = ObservationSettings( @@ -60,6 +62,10 @@ SETTINGS_HBA_IMMEDIATE = ObservationSettings( element_selection="GENERIC_201512", ), dithering=Dithering(enabled=True, power=-10, frequency=123000000), + SST=SST(subbands_calibrated=False), + XST=XST( + subbands=[1, 2, 3, 4, 5], subbands_step=5, integration_interval=1.0 + ), ), ], ) diff --git a/tangostationcontrol/test/configuration/test_observation_field_settings.py b/tangostationcontrol/test/configuration/test_observation_field_settings.py index 90b8aae33e9acbd09e51916fc13545b4076597d5..38f374851f32973f4030fd6839eeda97f272dfdc 100644 --- a/tangostationcontrol/test/configuration/test_observation_field_settings.py +++ b/tangostationcontrol/test/configuration/test_observation_field_settings.py @@ -5,19 +5,32 @@ import json from jsonschema.exceptions import ValidationError -from tangostationcontrol.configuration import Pointing, ObservationFieldSettings, Sap +from tangostationcontrol.configuration import ( + Pointing, + ObservationFieldSettings, + Sap, + XST, +) from test import base class TestObservationFieldSettings(base.TestCase): - def test_from_json(self): - sut = ObservationFieldSettings.from_json( + def test_xst_from_json_simple(self): + json_str = '{ "subbands": [1, 2, 3], "integration_interval": 1.0 }' + sut = XST.from_json(json_str) + + # check whether converting back to JSON does not throw + _ = sut.to_json() + + def test_from_json_simple(self): + json_str = ( '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", ' '"antenna_field": "HBA", ' '"antenna_set": "ALL", "filter": "HBA_110_190",' '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}]}' ) + sut = ObservationFieldSettings.from_json(json_str) self.assertEqual(sut.observation_id, 3) self.assertEqual(sut.stop_time, "2012-04-23T18:25:43") @@ -25,18 +38,27 @@ class TestObservationFieldSettings(base.TestCase): self.assertEqual(sut.filter, "HBA_110_190") self.assertEqual(len(sut.SAPs), 1) - sut = ObservationFieldSettings.from_json( + # check whether converting back to JSON does not throw + _ = sut.to_json() + + def test_from_json_hba(self): + json_str = ( '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", ' '"antenna_field": "HBA", ' '"antenna_set": "ALL", "filter": "HBA_110_190",' '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],' '"HBA": { "tile_beam": {"angle1":2.2, "angle2": 3.1, "direction_type":"MOON"} } }' ) + sut = ObservationFieldSettings.from_json(json_str) self.assertEqual(sut.HBA.tile_beam.angle1, 2.2) self.assertEqual(sut.HBA.tile_beam.angle2, 3.1) self.assertEqual(sut.HBA.tile_beam.direction_type, "MOON") + # check whether converting back to JSON does not throw + _ = sut.to_json() + + def test_from_json_first_beamlet(self): sut = ObservationFieldSettings.from_json( '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", ' '"antenna_field": "HBA", ' @@ -47,6 +69,22 @@ class TestObservationFieldSettings(base.TestCase): self.assertEqual(sut.first_beamlet, 2) + # check whether converting back to JSON does not throw + _ = sut.to_json() + + def test_from_json_statistics(self): + sut = ObservationFieldSettings.from_json( + '{"observation_id": 3, "stop_time": "2012-04-23T18:25:43", ' + '"antenna_field": "HBA", ' + '"antenna_set": "ALL", "filter": "HBA_110_190",' + '"SAPs": [{"subbands": [3, 2, 1], "pointing": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}}],' + '"HBA": {"tile_beam": {"angle1":1.2, "angle2": 2.1, "direction_type":"LMN"}},' + '"XST": { "subbands": [1, 2, 3], "integration_interval": 1.0 }}' + ) + + # check whether converting back to JSON does not throw + _ = sut.to_json() + def test_from_json_type_missmatch(self): for json_str in [ # observation_id