diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc246b077bca48b5e6616a5ac7d2d2b55c4e2370..46d29fe4d27e1cff7c99aa7904a0c40ced90e718 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -99,8 +99,9 @@ docker_build_image_all: - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-boot latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-docker latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation_control latest + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation-control latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-psoc latest + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-pcon latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-recv latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-sdp latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-bst latest @@ -267,6 +268,17 @@ docker_build_image_device_psoc: script: # Do not remove 'bash' or statement will be ignored by primitive docker shell - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-psoc $tag +docker_build_image_device_pcon: + extends: .base_docker_images_except + only: + refs: + - merge_requests + changes: + - docker-compose/device-pcon.yml + - docker-compose/lofar-device-base/* + script: +# Do not remove 'bash' or statement will be ignored by primitive docker shell + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-pcon $tag docker_build_image_device_tilebeam: extends: .base_docker_images_except only: @@ -339,11 +351,11 @@ docker_build_image_device_observation_control: refs: - merge_requests changes: - - docker-compose/device-observation_control.yml + - docker-compose/device-observation-control.yml - docker-compose/lofar-device-base/* script: # Do not remove 'bash' or statement will be ignored by primitive docker shell - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation_control $tag + - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh device-observation-control $tag docker_build_image_device_antennafield: extends: .base_docker_images_except only: diff --git a/CDB/LOFAR_ConfigDb.json b/CDB/LOFAR_ConfigDb.json index 70c85e9bd31a97e84758a08bebae82106b702a87..2ba1ebe751f6397710f82e8d246c74fe9eaae8dd 100644 --- a/CDB/LOFAR_ConfigDb.json +++ b/CDB/LOFAR_ConfigDb.json @@ -46,9 +46,9 @@ "PSOC": { "STAT/PSOC/1": { "properties": { - "SNMP_host": ["10.87.2.145"], + "SNMP_host": ["127.0.0.1"], "SNMP_community": ["public"], - "SNMP_mib_dir": ["devices/psoc_mib/PowerNet-MIB.mib"], + "SNMP_mib_dir": ["devices/mibs/PowerNet-MIB.mib"], "SNMP_timeout": ["10.0"], "SNMP_version": ["1"], "PSOC_sockets": [ @@ -66,6 +66,21 @@ } } }, + "PCON": { + "STAT": { + "PCON": { + "STAT/PCON/1": { + "properties": { + "SNMP_host": ["127.0.0.1"], + "SNMP_community": ["public"], + "SNMP_mib_dir": ["devices/mibs/ACC-MIB.mib"], + "SNMP_timeout": ["10.0"], + "SNMP_version": ["1"] + } + } + } + } + }, "TemperatureManager": { "STAT": { "TemperatureManager": { @@ -76,6 +91,9 @@ "APSPU, APSPU_TEMP_error_R", "UNB2, UNB2_TEMP_error_R", "RECV, RECV_TEMP_error_R" + ], + "Shutdown_Device_List":[ + "STAT/SDP/1", "STAT/UNB2/1", "STAT/RECV/1", "STAT/APSCT/1", "STAT/APSPU/1" ] } } diff --git a/CDB/stations/DTS_Outside_ConfigDb.json b/CDB/stations/DTS_Outside_ConfigDb.json index fab3e930cfb239c74d0d59e9d0a6a4a401504d70..ed3315567700d954413967c4b9338f14fe676978 100644 --- a/CDB/stations/DTS_Outside_ConfigDb.json +++ b/CDB/stations/DTS_Outside_ConfigDb.json @@ -145,7 +145,7 @@ "AntennaField": { "STAT": { "AntennaField": { - "STAT/AntennaField/1": { + "STAT/AntennaField/2": { "properties": { "RECV_devices": [ "STAT/RECV/1" @@ -188,7 +188,7 @@ ] } }, - "STAT/AntennaField/2": { + "STAT/AntennaField/1": { "properties": { "RECV_devices": [ "STAT/RECV/1" @@ -237,8 +237,11 @@ "DigitalBeam": { "STAT": { "DigitalBeam": { - "STAT/DigitalBeam/1": { + "STAT/DigitalBeam/2": { "properties": { + "AntennaField_Device": [ + "STAT/AntennaField/2" + ], "Input_to_Antenna_Mapping": [ "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", "-1", @@ -259,10 +262,10 @@ ] } }, - "STAT/DigitalBeam/2": { + "STAT/DigitalBeam/1": { "properties": { "AntennaField_Device": [ - "STAT/AntennaField/2" + "STAT/AntennaField/1" ], "Input_to_Antenna_Mapping": [ "0", "1", "2", "3", "4", "5", @@ -526,6 +529,38 @@ } } }, + "PSOC": { + "STAT": { + "PSOC": { + "STAT/PSOC/1": { + "properties": { + "SNMP_host": ["10.87.2.145"], + "PSOC_sockets": [ + "socket_1", + "socket_2", + "socket_3", + "socket_4", + "socket_5", + "socket_6", + "socket_7", + "socket_8" + ] + } + } + } + } + }, + "PCON": { + "STAT": { + "PCON": { + "STAT/PCON/1": { + "properties": { + "SNMP_host": ["10.151.225.5"] + } + } + } + } + }, "UNB2": { "STAT": { "UNB2": { diff --git a/CDB/stations/simulators_ConfigDb.json b/CDB/stations/simulators_ConfigDb.json index c2f70a5999d78628421abeea7012f5cc2079f2ac..7cd92391917029be134fdc9fd4846b5540153663 100644 --- a/CDB/stations/simulators_ConfigDb.json +++ b/CDB/stations/simulators_ConfigDb.json @@ -123,6 +123,9 @@ "properties": { "Alarm_Error_List": [ "RECV, HBAT_LED_on_RW" + ], + "Shutdown_Device_List":[ + "STAT/SDP/1", "STAT/UNB2/1", "STAT/RECV/1", "STAT/APSCT/1", "STAT/APSPU/1" ] } } diff --git a/bin/start-ds.sh b/bin/start-ds.sh index 9ae8ae900fdfe9bb57940afa24df6fe9776f8b10..8a5baba501cf7c48dd53a7d3b92196874020d3d6 100755 --- a/bin/start-ds.sh +++ b/bin/start-ds.sh @@ -32,7 +32,7 @@ if [[ $TANGOSTATIONCONTROL ]]; then else # Install the package, exit 1 if it fails cd tangostationcontrol || exit 1 - pip install ./ + pip install --upgrade --force-reinstall ./ fi # Return to the stored the directory, this preserves the working_dir argument in diff --git a/docker-compose/device-boot.yml b/docker-compose/device-boot.yml index 0388c08229c38f9abd475d4e59efd46ce7a14548..cbeb916536845b47aa0ded5dba2900fa1fa5d7f5 100644 --- a/docker-compose/device-boot.yml +++ b/docker-compose/device-boot.yml @@ -4,7 +4,7 @@ # created Observation devices. # # Defines: -# - device-observation_control: LOFAR2.0 station ObvservationControl +# - device-observation-control: LOFAR2.0 station ObvservationControl # # Requires: # - lofar-device-base.yml diff --git a/docker-compose/device-observation_control.yml b/docker-compose/device-observation-control.yml similarity index 87% rename from docker-compose/device-observation_control.yml rename to docker-compose/device-observation-control.yml index 4c84af7921444ab0923757ccff43d681d7120cb3..197d192a54ca10360439f41cfd6aeb0adeca70e3 100644 --- a/docker-compose/device-observation_control.yml +++ b/docker-compose/device-observation-control.yml @@ -4,7 +4,7 @@ # created Observation devices. # # Defines: -# - device-observation_control: LOFAR2.0 station ObvservationControl +# - device-observation-control: LOFAR2.0 station ObvservationControl # # Requires: # - lofar-device-base.yml @@ -12,8 +12,8 @@ version: '2.1' services: - device-observation_control: - image: device-observation_control + device-observation-control: + image: device-observation-control # build explicitly, as docker-compose does not understand a local image # being shared among services. build: @@ -21,7 +21,7 @@ services: dockerfile: docker-compose/lofar-device-base/Dockerfile args: SOURCE_IMAGE: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_USER}/tango-itango:${TANGO_ITANGO_VERSION} - container_name: ${CONTAINER_NAME_PREFIX}device-observation_control + container_name: ${CONTAINER_NAME_PREFIX}device-observation-control logging: driver: "json-file" options: diff --git a/docker-compose/device-pcon.yml b/docker-compose/device-pcon.yml new file mode 100644 index 0000000000000000000000000000000000000000..aa20c1498c3e135accd543800d9a50e41a3ea092 --- /dev/null +++ b/docker-compose/device-pcon.yml @@ -0,0 +1,42 @@ +# +# Requires: +# - lofar-device-base.yml +# +version: '2.1' + +volumes: + iers-data: {} + +services: + device-pcon: + image: device-pcon + # build explicitly, as docker-compose does not understand a local image + # being shared among services. + build: + context: .. + dockerfile: docker-compose/lofar-device-base/Dockerfile + args: + SOURCE_IMAGE: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}-tango-itango:${TANGO_ITANGO_VERSION} + container_name: ${CONTAINER_NAME_PREFIX}device-pcon + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "10" + networks: + - control + ports: + - "5719:5719" # unique port for this DS + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ..:/opt/lofar/tango:rw + environment: + - TANGO_HOST=${TANGO_HOST} + working_dir: /opt/lofar/tango + entrypoint: + - bin/start-ds.sh + # configure CORBA to _listen_ on 0:port, but tell others we're _reachable_ through ${HOSTNAME}:port, since CORBA + # can't know about our Docker port forwarding + - l2ss-pcon pcon STAT -v -ORBendPoint giop:tcp:device-pcon:5719 -ORBendPointPublish giop:tcp:${HOSTNAME}:5719 + restart: unless-stopped diff --git a/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py b/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py index 86298311c3247eef342becd6d2679c8cea41e6a8..256f78bf155b7a3a17bd70e426478dd56bfc8182 100644 --- a/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py +++ b/docker-compose/tango-prometheus-exporter/code/tango-prometheus-client.py @@ -37,8 +37,33 @@ class ArchiverPolicy(object): def __init__(self, config: dict = None): self.config = config or self.EMPTY_POLICY + def device_list(self) -> list: + """ Retrieve the device list from TangoDB """ + device_list = [] + db = Database() + server_list = db.get_server_list() # e.g. SDP/STAT, RECV/STAT + for server in server_list: + # https://pytango.readthedocs.io/en/stable/database.html#tango.Database.get_device_class_list + class_list = db.get_device_class_list(server) + for cls in class_list[::2]: + if "dserver" in cls: + continue + device_list.append(cls.lower()) + return device_list + def devices(self) -> list: - return list(self.config["devices"].keys()) + """ Filter the device list from TangoDB following the lofar2-policy file """ + # Devices list from TangoDB + db_devices = self.device_list() + # Devices listed in policy file + config_devices = list(k.lower() for k in self.config["devices"].keys()) + # Match device names fetched from DB against device names in policy file + devices = [] + for config_dev in config_devices: + for db_dev in db_devices: + if fnmatch.fnmatch(db_dev, config_dev): + devices.append(db_dev) + return devices def attribute_list(self, device_name: str, attribute_list: list) -> dict: """ Return the full set of archiving policy for the given device. """ @@ -150,7 +175,7 @@ class CustomCollector(object): # obtain extended info about all attributes attr_infos = {attr_info.name: attr_info for attr_info in dev.attribute_list_query()} - if dev.state() not in [DevState.STANDBY, DevState.ON, DevState.ALARM]: + if dev.state() not in [DevState.STANDBY, DevState.ON, DevState.ALARM, DevState.DISABLE]: logger.error(f"Error processing device {device_name}: it is in state {dev.state()}") # at least log state & status @@ -184,7 +209,7 @@ class CustomCollector(object): attribute_metrics = GaugeMetricFamily("device_attribute", 'Device attribute value', labels=['station', 'device', 'name', 'str_value', 'type', 'x', 'y', 'idx']) scraping_metrics = GaugeMetricFamily("device_scraping", 'Device scraping duration', labels=['station', 'device']) - + for device_name in self.policy.devices(): logger.debug(f"Processing device {device_name}") dev_scrape_begin = time.time() diff --git a/docker-compose/tango-prometheus-exporter/lofar2-policy.json b/docker-compose/tango-prometheus-exporter/lofar2-policy.json index 5c7d49b01a2fdb2bf79a1b5d4559e8d9dc5e530b..994d9dd1877b87ab7ccecbcfe325c97333dd7f92 100644 --- a/docker-compose/tango-prometheus-exporter/lofar2-policy.json +++ b/docker-compose/tango-prometheus-exporter/lofar2-policy.json @@ -29,8 +29,14 @@ }, "STAT/Docker/1": { }, + "STAT/Observation/*":{ + }, + "STAT/ObservationControl/1":{ + }, "STAT/PSOC/1": { }, + "STAT/PCON/1": { + }, "STAT/RECV/1": { "include": [ "ANT_mask_RW", diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh index 617744dd3988835d0d7923a18357f65f10c2b1dd..30df5c7cf02be8a26b86c506299d64e0acfa45ef 100755 --- a/sbin/run_integration_test.sh +++ b/sbin/run_integration_test.sh @@ -69,7 +69,8 @@ sleep 1 # dsconfig container must be up and running... # shellcheck disable=SC2016 echo '/usr/local/bin/wait-for-it.sh ${TANGO_HOST} --strict --timeout=300 -- true' | make run dsconfig bash - -DEVICES="device-boot device-apsct device-apspu device-sdp device-recv device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-antennafield device-temperature-manager device-observation" +DEVICES="device-boot device-apsct device-apspu device-sdp device-recv device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-pcon device-antennafield device-temperature-manager device-observation device-observation-control" + SIMULATORS="sdptr-sim recv-sim unb2-sim apsct-sim apspu-sim" # Build only the required images, please do not build everything that makes CI diff --git a/sbin/tag_and_push_docker_image.sh b/sbin/tag_and_push_docker_image.sh index e3053a31fd874061c69099c49338f4204f3eaf83..d12bee575e594e0264e39c8eca6013704eb4a805 100755 --- a/sbin/tag_and_push_docker_image.sh +++ b/sbin/tag_and_push_docker_image.sh @@ -71,7 +71,7 @@ LOCAL_IMAGES=( "device-apsct device-apsct y" "device-apspu device-apspu y" "device-boot device-boot y" "device-docker device-docker y" "device-observation device-observation y" - "device-observation_control device-observation_control y" + "device-observation-control device-observation-control y" "device-recv device-recv y" "device-temperature-manager device-temperature-manager y" "device-sdp device-sdp y" "device-sst device-sst y" diff --git a/tangostationcontrol/docs/source/beam_tracking.rst b/tangostationcontrol/docs/source/beam_tracking.rst deleted file mode 100644 index a9860420af79ffa6ddc9e348abfb122911a8efc9..0000000000000000000000000000000000000000 --- a/tangostationcontrol/docs/source/beam_tracking.rst +++ /dev/null @@ -1,85 +0,0 @@ -Beam Tracking -==================== - -A primary function of the station is to combine its antenna signals to create a more sensitive signal. The antennas are typically aimed at celestial sources moving across the sky, but can also be aimed at stationary targets, for example to point at Earth-bound signals or to let the sky pass through the beam instead. - -Given a certain direction, and knowing the speed of light, one can compute the differences in arrival time for light from the observed source (its wave front) towards each antenna. The antenna signals are then aligned towards the source by delaying the signal inputs based on these differences. The antennas closest to the source get the largest delay. For celestial sources, the light is assumed to be infinitely far away and thus travel in parallel towards each antenna, greatly simplifying the calculations involved. - -In practice, antenna signals can only be coarsely delayed. Fine delay compensation consists of rotating the signal inputs to compensate for the remaining differences in phase. The amount of rotation is frequency dependent. The aligned signals are subsequently added, creating a single signal output of higher sensitivity towards the observed source, albeit with a narrower field of view. - -Beam tracking therefor requires the *pointing* direction in which to observe, as well as the *positions* of the antennas involved. Finally, the antennas need to be periodically realigned to track moving sources. - -Terminology --------------------- - -We distinguish the following concepts: - -- *Beam forming* is combining individual element signals into one. This is performed by the HBAT hardware and SDP firmware, -- *Beam steering* is uploading the delays or weights to the beam-forming hardware, in order to point the beam in a certain direction, -- *Beam tracking* is updating the beam steering over time to track a celestial target, compensating for the Earth's movement through space. - -Pointing --------------------- - -A *pointing* describes the direction in the sky, and consists of a set of coordinates and the relevant coordinate system. They are represented as a tuple of 3 strings: ``("coordinate_system", "angle1", "angle2")``, where the interpretation of ``angle1`` and ``angle2`` depends on the coordinate system used. For example: - -- ``("AZELGEO", "0deg", "90deg")`` points at Zenith (Elevation = 90°, with respect to the Earth geode), -- ``("J2000", "0deg", "90deg")`` points at the North Celestial Pole (Declination = 90°), -- ``("SUN", "0deg", "0deg")`` points at the centre of the Sun. - -For a full list of the supported coordinate systems, see https://casacore.github.io/casacore/classcasacore_1_1MDirection.html - -Coordinates in a celestial coordinate system result in a different direction depending on the time. Each pointing is associated with a *timestamp* for when it was or is to be applied. When not specified, ``datetime.datime.now()`` is used. - -Positions --------------------- - -The positions of the antennas are required in ITRF, a carthesian geocentric coordinate system ("XYZ" with its origin at the center of the Earth). These coordinates provide an absolute 3D position on Earth and form the basis for most delay computations. These positions are not stationary however, due to the tectonic plate movement. Instead, we use source coordinates in ETRS89 (also carthesian geocentric), and convert those to ITRF using updated and extrapolated tectonic models (see ``tangostationcontrol.tilebeam.geo.ETRS_to_ITRF``). - -:recv.HBAT_reference_ETRS: (property) The reference position of each HBA tile, in ETRS89. - - :type: ``float[96][3]`` - -:recv.ITRF_Reference_Frame: (property) The ITRF system to convert coordinates to (default: ITRF2005). - - :type: ``str`` - -:recv.ITRF_Reference_Epoch: (property) The year to extrapolate the ITRF system to (typically half-year increments, such as 2015.5). - - :type: ``float`` - -:recv.HBA_reference_ITRF_R: The computed refence positions of each HBA tile, in ITRF. - - :type: ``float[96][3]`` - -NB: It is also possible to explicitly specify ITRF positions in the ``recv.HBAT_reference_ITRF`` property to side step the calculations based on ETRS. - -HBAT element positions -```````````````````````` - -The positions of the elements within an HBA tile are handled differently. Instead of storing the positions of each of the 16 elements in each tile, we use the fact that the relative positions of the elements within each tile is fixed, and that in LOFAR stations, all the HBA tiles of a station are on the same plane (instead of following the curvature of the Earth). This plane is given its own station-local coordinates, the PQR system: - -- It's origin is at a chosen center of the station, -- The Q axis is aligned with an absolute North (not the North of the station, which would be a different direction per station), -- The P axis is roughly East, -- The R axis is roughly down, -- The HBA tiles on a station all lie on the same PQ plane, so R == 0. - -These facts allow us to use the following information to calculate the absolute position of each tile element. The conversion takes the relative offsets of the elements within a tile, rotates them in PQR space, rotates those into relative ETRS offsets, and finally into absolute positions in ETRS. See ``tangostationcontrol.tilebeam.hba_tile`` for these computations. - -:recv.HBAT_base_antenna_offsets: (property) The relative offsets of each element within a tile, with respect to the tile's reference position (the center of the ground plane). - - :type: ``float[16][3]`` - -:recv.HBAT_PQR_rotation_angles_deg: (property) The horizontal rotation of each HBA tile in the PQ plane, in degrees (Q -> P). - - :type: ``float[96]`` - -:recv.HBAT_PQR_to_ETRS_rotation_matrix: (property) The 3D rotation matrix to convert PQR coordinates into relative ETRS coordinates. - - :type: ``float[3][3]`` - -:recv.HBAT_reference_ETRS: (property) The absolute reference position of each HBA tile, in ETRS89. - - :type: ``float[96][3]`` - diff --git a/tangostationcontrol/docs/source/devices/antennafield.rst b/tangostationcontrol/docs/source/devices/antennafield.rst index e91a3ba71ec4b417fdadc7a9183baef16e75bf31..d9c36a0148fab3b74b8cf1311b0d8c4369686da9 100644 --- a/tangostationcontrol/docs/source/devices/antennafield.rst +++ b/tangostationcontrol/docs/source/devices/antennafield.rst @@ -1,4 +1,99 @@ -antennfield -==================== +AntennaField +-------------------- + +The ``antennafield == DeviceProxy("STAT/AntennaField/1")`` device represents a set of *antennas* or *tiles* that collectively form an antenna field. It represents a selection of inputs from one or more ``RECV`` devices, annotated with metadata such as positional information. + +:nr_tiles_R: The number of antennas or tiles in the antenna field. + + :type: ``uint32`` + +It provides many settings that map onto the ``RECV`` device directly, serving as a funnel: + +:HBAT_ANT_mask_RW: Which antennas are configured when writing settings. + + :type: ``bool[N_tiles]`` + +.. warning:: Any antennas in the field that are not connected to any ``RECV`` device will return default values (f.e. ``False`` or ``0``). + +Positions +```````````````````` + +The following attributes expose positional information about the individual antennas in the field, in different formats: + +:HBAT_reference_GEO_R: Reference position of each HBA tile, in latitude/longitude (degrees). + + :type: ``float64[N_tiles][2]`` + +:Antenna_Field_Reference_GEO_R: Reference position of the antenna field, in latitude/longitude (degrees). + + :type: ``float64[2]`` + +Additionally, the ``ITRF`` and ``GEOHASH`` variants provide the same information, but in ITRF (x/y/z, metres), and in Geohash strings, respectively. + +Also, the offsets of the elements within each HBA tile are provided: + +:HBAT_antenna_ITRF_offsets_R: Relative position of each HBA tile element with respect to the tile reference. + + :type: ``float64[N_tiles][N_elements * 3]`` + :shape: ``float64[N_tiles][N_elements][3]`` + +Configuration +```````````````````` + +The antennas represented by the antenna field are selected by the following properties: + +:RECV_devices: The list of ``RECV`` devices from which antennas are selected. + + :type: ``str[]`` + +: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[]`` + :shape: ``int32[][2]`` + +:HBAT_Control_to_RECV_mapping: Pairs of numbers ``(recv_idx, ant_idx)`` describing the inputs on which the HBAT *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 +"""""""""""""""""""" + +The positions are given in ETRS, using the following properties: + +:HBAT_reference_ETRS: Reference position of each HBA tile, in ETRS (x/y/z, metres). + + :type: ``float64[N_tiles][3]`` + +:Antenna_Field_Reference_ETRS: Reference position of the antenna field, in ETRS (x/y/z, metres). + + :type: ``float64[3]`` + +:ITRF_Reference_Frame: Reference frame to use for converting ETRS to ITRF (f.e. "ITRF2005"). + + :type: ``str`` + +:ITRF_Reference_Epoch: Epoch towards which to extrapolate the ITRF frame, typically in half-year increments (f.e. 2015.5). + + :type: ``float32`` + +For the ETRS positions, there is an alternative to provide them using the respective ``ITRF`` property, which overrides the automatic ETRS-to-ITRF conversion. + +HBAT element positions +```````````````````````` + +The positions of the elements within an HBA tile are handled differently. Instead of storing the positions of each of the 16 elements in each tile, we use the fact that the relative positions of the elements within each tile is fixed, and that in LOFAR stations, all the HBA tiles of a station are on the same plane (instead of following the curvature of the Earth). This plane is given its own station-local coordinates, the PQR system: + +- It's origin is at a chosen center of the station, +- The Q axis is aligned with an absolute North (not the North of the station, which would be a different direction per station), +- The P axis is roughly East, +- The R axis is roughly down, +- The HBA tiles on a station all lie on the same PQ plane, so R == 0. + +These facts allow us to use the following information to calculate the absolute position of each tile element. The conversion takes the relative offsets of the elements within a tile, rotates them in PQR space, rotates those into relative ETRS offsets, and finally into absolute positions in ETRS. See ``tangostationcontrol.tilebeam.hba_tile`` for these computations. + +:recv.HBAT_PQR_rotation_angles_deg: (property) The horizontal rotation of each HBA tile in the PQ plane, in degrees (Q -> P). + + :type: ``float[96]`` + +:recv.HBAT_PQR_to_ETRS_rotation_matrix: (property) The 3D rotation matrix to convert PQR coordinates into relative ETRS coordinates. + + :type: ``float[3][3]`` -``antennafield == DeviceProxy("STAT/AntennaField/1")`` diff --git a/tangostationcontrol/docs/source/devices/beamlet.rst b/tangostationcontrol/docs/source/devices/beamlet.rst index 39e2a588776a2e3da8adf5f030687f81492c4a45..21ad55ec0a7c1096dfd4eb1d33b122533b9ca013 100644 --- a/tangostationcontrol/docs/source/devices/beamlet.rst +++ b/tangostationcontrol/docs/source/devices/beamlet.rst @@ -1,5 +1,5 @@ Beamlet -==================== +-------------------- The ``beamlet == DeviceProxy("STAT/Beamlet/1")`` device controls the creation and emission of beamlets. Each beamlet is a signal stream characterised by: diff --git a/tangostationcontrol/docs/source/devices/bst-sst-xst.rst b/tangostationcontrol/docs/source/devices/bst-sst-xst.rst index 90bc0df19a4bbcaea7b5b24df72b095395a88477..c319c71e9cf83fae9fc35cec51c4dfe4832a189c 100644 --- a/tangostationcontrol/docs/source/devices/bst-sst-xst.rst +++ b/tangostationcontrol/docs/source/devices/bst-sst-xst.rst @@ -1,5 +1,5 @@ BST, SST, and XST -==================== +-------------------- The ``bst == DeviceProxy("STAT/BST/1")``, ``sst == DeviceProxy("STAT/SST/1")`` and ``xst == DeviceProxy("STAT/XST/1")`` devices manages the BSTs (beamlet statistics) SSTs (subband statistics) and XSTs (crosslet statistics), respectively. The statistics are emitted piece-wise through UDP packets by the FPGAs on the Uniboards in SDP. By default, each device configures the statistics to be streamed to itself (the device), from where the user can obtain them. @@ -16,11 +16,11 @@ See the following links for a full description of the BST, SST, and XST monitori - https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD BST Statistics attributes ------------------------------- +`````````````````````````````` SST Statistics attributes ------------------------------- +`````````````````````````````` The SSTs represent the amplitude of the signal in each subband, for each antenna, as an integer value. They are exposed through the following attributes: @@ -43,7 +43,7 @@ The SSTs represent the amplitude of the signal in each subband, for each antenna Typically, ``N_ant == 192``, and ``N_subbands == 512``. XST Statistics attributes ------------------------------- +`````````````````````````````` The XSTs represent the cross-correlations between each pair of antennas, as complex values. The phases and amplitudes of the XSTs represent the phase and amplitude difference between the antennas, respectively. They are exposed as a matrix ``xst[a][b]``, of which only the triangle ``a<=b`` is filled, as the cross-correlation between antenna pairs ``(b,a)`` is equal to the complex conjugate of the cross-correlation of ``(a,b)``. The other triangle contains incidental values, but will be mostly 0. @@ -83,7 +83,7 @@ Conversely, to calculate the block index for an antenna pair ``(a,b)``, use:: return baseline_index(a // 12, b // 12) Configuring the XSTs -````````````````````````````` +""""""""""""""""""""""""""""" The XSTs can be configured with several settings: @@ -102,7 +102,7 @@ The XSTs can be configured with several settings: :type: ``uint32[N_fpgas][8]`` Subscribe to statistics streams ---------------------------------- +`````````````````````````````````` The TCP stream interface allows a user to subscribe to the statistics packet streams, combined into a single TCP stream. The statistics will be streamed until the user disconnects, or the device is turned off. Any number of subscribers is supported, as bandwidth allows. Simply connect to the following port: diff --git a/tangostationcontrol/docs/source/devices/configure.rst b/tangostationcontrol/docs/source/devices/configure.rst index e6ed339a17c76a50c19a2e8b4b169c166759a986..0ed05a0fba41d10c9a73a91da355ddbf3f192b97 100644 --- a/tangostationcontrol/docs/source/devices/configure.rst +++ b/tangostationcontrol/docs/source/devices/configure.rst @@ -1,5 +1,5 @@ Device Configuration -========================= +------------------------- The devices receive their configuration from two sources: @@ -9,7 +9,7 @@ The devices receive their configuration from two sources: .. _tangodb: TangoDB -------------------------- +```````````````````````````` The TangoDB database is a persistent store for the properties of each device. The properties encode static settings, such as the hardware addresses, and default values for control attributes. diff --git a/tangostationcontrol/docs/source/devices/digitalbeam.rst b/tangostationcontrol/docs/source/devices/digitalbeam.rst deleted file mode 100644 index 332e47993e3000259749e58be5840d0ae7f778ab..0000000000000000000000000000000000000000 --- a/tangostationcontrol/docs/source/devices/digitalbeam.rst +++ /dev/null @@ -1,4 +0,0 @@ -digitalbeam -==================== - -``digitalbeam == DeviceProxy("STAT/DigitalBeam/1")`` diff --git a/tangostationcontrol/docs/source/devices/overview.rst b/tangostationcontrol/docs/source/devices/overview.rst new file mode 100644 index 0000000000000000000000000000000000000000..c1db108117f629507f59dc9f4ebcdf57c46e6396 --- /dev/null +++ b/tangostationcontrol/docs/source/devices/overview.rst @@ -0,0 +1,103 @@ +Devices +===================================== + +This package implements the *Station Control (SC)* part of a LOFAR2.0 station, the core of which implements several Tango devices that connect to the station's hardware as well as to each other. In the following graph, green components are implemented in this package, the gray components are external: + +.. graphviz:: + + digraph logical_layout { + fontname="Helvetica,Arial,sans-serif" + node [fontname="Helvetica,Arial,sans-serif" fontsize="20pt" style=filled] + edge [fontname="Helvetica,Arial,sans-serif" fontsize="20pt"] + + subgraph clusterSC { + label = "SC"; + + node [shape=ellipse color=aquamarine]; + + ObservationControl -> Observation; + Observation -> SDP; + Observation -> AntennaField; + Observation -> TileBeam; + Observation -> DigitalBeam; + + AntennaField -> RECV; + TileBeam -> AntennaField; + DigitalBeam -> Beamlet; + DigitalBeam -> AntennaField; + + SST -> SDP; + XST -> SDP; + BST -> SDP; + + APSCT; + APSPU; + UNB2; + + PSOC; + } + + subgraph clusterSDP { + label = "SDP"; + + SDPTR -> Firmware; + UNB2TR -> Uniboard2; + } + SDP -> SDPTR; + Beamlet -> SDPTR; + SST -> SDPTR; + XST -> SDPTR; + BST -> SDPTR; + UNB2 -> UNB2TR; + + subgraph clusterRECV { + label = "RECV"; + + RECVTR -> RCU -> HBAT; + } + RECV -> RECVTR; + + subgraph clusterAPSCT { + label = "APSCT"; + + APSCTTR; + } + APSCT -> APSCTTR; + + subgraph clusterAPSPU { + label = "APSPU"; + + APSPUTR; + } + APSPU -> APSPUTR; + + subgraph clusterPSOC { + label = "PSOC"; + } + PSOC -> clusterPSOC; + } + +A brief description of each of these devices: + +* `ObservationControl` device spawns new Observation devices, given an observation specification, +* `Observation` device sets up the software and hardware on the station to execute a given specification, +* `AntennaField` device controls a set of antennas and their properties (f.e. their positions), +* `RECV` device represents the hardware that controls the antennas in the station, +* `TileBeam` device steers the beam of the HBA tiles, actively tracking any source, +* `SDP` device represents generic functionality of the firmware that digitally combines antenna inputs, +* `SST`, `XST`, and `BST` devices control and expose statistics generated by the SDP firmware, +* `Beamlet` device controls the observation output data (beamlets) that stream out of the station (in LOFAR, to CEP), +* `DigitalBeam` device steers the beam formed in SDP, actively tracking any source. + +Auxilliary devices that control hardware are: + +* `APSCT` device controls the ASPCT clock selection and distribution board, +* `APSPU` device controls the APSPU 48V distribution board, +* `UNB2` device controls the Uniboards that hold the SDP FPGAs (and thus firmware). +* `PSOC` device controls the power sockets (230V distribution). + +Finally, the stack holds the auxilliary devices that control the software devices. They connect to too many devices to draw: + +* `Docker` device controls the Docker containers of the software stack, +* `Boot` device controls the proces for booting up the devices and initialising the hardware, +* `TemperatureManager` device acts on temperature alarms originating from the hardware. diff --git a/tangostationcontrol/docs/source/devices/psoc.rst b/tangostationcontrol/docs/source/devices/psoc.rst index bccb7b0d487aec270be78393625da2d00c1d1b24..c3265005fd41bdd9dca078b0fe1d60860a4e848e 100644 --- a/tangostationcontrol/docs/source/devices/psoc.rst +++ b/tangostationcontrol/docs/source/devices/psoc.rst @@ -1,7 +1,6 @@ .. _psoc: PSOC -==================== +-------------------- -The ``psoc == DeviceProxy("STAT/PSOC/1")`` device controls the Power Distribution -Unit (PSOC) +The ``psoc == DeviceProxy("STAT/PSOC/1")`` device controls the Power Distribution Unit (PSOC). diff --git a/tangostationcontrol/docs/source/devices/recv.rst b/tangostationcontrol/docs/source/devices/recv.rst index aaa5dd91435647b9e02700975f764e804167efe7..5e555d91c5deaa7c1131313d809707d38a94a835 100644 --- a/tangostationcontrol/docs/source/devices/recv.rst +++ b/tangostationcontrol/docs/source/devices/recv.rst @@ -1,5 +1,5 @@ RECV -==================== +--------------------- The ``recv == DeviceProxy("STAT/RECV/1")`` device controls the RCUs, the LBA antennas, and HBA tiles. Central to its operation are the masks (see also :ref:`attribute-masks`): @@ -9,12 +9,14 @@ The ``recv == DeviceProxy("STAT/RECV/1")`` device controls the RCUs, the LBA ant :Ant_mask_RW: Controls which antennas will actually be configured when attributes referring to antennas are written. - :type: ``bool[N_RCUs][N_antennas_per_RCU]`` + :type: ``bool[N_antennas]`` + +Typically, ``N_RCUs == 32``, and ``N_antennas == 96``. -Typically, ``N_RCUs == 32``, and ``N_antennas_per_RCU == 3``. +.. note:: The antennas are hooked up to the RCUs in sets of 3, in order. Error information ---------------------- +````````````````````` These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors: @@ -24,7 +26,7 @@ These attributes summarise the basic state of the device. Any elements which are :ANT_error_R: Whether the antennas appear usable. - :type: ``bool[N_RCUs][N_antennas_per_RCU]`` + :type: ``bool[N_antennas]`` :RCU_IOUT_error_R: Whether there are alarms on any of the amplitudes in the measured currents. diff --git a/tangostationcontrol/docs/source/devices/sdp.rst b/tangostationcontrol/docs/source/devices/sdp.rst index 6386dd851b178ce44b0b1b53968ba0f219fe5140..8b2cadda2ee6b3a7403d954091e8ce1548eaa90c 100644 --- a/tangostationcontrol/docs/source/devices/sdp.rst +++ b/tangostationcontrol/docs/source/devices/sdp.rst @@ -1,5 +1,5 @@ SDP -==================== +--------------------- The ``sdp == DeviceProxy("STAT/SDP/1")``` device controls the digital signal processing in SDP, performed by the firmware on the FPGAs on the Uniboards. Central to its operation is the mask (see also :ref:`attribute-masks`): @@ -15,7 +15,7 @@ See the following links for a full description of the SDP monitoring and control - https://plm.astron.nl/polarion/#/project/LOFAR2System/wiki/L2%20Interface%20Control%20Documents/SC%20to%20SDP%20ICD Basic configuration ---------------------- +````````````````````` The following points are significant for the operations of this device: @@ -28,7 +28,7 @@ The following points are significant for the operations of this device: :type: ``bool[N_fpgas]`` Data-quality information ---------------------------- +``````````````````````````` The following fields describe the data quality (see also :doc:`../signal_chain`): @@ -41,7 +41,7 @@ The following fields describe the data quality (see also :doc:`../signal_chain`) :type: ``double[N_fpgas][N_ants_per_fpga]`` Error information ---------------------- +``````````````````````````` These attributes summarise the basic state of the device. Any elements which are not present in ``FPGA_mask_RW`` will be ignored and thus not report errors: @@ -54,7 +54,7 @@ These attributes summarise the basic state of the device. Any elements which are :type: ``bool[N_fpgas]`` Version Information ---------------------- +``````````````````````````` The following fields provide version information: @@ -71,7 +71,7 @@ The following fields provide version information: :type: ``str[N_fpgas]`` Waveform Generator ---------------------- +``````````````````````````` The antenna input of SDP can be replaced by an internal waveform generator for debugging and testing purposes. The generator is configured per antenna per FPGA: @@ -94,7 +94,7 @@ The antenna input of SDP can be replaced by an internal waveform generator for d :type: ``float32[N_fpgas][N_ants_per_fpga]`` Usage example -``````````````````````` +""""""""""""""""""""""" For example, the following code inserts a wave on LBA subband 102 on FPGAs 8 - 11:: diff --git a/tangostationcontrol/docs/source/devices/temperature-manager.rst b/tangostationcontrol/docs/source/devices/temperature-manager.rst index c4f919377d5fbcb79338b0ea28e24c4cbf35c975..2228aa38ae58fbd353443e037abaca67cea1881a 100644 --- a/tangostationcontrol/docs/source/devices/temperature-manager.rst +++ b/tangostationcontrol/docs/source/devices/temperature-manager.rst @@ -1,4 +1,4 @@ -temperature-manager -==================== +TemperatureManager +-------------------- ``temperature_manager == DeviceProxy("STAT/TemperatureManager/1")`` diff --git a/tangostationcontrol/docs/source/devices/tilebeam-digitalbeam.rst b/tangostationcontrol/docs/source/devices/tilebeam-digitalbeam.rst new file mode 100644 index 0000000000000000000000000000000000000000..eeccd01ea74f10ab6f1d34fbf2b64cf9e79f4059 --- /dev/null +++ b/tangostationcontrol/docs/source/devices/tilebeam-digitalbeam.rst @@ -0,0 +1,174 @@ +TileBeam, DigitalBeam +------------------------ + +A primary function of the station is to combine its antenna signals to create a more sensitive signal. The antennas are typically aimed at celestial sources moving across the sky, but can also be aimed at stationary targets, for example to point at Earth-bound signals or to let the sky pass through the beam instead. + +Given a certain direction, and knowing the speed of light, one can compute the differences in arrival time for light from the observed source (its wave front) towards each antenna. The antenna signals are then aligned towards the source by delaying the signal inputs based on these differences. The antennas closest to the source get the largest delay. For celestial sources, the light is assumed to be infinitely far away and thus travel in parallel towards each antenna, greatly simplifying the calculations involved. + +In practice, antenna signals can only be coarsely delayed. Fine delay compensation consists of rotating the signal inputs to compensate for the remaining differences in phase. The amount of rotation is frequency dependent. The aligned signals are subsequently added, creating a single signal output of higher sensitivity towards the observed source, albeit with a narrower field of view. + +Beam tracking therefor requires a *pointing* direction in which to observe, as well as the *positions* of the antennas involved. Finally, the antennas need to be periodically realigned to track moving sources. We distinguish the following concepts: + +- *Beam forming* is combining individual element signals into one. This is performed by the HBAT hardware and SDP firmware, +- *Beam steering* is uploading the delays or weights to the beam-forming hardware, in order to point the beam in a certain direction, +- *Beam tracking* is updating the beam steering over time to track a celestial target, compensating for the Earth's movement through space. + +The ``tilebeam == DeviceProxy("STAT/TileBeam/1")`` device configures the HBA beam former in each HBA tile, which adds the signals of its 16 elements within the tile. The output signal of these tiles is used as input for the digital beam former (just like the direct output of an LBA). + +The ``digitalbeam == DeviceProxy("STAT/DigitalBeam/1")`` device configures the digital beam formed in SDP from antenna or tile inputs. The output signal in SDP are *beamlets*, which can + +Both devices beamform the antennas configured in its associated ``AntennaField`` device, but differ in what they beamform and with respect to which position: + +- TileBeam: + + - Beamforms HBA elements in the HBA tiles of its AntennaField device, + - Uses ``antennafield.HBAT_Reference_ITRF_R`` as the reference position for each tile, + - Allows a different pointing per HBA tile, + - ``N_output := antennafield.nr_tiles_R``, + - Uploads the computed weights to ``antennafield.HBAT_bf_delay_steps_RW``, + - These weights are actually *delay steps* to be applied in the tile for each element. + +- DigitalBeam + + - Beamforms all the antennas or tiles of its AntennaField device, + - Uses ``antennafield.Antenna_Field_Reference_ITRF_R`` as the reference position, + - Allows a different pointing per beamlet, + - ``N_output := NUM_BEAMLETS = 488``, + - Uploads the computed weights to ``beamlet.FPGA_bf_weights_xx_yy_RW``, + - These weights are actually complex *phase rotations* to be applied on each antenna input. + +Common functionality +````````````````````` + +The following functionality holds for both TileBeam and DigitalBeam. + +Beam Tracking +""""""""""""""""""""""""""""""""" + +Beam tracking automatically recomputes and reapplies pointings periodically, and immediately when new pointings are configured. It exposes the following interface: + +:Tracking_enabled_R: Whether beam tracking is running. + + :type: ``bool`` + +:Pointing_direction_RW: The direction in which the beam should be tracked for each antenna. The beam tracker will steer the beam periodically, and explicitly whenever the pointings change. + + :type: ``str[N_output][3]`` + +:Pointing_direction_R: The last applied pointing of each antenna. + + :type: ``str[N_output][3]`` + +:Pointing_timestamp_R: The timestamp for which the last set pointing for each antenna was applied and set (in seconds since 1970). + + :type: ``float[N_output][3]`` + +A pointing describes the direction in the sky, and consists of a set of coordinates and the relevant coordinate system. They are represented as a tuple of 3 strings: ``("coordinate_system", "angle1", "angle2")``, where the interpretation of ``angle1`` and ``angle2`` depends on the coordinate system used. For example: + +- ``("AZELGEO", "0deg", "90deg")`` points at Zenith (Elevation = 90°, with respect to the Earth geode), +- ``("J2000", "0deg", "90deg")`` points at the North Celestial Pole (Declination = 90°), +- ``("SUN", "0deg", "0deg")`` points at the centre of the Sun. + +For a full list of the supported coordinate systems, see https://casacore.github.io/casacore/classcasacore_1_1MDirection.html + +Beam Steering +""""""""""""""""""""""""""""""""" + +The beam steering is responsible for pointing the beams at a target, by converting the pointing to hardware-specific weights and uploading them to the corresponding device. The beam steering is typically controlled by the beam tracker. To point the antennas in any direction manually, you should disable beam tracking first: + +:Tracking_enabled_RW: Enable or disable beam tracking (default: ``True``). + + :type: ``bool`` + +:set_pointing(pointings): Point the beams towards the specified ``pointings[N_output][3]`` for all outputs. + + :returns: ``None`` + +Celestial and geographical models +""""""""""""""""""""""""""""""""" + +We use `python-casacore <https://casacore.github.io/python-casacore/index.html>`_ to compute the direction of a given pointing with respect to our antennas and reference positions. Casacore in turn uses *measures* tables for the precise measurements of celestial positions, geodetical information, and time calibrations (f.e. leap seconds). These tables need to be installed and periodically updated to maintain the pointing accuracy: + +:measures_directory_R: Directory of the active set of measures tables. The directory name includes the timestamp denoting their age. + + :type: ``str`` + +:measures_directories_available_R: List of installed sets of measures tables. + + :type: ``str[64]`` + +:download_measures(): Download (but do not activate) the latest measures tables from ftp://ftp.astron.nl. Returns the directory name in which the measures were installed. + + :returns: ``str`` + +:use_measures(dir): Activate the measures tables in the provided directory. This necessitates turning off and restarting the TileBeam device, so the command will always appear to fail. Turn the device back and the selected measures tables will be active. + + :returns: ``(does not return)`` + +Timing +""""""""""""""""""""""""""""""""" + +The beam tracking applies an update each *interval*, and aims to apply it at timestamps ``(now % Beam_tracking_interval) - Beam_tracking_application_offset``. To do so, it starts its computations every interval ``Beam_tracking_preparation_period`` seconds before. It then starts to compute the weights, waits to apply them, and applies them by uploading the weights to the underlying hardware. + +The following properties are used: + +:Beam_tracking_interval: Update the beam tracking at this interval (seconds). + + :type: ``float`` + +:Beam_tracking_application_offset: Update the beam tracking this amount of time before the next interval (seconds). + + :type: ``float`` + +:Beam_tracking_preparation_period: Prepare time for each period to compute and upload the weights (seconds). + + :type: ``float`` + +The following timers allow you to track the durations of each stage: + +:Duration_compute_weights_R: Amount of time it took to compute the last weights (seconds). + + :type: ``float`` + +:Duration_preparation_period_slack_R: Amount of time left in the prepration period between computing and uploading the weights (seconds). + + :type: ``float`` + +:Duration_apply_weights_R: Amount of time it took to apply (upload) the weights (seconds). + + :type: ``float`` + +DigitalBeam +````````````````````` + +The DigitalBeam device applies the following configuration to compute each beamlet. Here, ``N_ant := antennafield.nr_tiles_R`` and ``N_beamlet := NUM_BEAMLETS == N_output``. + +:antenna_select_RW: Which beamlets to form with which antenna. ``True`` by default, except for antennas which are not mapped on any FPGA input (see "Configuration" below). + + :type: ``bool[N_ant][N_beamlet]`` + +:beamlet.subband_select_RW: Which subband to beamform for each beamlet. + + :type: ``uint32[N_beamlet]`` + +:sdp.clock_RW: Which clock to use (in Hz, default: 200e6). + + :type: ``uint32`` + +:sdp.antenna_type_R: Which antenna type is connected: "LBA" or "HBA". Configurable through the ``sdp.Antenna_Type`` property. + + :type: ``str`` + +:sdp.nyquist_zone_R: Relevant Nyquist zone (0, 1 or 2), based on the clock and antenna type. + + :type: ``uint32`` + +Configuration +""""""""""""""""""""" + +The following properties configure the DigitalBeam: + +:Input_to_Antenna_Mapping: Which antenna from the AntennaField is the input for each antenna input of the FPGAs. + + :type: ``uint32[96]`` + diff --git a/tangostationcontrol/docs/source/devices/tilebeam.rst b/tangostationcontrol/docs/source/devices/tilebeam.rst deleted file mode 100644 index 1df29562f605039cad0f77ad284b1f0d3b562f0b..0000000000000000000000000000000000000000 --- a/tangostationcontrol/docs/source/devices/tilebeam.rst +++ /dev/null @@ -1,86 +0,0 @@ -Beam -==================== - -The ``tilebeam == DeviceProxy("STAT/TileBeam/1")`` device sets up the beamforming on the station. It configures the HBA tile beam former, which adds the signals of its 16 elements within the tile. These element signals can be delayed a configurable amount of time, allowing their sum to become more sensitive in a certain direction. Each tile can have their own pointing direction configured. - -Beam Tracking --------------------- - -Beam tracking automatically recomputes and reapplies pointings periodically, and immediately when new pointings are configured. It exposes the following interface: - -:Tracking_enabled_R: Whether beam tracking is running. - - :type: ``bool`` - -:Pointing_direction_RW: The direction in which the beam should be tracked for each antenna. The beam tracker will steer the beam periodically, and explicitly whenever the pointings change. - - :type: ``str[N_ant][3]`` - -:Pointing_direction_R: The last applied pointing of each antenna. - - :type: ``str[N_ant][3]`` - -:Pointing_timestamp_R: The timestamp for which the last set pointing for each antenna was applied and set (in seconds since 1970). - - :type: ``float[N_ant][3]`` - -Beam Steering --------------------- - -The beam steering is responsible for pointing the beams at a target, by converting pointings to ``recv.HBAT_bf_delay_steps``. The beam steering is typically controlled by the beam tracker. To point the antennas in any direction manually, you should disable beam tracking first: - -:Tracking_enabled_RW: Enable or disable beam tracking (default: ``True``). - - :type: ``bool`` - -:set_pointing(pointings): Point the beams towards the specified ``pointings[N_ant][3]`` for all antennas (for which ``recv.ANT_mask_RW`` is set). - - :returns: ``None`` - -Computation Parameters ------------------------ - -To form a beam from a set of antennas in a certain direction, the signals coming from each antenna needs to be aligned, such that signals coming from that direction become coherent. In tile beam forming, this is accomplished by proportionally delaying the signals from the antennas that are closer to the target. - -To compute the required delays, both the direction of the source and the position of the antennas are required. Furthermore, a *reference position* is used, a virtual position towards which the antenna signals are aligned. The reference position is typically in the center of the set of antennas. - -HBA positions -``````````````````````` - -For the tile beam, these positions are stored as follows in the ``recv`` device: - -:recv.HBAT_antenna_itrf_offsets_R: The relative offsets of the elements within each HBA tile with respect to its reference position, in ITRF. - - :type: ``double[16][3]`` - -:recv.HBAT_reference_itrf_R: The absolute reference position for an HBA tile, in ITRF. - - :type: ``double[96][3]`` - -NB: A LOFAR antenna field is constructed such that all tiles lie within the same plane, instead of following the curvature of the Earth. This means that the relative offsets of the elements with an HBA tile in ITRF is the same for every tile in the antenna field. - -HBA delays -``````````````````````` - -Each HBA tile is steered by configuring the number of *delay steps* to introduce for each element. - -Direction information -``````````````````````` - -We use `python-casacore <https://casacore.github.io/python-casacore/index.html>`_ to compute the direction of a given pointing with respect to our antennas and reference positions. Casacore in turn uses *measures* tables for the precise measurements of celestial positions, geodetical information, and time calibrations (f.e. leap seconds). These tables need to be installed and periodically updated to maintain the pointing accuracy: - -:measures_directory_R: Directory of the active set of measures tables. The directory name includes the timestamp denoting their age. - - :type: ``str`` - -:measures_directories_available_R: List of installed sets of measures tables. - - :type: ``str[64]`` - -:download_measures(): Download (but do not activate) the latest measures tables from ftp://ftp.astron.nl. Returns the directory name in which the measures were installed. - - :returns: ``str`` - -:use_measures(dir): Activate the measures tables in the provided directory. This necessitates turning off and restarting the TileBeam device, so the command will always appear to fail. Turn the device back and the selected measures tables will be active. - - :returns: ``(does not return)`` diff --git a/tangostationcontrol/docs/source/devices/using.rst b/tangostationcontrol/docs/source/devices/using.rst index 6eff9278eb74fc06a66b0d5b90f09d4f58eb0008..053d73bda33703c187f6bd9de9d7d84194647254 100644 --- a/tangostationcontrol/docs/source/devices/using.rst +++ b/tangostationcontrol/docs/source/devices/using.rst @@ -1,5 +1,5 @@ Using Devices -============= +---------------- The station exposes *devices*, each of which is a remote software object that manages part of the station. Each device has the following properties: @@ -13,7 +13,7 @@ The station exposes *devices*, each of which is a remote software object that ma The devices are accessed remotely using ``DeviceProxy`` objects. See :doc:`../interfaces/control` on how to do this. States ------------- +```````````````` The state of a device is then queried with ``device.state()``. Each device can be in one of the following states: @@ -22,7 +22,8 @@ The state of a device is then queried with ``device.state()``. Each device can b - ``DevState.STANDBY``: The device is initialised and ready to be configured further, - ``DevState.ON``: The device is operational, - ``DevState.ALARM``: The device is operational, but one or more attributes are in alarm, -- ``DevState.FAULT``: The device is malfunctioning. Functionality cannot be counted on. +- ``DevState.FAULT``: The device is malfunctioning. Functionality cannot be counted on, +- ``DevState.DISABLE``: The device is not operating because its hardware has been shut down. - The ``device.state()`` function can throw an error, if the device cannot be reached at all. For example, because it's docker container is not running. See the :ref:`docker` device on how to start it. @@ -49,6 +50,10 @@ The state of a device is then queried with ``device.state()``. Each device can b alarm -> fault [label = "device", color="green"]; fault -> init [label = "user", color="red"]; fault -> off [label = "user", color="red"]; + standby -> disable [label = "user", color="green"]; + on -> disable [label = "user", color="green"]; + alarm -> disable [label = "user", color="green"]; + disable -> off [label= "user", color="red"]; } @@ -58,6 +63,8 @@ Each device provides the following commands to change the state: :warm_boot(): Turn on the device, but do not change the hardware. Moves from ``OFF`` to ``ON``. +:disable_hardware(): Shut down the hardware related to the device. Moves from ``STANDBY``, ``ON`` or ``ALARM`` to ``DISABLE`` + :off(): Turn the device ``OFF`` from any state. The following procedure is a good way to bring a device to ``ON`` from any state:: @@ -115,7 +122,7 @@ See also :ref:`boot`, which provides functionality to initialise all the devices .. _attributes: Attributes ------------- +```````````````` The device can be operated in ``ON`` state, where it exposes *attributes* and *commands*. The attributes can be accessed as python properties, for example:: @@ -133,7 +140,7 @@ The attributes with an: - ``_RW`` suffix are control points, reflecting the desired state of the hardware. They are read-write, where writing requests the hardware to set the specified value. Reading them returns the last requested value. Meta data -````````````` +""""""""""""""""""""" A description of the attribute can be retrieved using:: @@ -142,7 +149,7 @@ A description of the attribute can be retrieved using:: .. _attribute-masks: Attribute masks ---------------------- +```````````````` Several devices employ *attribute masks* in order to toggle which elements in their hardware array are actually to be controlled. This construct is necessary as most control points consist of arrays of values that cover all hardware elements. These array control points are always fully sent: it is not possible to update only a single element without uploading the rest. Without a mask, it is impossible to control a subset of the hardware. diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst index b3b686818cc758f1ed662f07bff5d4f0095b1920..07c5fb4ac45444e941b31b3869198b2504284389 100644 --- a/tangostationcontrol/docs/source/index.rst +++ b/tangostationcontrol/docs/source/index.rst @@ -18,22 +18,21 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st installation interfaces/overview + devices/overview devices/using - devices/tilebeam - devices/beamlet - devices/digitalbeam devices/antennafield - devices/boot - devices/docker - devices/psoc + devices/tilebeam-digitalbeam + devices/beamlet devices/recv devices/sdp devices/bst-sst-xst + devices/boot + devices/docker + devices/psoc devices/temperature-manager devices/configure configure_station signal_chain - beam_tracking developer faq diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg index cd64d07bf69914aae880fa881b995333942881b3..82d6efb049aa4dae8f7842518c20aea2f4ae1ba1 100644 --- a/tangostationcontrol/setup.cfg +++ b/tangostationcontrol/setup.cfg @@ -37,6 +37,7 @@ console_scripts = l2ss-apsct = tangostationcontrol.devices.apsct:main l2ss-apspu = tangostationcontrol.devices.apspu:main l2ss-psoc = tangostationcontrol.devices.psoc:main + l2ss-pcon = tangostationcontrol.devices.pcon:main l2ss-tilebeam = tangostationcontrol.devices.tilebeam:main l2ss-beamlet = tangostationcontrol.devices.sdp.beamlet:main l2ss-digitalbeam = tangostationcontrol.devices.sdp.digitalbeam:main diff --git a/tangostationcontrol/setup.py b/tangostationcontrol/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..b908cbe55cb344569d32de1dfc10ca7323828dc5 --- /dev/null +++ b/tangostationcontrol/setup.py @@ -0,0 +1,3 @@ +import setuptools + +setuptools.setup() diff --git a/tangostationcontrol/tangostationcontrol/clients/README.md b/tangostationcontrol/tangostationcontrol/clients/README.md index c7085de860b20ade0e8d759f3bbd72d211e452df..9a0c749575c32ad6d582427f154562d97a7fc8a6 100644 --- a/tangostationcontrol/tangostationcontrol/clients/README.md +++ b/tangostationcontrol/tangostationcontrol/clients/README.md @@ -16,11 +16,8 @@ Inside lofar/tango/tangostationcontrol/tangostationcontrol/devices/lofar_device. Init_value: Initialisation value. If none is presents, fills the attribute with zero data of the correct type and dimension **kwargs: any other non attribute_wrapper arguments. NOTE: the `__init__` function contains wrappers for the unassigned read/write functions. In previous versions the read function of an RW attribute used to return the last value it had written *to* the client instead of the value from the client. This has since been changed. - -`initial_value`: - This function fills the attribute with a default value of all zero's with the proper dimensions and type if None is specified. -`Set_comm_client`: +`set_comm_client`: This function can be called to assign a read and write function to the attribute using the data accessor or client given to this function. The attribute wrapper assumes the client is running and has a function called ‘setup_attribute’ which will provide it with a valid read/write function. `async_set_comm_client`: @@ -29,14 +26,6 @@ This function can be called to assign a read and write function to the attribute `set_pass_func`: Can be called to assign a 'fake' read/write function. This is useful as a fallback option while development is still ongoing. -`_decorate_read_function`: -Wrap an attribute read function to annotate its exceptions with our comms_annotation to be able to identify which attribute triggered the error. - - - -`_decorate_write_function`: -Wrap an attribute write function to annotate its exceptions with our comms_annotation to be able to identify which attribute triggered the error. - diff --git a/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py index 5d9f5bdc8cd123e0ffd28c962548fc94bdf82572..ae00635acf68e059c62faf7a5b8d27a2ca435eec 100644 --- a/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py +++ b/tangostationcontrol/tangostationcontrol/clients/attribute_wrapper.py @@ -1,6 +1,8 @@ +from operator import mul +from functools import reduce + from tango.server import attribute -from tango import AttrWriteType -import numpy +from tango import AttrWriteType, AttReqType from tangostationcontrol.devices.device_decorators import fault_on_error import logging @@ -8,12 +10,46 @@ import logging logger = logging.getLogger() +class attribute_io(object): + """ Holds the I/O functionality for an attribute for a specific device. """ + + def __init__(self, device, attribute_wrapper): + # Link to the associated device + self.device = device + + # Link to the associated attribute wrapper + self.attribute_wrapper = attribute_wrapper + + # Link to last (written) value + self.cached_value = None + + # Specific read and write functions for this attribute on this device + self.read_function = lambda: None + self.write_function = lambda value: None + + def cached_read_function(self): + """ Return the last (written) value, if available. Otherwise, read + from the device. """ + + if self.cached_value is not None: + return self.cached_value + + self.cached_value = self.read_function() + return self.cached_value + + def cached_write_function(self, value): + """ Writes the given value to the device, and updates the cache. """ + + self.write_function(value) + self.cached_value = value + + class attribute_wrapper(attribute): """ Wraps all the attributes in a wrapper class to manage most of the redundant code behind the scenes """ - def __init__(self, comms_id=None, comms_annotation=None, datatype=None, dims=(1,), access=AttrWriteType.READ, init_value=None, **kwargs): + def __init__(self, comms_id=None, comms_annotation=None, datatype=None, dims=(1,), access=AttrWriteType.READ, **kwargs): """ wraps around the tango Attribute class. Provides an easier interface for 1d or 2d arrays. Also provides a way to abstract managing the communications interface. @@ -34,60 +70,62 @@ class attribute_wrapper(attribute): self.comms_id = comms_id # store data that can be used to identify the comms interface to use. not used by the wrapper itself self.comms_annotation = comms_annotation # store data that can be used by the comms interface. not used by the wrapper itself - self.init_value = init_value - is_scalar = dims == (1,) - - self.numpy_type = datatype # tango changes our attribute to their representation (E.g numpy.int64 becomes "DevLong64") + self.datatype = datatype - # check if not scalar - if is_scalar: - # scalar, just set the single dimension. + if dims == (1,): # Tango defines a scalar as having dimensions (1,0), see https://pytango.readthedocs.io/en/stable/server_api/attribute.html max_dim_x = 1 max_dim_y = 0 - else: - # get first dimension + dtype = datatype + elif len(dims) == 1: max_dim_x = dims[0] - - # single dimension/spectrum requires the datatype to be wrapped in a tuple - datatype = (datatype,) - - if len(dims) == 2: - # get second dimension - max_dim_y = dims[1] - # wrap the datatype tuple in another tuple for 2d arrays/images - datatype = (datatype,) - else: - max_dim_y = 0 + max_dim_y = 0 + dtype = (datatype,) + elif len(dims) == 2: + max_dim_x = dims[1] + max_dim_y = dims[0] + dtype = ((datatype,),) + else: + # >2D arrays collapse into the X and Y dimensions. The Y (major) dimension mirrors the first dimension given, the + # rest collapses into the X (minor) dimension. + max_dim_x = reduce(mul, dims[1:]) + max_dim_y = dims[0] + dtype = ((datatype,),) if access == AttrWriteType.READ_WRITE: """ If the attribute is of READ_WRITE type, assign the write and read functions to it""" + # we return the last written value, as we are the only ones in control, + # and the hardware does not necessarily return what we've written + # (see L2SDP-725). + @fault_on_error() def write_func_wrapper(device, value): """ write_func_wrapper writes a value to this attribute """ - self.write_function(value) - device.value_dict[self] = value + try: + io = self.get_attribute_io(device) + + return io.cached_write_function(value) + except Exception as e: + raise e.__class__(f"Could not write attribute {comms_annotation}") from e @fault_on_error() def read_func_wrapper(device): """ read_func_wrapper reads the attribute value, stores it and returns it" """ + + # lofar.read_attribute ignores fisallowed. So check again if we're allowed to read. + if not device.is_attribute_access_allowed(AttReqType.READ_REQ): + return None + try: - # we return the last written value, as we are the only ones in control, - # and the hardware does not necessarily return what we've written - # (see L2SDP-725). - if self in device.value_dict: - return device.value_dict[self] - - # value was never written, so obtain current one and cache that instead - value = self.read_function() - device.value_dict[self] = value - return value + io = self.get_attribute_io(device) + + return io.cached_read_function() except Exception as e: raise e.__class__(f"Could not read attribute {comms_annotation}") from e @@ -101,8 +139,15 @@ class attribute_wrapper(attribute): """ read_func_wrapper reads the attribute value, stores it and returns it" """ + + # lofar.read_attribute ignores fisallowed. So check again if we're allowed to read. + if not device.is_attribute_access_allowed(AttReqType.READ_REQ): + return None + try: - return self.read_function() + io = self.get_attribute_io(device) + + return io.read_function() except Exception as e: raise e.__class__(f"Could not read attribute {comms_annotation}") from e @@ -114,60 +159,20 @@ class attribute_wrapper(attribute): # # NOTE: fisallowed=<callable> does not work: https://gitlab.com/tango-controls/pytango/-/issues/435 # So we have to use fisallowed=<str> here, which causes the function device.<str> to be called. - super().__init__(dtype=datatype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, access=access, fisallowed="is_attribute_access_allowed", **kwargs) + super().__init__(dtype=dtype, max_dim_y=max_dim_y, max_dim_x=max_dim_x, access=access, fisallowed="is_attribute_access_allowed", format=str(dims), **kwargs) - def initial_value(self): + def get_attribute_io(self, device): """ - returns a numpy array filled with zeroes fit to the size of the attribute. Or if init_value is not the default None, return that value + returns the attribute I/O functions from a certain device, or registers it if not present """ - if self.init_value is not None: - return self.init_value - - if self.dim_y > 1: - dims = (self.dim_x, self.dim_y) - else: - dims = (self.dim_x,) - - # x and y are swapped for numpy and Tango. to maintain tango conventions, x and y are swapped for numpy - if len(dims) == 2: - numpy_dims = tuple((dims[1], dims[0])) - else: - numpy_dims = dims - if self.dim_x == 1: - - if self.numpy_type == str: - value = '' - else: - value = self.numpy_type(0) - else: - value = numpy.zeros(numpy_dims, dtype=self.numpy_type) - - return value - - def _decorate_read_function(self, read_attr_func): - """ Wrap an attribute read function to annotate its exceptions with our - comms_annotation to be able to identify which attribute triggered the error. """ - def wrapper(): - try: - return read_attr_func() - except Exception as e: - raise Exception(f"Failed to read attribute {self.comms_annotation}") from e - - return wrapper - - def _decorate_write_function(self, write_attr_func): - """ Wrap an attribute write function to annotate its exceptions with our - comms_annotation to be able to identify which attribute triggered the error. """ - def wrapper(value): - try: - write_attr_func(value) - except Exception as e: - raise Exception(f"Failed to write attribute {self.comms_annotation}") from e - - return wrapper + try: + return device._attribute_wrapper_io[self] + except KeyError: + device._attribute_wrapper_io[self] = attribute_io(device, self) + return device._attribute_wrapper_io[self] - def set_comm_client(self, client): + def set_comm_client(self, device, client): """ takes a communications client as input arguments This client should be of a class containing a "get_mapping" function and return a read and write function that the wrapper will use to get/set data. @@ -175,29 +180,28 @@ class attribute_wrapper(attribute): try: read_attr_func, write_attr_func = client.setup_attribute(self.comms_annotation, self) - self.read_function = self._decorate_read_function(read_attr_func) - self.write_function = self._decorate_write_function(write_attr_func) + io = self.get_attribute_io(device) + io.read_function = read_attr_func + io.write_function = write_attr_func except Exception as e: raise Exception(f"Exception while setting {client.__class__.__name__} attribute with annotation: '{self.comms_annotation}'") from e - async def async_set_comm_client(self, client): + async def async_set_comm_client(self, device, client): """ Asynchronous version of set_comm_client. """ try: read_attr_func, write_attr_func = await client.setup_attribute(self.comms_annotation, self) - self.read_function = self._decorate_read_function(read_attr_func) - self.write_function = self._decorate_write_function(write_attr_func) + io = self.get_attribute_io(device) + io.read_function = read_attr_func + io.write_function = write_attr_func except Exception as e: raise Exception(f"Exception while setting {client.__class__.__name__} attribute with annotation: '{self.comms_annotation}'") from e - - def set_pass_func(self): - def pass_func(value=None): - pass - + def set_pass_func(self, device): logger.debug("using pass function for attribute with annotation: {}".format(self.comms_annotation)) - self.read_function = pass_func - self.write_function = pass_func + io = self.get_attribute_io(device) + io.read_function = lambda: None + io.write_function = lambda value: None diff --git a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py index 448b4fc5f1754cb54273140aa240256f4d6ff58d..53211e52fea7472390d82c450543eb8351ca165f 100644 --- a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py +++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py @@ -175,7 +175,7 @@ class OPCUAConnection(AsyncCommClient): # get all the necessary data to set up the read/write functions from the attribute_wrapper dim_x = attribute.dim_x dim_y = attribute.dim_y - ua_type = numpy_to_OPCua_dict[attribute.numpy_type] # convert the numpy type to a corresponding UA type + ua_type = numpy_to_OPCua_dict[attribute.datatype] # convert the numpy type to a corresponding UA type # configure and return the read/write functions prot_attr = ProtocolAttribute(node, dim_x, dim_y, ua_type) diff --git a/tangostationcontrol/tangostationcontrol/clients/snmp_client.py b/tangostationcontrol/tangostationcontrol/clients/snmp_client.py index 4776bd15f922714739c05f8fbb316606e923877f..b161d9b42281b6ef981eee31ebfec6473cb701a9 100644 --- a/tangostationcontrol/tangostationcontrol/clients/snmp_client.py +++ b/tangostationcontrol/tangostationcontrol/clients/snmp_client.py @@ -83,7 +83,10 @@ class SNMP_client(CommClient): # SNMP has tables that require an index number to access them. regular non-table variable have an index of 0 idx = annotation.get('index', 0) - return mib, name, idx + # SNMP values like to use weird units like tenths of amps because its all integers. We added a scaling factor to correct for that. + scaling_factor = annotation.get('scaling_factor', 1) + + return mib, name, idx, scaling_factor except KeyError: raise ValueError(f"SNMP attribute annotation requires a dict argument with both a 'name' and 'mib' key. Instead got: {annotation}") @@ -94,7 +97,7 @@ class SNMP_client(CommClient): dim_x = attribute.dim_x dim_y = attribute.dim_y - dtype = attribute.numpy_type + dtype = attribute.datatype return dim_x, dim_y, dtype @@ -108,11 +111,11 @@ class SNMP_client(CommClient): """ # process the annotation - mib, name, idx = self._process_annotation(annotation) + mib, name, idx, scaling_factor = self._process_annotation(annotation) # get all the necessary data to set up the read/write functions from the attribute_wrapper dim_x, dim_y, dtype = self.setup_value_conversion(attribute) - snmp_attr = snmp_attribute(self.SNMP_comm, mib, name, idx, dtype, dim_x, dim_y) + snmp_attr = snmp_attribute(self.SNMP_comm, mib, name, idx, dtype, dim_x, dim_y, scaling_factor) # return the read/write functions def read_function(): @@ -125,13 +128,14 @@ class SNMP_client(CommClient): class snmp_attribute: - def __init__(self, comm: SNMP_comm, mib, name, idx, dtype, dim_x, dim_y): + def __init__(self, comm: SNMP_comm, mib, name, idx, dtype, dim_x, dim_y, scaling_factor=1): self.comm = comm self.mib = mib self.name = name self.idx = idx self.dtype = dtype + self.scaling_factor = scaling_factor self.len = self.get_len(dim_x, dim_y) self.is_scalar = self.len == 1 @@ -203,7 +207,7 @@ class snmp_attribute: # IpAddress values get printed as their raw value but in hex (7F 20 20 01 for 127.0.0.1 for example) vals.append(varBind[1].prettyPrint()) - elif snmp_type is hlapi.Integer32 or snmp_type is hlapi.Integer and self.dtype == str: + elif (snmp_type is hlapi.Integer32 or snmp_type is hlapi.Integer) and self.dtype == str: # Integers can have 'named values', Where a value can be translated to a specific name. A dict basically # Example: {1: "other", 2: "invalid", 3: "dynamic", 4: "static",} @@ -215,7 +219,10 @@ class snmp_attribute: vals.append(varBind[1].prettyPrint()) else: # convert from the funky pysnmp types to numpy types and then append - vals.append(snmp_to_numpy_dict[snmp_type](varBind[1])) + value = snmp_to_numpy_dict[snmp_type](varBind[1]) + + # scale the value correctly and append. + vals.append(value * self.scaling_factor) if self.is_scalar: vals = vals[0] @@ -235,7 +242,5 @@ class mib_loader: logger.debug(f"mib sources: {self.mibBuilder.getMibSources()}") def load_pymib(self, mib_name): - logger.debug(f"test {self.mibBuilder.getMibSources()}") - logger.debug(f"test {mib_name}") self.mibBuilder.loadModules(mib_name) diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/__init__.py b/tangostationcontrol/tangostationcontrol/clients/statistics/__init__.py similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/__init__.py rename to tangostationcontrol/tangostationcontrol/clients/statistics/__init__.py diff --git a/tangostationcontrol/tangostationcontrol/clients/statistics_client.py b/tangostationcontrol/tangostationcontrol/clients/statistics/client.py similarity index 95% rename from tangostationcontrol/tangostationcontrol/clients/statistics_client.py rename to tangostationcontrol/tangostationcontrol/clients/statistics/client.py index 017ad5e70c34752a8ff815b42105d73c720d8dbf..869cb78dab4b013b7cb6634e517f4dad73f187c5 100644 --- a/tangostationcontrol/tangostationcontrol/clients/statistics_client.py +++ b/tangostationcontrol/tangostationcontrol/clients/statistics/client.py @@ -2,11 +2,10 @@ from queue import Queue import logging import numpy -from .comms_client import AsyncCommClient -from .tcp_replicator import TCPReplicator -from .udp_receiver import UDPReceiver - -from tangostationcontrol.devices.sdp.statistics_collector import StatisticsConsumer +from tangostationcontrol.clients.comms_client import AsyncCommClient +from tangostationcontrol.clients.tcp_replicator import TCPReplicator +from tangostationcontrol.clients.udp_receiver import UDPReceiver +from tangostationcontrol.clients.statistics.consumer import StatisticsConsumer logger = logging.getLogger() diff --git a/tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/clients/statistics/client_thread.py similarity index 100% rename from tangostationcontrol/tangostationcontrol/clients/statistics_client_thread.py rename to tangostationcontrol/tangostationcontrol/clients/statistics/client_thread.py diff --git a/tangostationcontrol/tangostationcontrol/clients/statistics/consumer.py b/tangostationcontrol/tangostationcontrol/clients/statistics/consumer.py new file mode 100644 index 0000000000000000000000000000000000000000..066839a293f61aa65b0da741800b8cdac2e31f34 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/clients/statistics/consumer.py @@ -0,0 +1,70 @@ +import logging +from threading import Thread +from queue import Queue + +from tangostationcontrol.clients.statistics.client_thread import StatisticsClientThread +from tangostationcontrol.devices.sdp.statistics_collector import StatisticsCollector + +logger = logging.getLogger() + + +class StatisticsConsumer(Thread, StatisticsClientThread): + """ Base class to process statistics packets from a queue, asynchronously. """ + + # Maximum time to wait for the Thread to get unstuck, if we want to stop + DISCONNECT_TIMEOUT = 10.0 + + # No default options required, for now? + _DEFAULT_OPTIONS = {} + + def __init__(self, queue: Queue, collector: StatisticsCollector): + self.queue = queue + self.collector = collector + self.last_packet = None + + super().__init__() + self.start() + + @property + def _options(self) -> dict: + return StatisticsConsumer._DEFAULT_OPTIONS + + def run(self): + logger.info("Starting statistics thread") + + while True: + self.last_packet = self.queue.get() + + # This is the exception/slow path, but python doesn't allow us to optimise that + if self.last_packet is None: + # None is the magic marker to stop processing + break + + try: + self.collector.process_packet(self.last_packet) + except ValueError as e: + logger.exception("Could not parse statistics packet") + + # continue processing + + logger.info("Stopped statistics thread") + + def join(self, timeout=0): + # insert magic marker + self.queue.put(None) + logger.info("Sent shutdown to statistics thread") + + super().join(timeout) + + def disconnect(self): + # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver + # and StatisticsConsumer. + if not self.is_alive(): + return + + # try to get the thread shutdown, but don't stall forever + self.join(self.DISCONNECT_TIMEOUT) + + if self.is_alive(): + # there is nothing we can do except wait (stall) longer, which could be indefinitely. + logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.") diff --git a/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py index 5b32466eabe8690936cae768814841a1f7f1f1b0..fd44bcc7247313a606fe20e8e105da4335780338 100644 --- a/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py +++ b/tangostationcontrol/tangostationcontrol/clients/tcp_replicator.py @@ -6,7 +6,7 @@ import asyncio import logging import sys -from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread +from tangostationcontrol.clients.statistics.client_thread import StatisticsClientThread logger = logging.getLogger() diff --git a/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py index 9f90782106602aab556061c2cfec19caef5d7c87..d07982f17cff4661dd441294614017781d5ce8a6 100644 --- a/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py +++ b/tangostationcontrol/tangostationcontrol/clients/udp_receiver.py @@ -7,7 +7,7 @@ import socket import time from typing import List # not needed for python3.9+, where we can use the type "list[Queue]" directly -from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread +from tangostationcontrol.clients.statistics.client_thread import StatisticsClientThread logger = logging.getLogger() diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py index a2c4ad391c17e752c19e7a07652d08fbfc2d2efa..89ab11c0db3b31af805b5cda78ce21a77ee9318a 100644 --- a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py +++ b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py @@ -140,7 +140,7 @@ def configure_logger(logger: logging.Logger=None, log_extra=None, debug=False): # Always also log the hostname because it makes the origin of the log clear. hostname = socket.gethostname() - formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)d %(levelname)s - HOST="{}" DEVICE="%(tango_device)s" PID="%(process)d" TNAME="%(threadName)s" FILE="%(pathname)s" LINE="%(lineno)d" FUNC="%(funcName)s" MSG="%(message)s"'.format(hostname), datefmt = '%Y-%m-%dT%H:%M:%S') + formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)d %(levelname)s - %(tango_device)s: %(message)s [%(funcName)s in %(filename)s:%(lineno)d]'.format(hostname), datefmt = '%Y-%m-%dT%H:%M:%S') handler.setFormatter(formatter) handler.addFilter(LogSuppressErrorSpam()) handler.addFilter(LogAnnotator()) @@ -171,6 +171,8 @@ def configure_logger(logger: logging.Logger=None, log_extra=None, debug=False): except Exception: logger.exception("Cannot forward logs to ELK.") + # Don't log to Tango to reduce log spam + """ # Log to Tango try: handler = TangoLoggingHandler() @@ -179,6 +181,7 @@ def configure_logger(logger: logging.Logger=None, log_extra=None, debug=False): logger.addHandler(handler) except Exception: logger.exception("Cannot forward logs to Tango.") + """ return logger diff --git a/tangostationcontrol/tangostationcontrol/common/states.py b/tangostationcontrol/tangostationcontrol/common/states.py new file mode 100644 index 0000000000000000000000000000000000000000..cc458005621a3116b7a05839e81727d3dc796e70 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/common/states.py @@ -0,0 +1,12 @@ +from tango import DevState + +# The Device states in which we consider our device operational, +# and thus allow interaction. +OPERATIONAL_STATES = [DevState.ON, DevState.ALARM] + +# States in which Initialise() has happened, and the hardware +# can thus be configured or otherwise interacted with. +INITIALISED_STATES = OPERATIONAL_STATES + [DevState.STANDBY, DevState.DISABLE] + +# States in which most commands are allowed +DEFAULT_COMMAND_STATES = INITIALISED_STATES diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py index 38701a47ae231c938012099596802aa74fc54c1c..4c39a6bdea1db44f52c87f2fd3a4e6add71e3d99 100644 --- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py +++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py @@ -6,11 +6,15 @@ """ AntennaField Device Server for LOFAR2.0 """ +from enum import IntEnum +from math import pi +import numpy + +# PyTango imports from tango import DeviceProxy, DevSource, AttrWriteType, DevVarFloatArray, DevVarLongArray from tango.server import device_property, attribute, command -import numpy -from math import pi +# Additional import from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions @@ -26,6 +30,17 @@ __all__ = ["AntennaField", "HBATToRecvMapper", "main"] # Highest number of HBA tiles we support per AntennaField MAX_NUMBER_OF_HBAT = 96 +class AntennaUse(IntEnum): + AUTO = 0 # use antenna only if its OK or SUSPICIOUS + ON = 1 # force antenna to be on, regardless of quality + OFF = 2 # force antenna to be off, regardless of quality + +class AntennaQuality(IntEnum): + OK = 0 + SUSPICIOUS = 1 + BROKEN = 2 + BEYOND_REPAIR = 3 + class mapped_attribute(attribute): def __init__(self, mapping_attribute, dtype, max_dim_x, max_dim_y=0, access=AttrWriteType.READ, **kwargs): @@ -70,6 +85,22 @@ class AntennaField(lofar_device): calculated, as well as the geohash. """ + # ----- Antenna states + + Antenna_Quality = device_property( + doc="Operational quality state of each antenna", + dtype='DevVarUShortArray', + mandatory=False, + default_value = numpy.array([AntennaQuality.OK] * MAX_NUMBER_OF_HBAT) + ) + + Antenna_Use = device_property( + doc="Operational State of each antenna", + dtype='DevVarUShortArray', + mandatory=False, + default_value = numpy.array([AntennaUse.AUTO] * MAX_NUMBER_OF_HBAT) + ) + # ----- Position information Antenna_Field_Reference_ITRF = device_property( @@ -164,6 +195,13 @@ class AntennaField(lofar_device): default_value = [] ) + Antenna_Quality_R = attribute(access=AttrWriteType.READ, + dtype=(numpy.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) + Antenna_Use_R = attribute(access=AttrWriteType.READ, + dtype=(numpy.uint16,), max_dim_x=MAX_NUMBER_OF_HBAT) + Antenna_Usage_Mask_R = attribute(access=AttrWriteType.READ, + dtype=(bool,), max_dim_x=MAX_NUMBER_OF_HBAT) + HBAT_ANT_mask_RW = mapped_attribute("ANT_mask_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_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) @@ -209,6 +247,19 @@ class AntennaField(lofar_device): doc='Number of HBAT in this field', dtype=numpy.int32) + def read_Antenna_Use_R(self): + return self.Antenna_Use + + def read_Antenna_Quality_R(self): + return self.Antenna_Quality + + def read_Antenna_Usage_Mask_R(self): + antenna_usage = numpy.zeros(MAX_NUMBER_OF_HBAT, dtype=bool) + for n in range(0, MAX_NUMBER_OF_HBAT): + 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)) + return antenna_usage + def read_nr_tiles_R(self): # The number of tiles should be equal to: # * the number of elements in the HBAT_Control_to_RECV_mapping (after reshaping), @@ -278,12 +329,6 @@ class AntennaField(lofar_device): def read_HBAT_reference_GEOHASH_R(self): return GEO_to_GEOHASH(self.read_HBAT_reference_GEO_R()) - @log_exceptions() - def configure_for_initialise(self): - super().configure_for_initialise() - self.__setup_all_receiver_proxies() - self.__setup_mapper() - def __setup_all_receiver_proxies(self): self.recv_proxies = [] @@ -317,6 +362,43 @@ class AntennaField(lofar_device): for idx, recv_proxy in enumerate(self.recv_proxies): recv_proxy.write_attribute(mapped_point, mapped_value[idx]) + # -------- + # Overloaded functions + # -------- + + @log_exceptions() + def configure_for_initialise(self): + super().configure_for_initialise() + self.__setup_all_receiver_proxies() + self.__setup_mapper() + + @log_exceptions() + def _prepare_hardware(self): + # Initialise the RCU hardware. + 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 + # WARN: Needed in configure_for_initialise but Tango does not allow to write attributes in INIT state + self.proxy.write_attribute('HBAT_ANT_mask_RW', self.read_attribute('Antenna_Usage_Mask_R')) + # -------- # Commands # -------- @@ -375,14 +457,10 @@ class HBATToRecvMapper(object): "RCU_band_select_RW": numpy.zeros(96, dtype=numpy.int64) } self.__reshape_attributes_in = { - "ANT_mask_RW": (96,), "HBAT_BF_delay_steps_RW": (96, 32), - "RCU_band_select_RW": (96,), } self.__reshape_attributes_out = { - "ANT_mask_RW": (32, 3), "HBAT_BF_delay_steps_RW": (96, 32), - "RCU_band_select_RW": (32, 3) } def map_read(self, mapped_attribute, recv_results): diff --git a/tangostationcontrol/tangostationcontrol/devices/apsct.py b/tangostationcontrol/tangostationcontrol/devices/apsct.py index e4e411e115713931e20bceb7c290242710156c3e..60563f1138c125c79274b1c2f5342b7df68c02e8 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apsct.py +++ b/tangostationcontrol/tangostationcontrol/devices/apsct.py @@ -21,9 +21,9 @@ import numpy from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import device_logging_to_python +from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES from tangostationcontrol.devices.device_decorators import only_in_states from tangostationcontrol.devices.opcua_device import opcua_device -from tangostationcontrol.devices.lofar_device import lofar_device import logging logger = logging.getLogger() @@ -97,8 +97,8 @@ class APSCT(opcua_device): self.read_attribute("APSCT_PLL_160MHz_locked_R") and self.read_attribute("APSCT_PLL_160MHz_error_R")] return any(errors) - APSCT_TEMP_error_R = attribute(dtype=bool, polling_period=1000) - APSCT_VOUT_error_R = attribute(dtype=bool) + APSCT_TEMP_error_R = attribute(dtype=bool, fisallowed="is_attribute_access_allowed", polling_period=1000) + APSCT_VOUT_error_R = attribute(dtype=bool, fisallowed="is_attribute_access_allowed") def read_APSCT_TEMP_error_R(self): return (self.alarm_val("APSCT_TEMP_R")) @@ -133,13 +133,20 @@ class APSCT(opcua_device): else: raise Exception("200MHz signal is not locked. The subrack probably do not receive clock input or the CLK PCB is broken?") + def _disable_hardware(self): + """ Disable the APSCT hardware. """ + + # Turn off the APSCT + self.APSCT_off() + self.wait_attribute("APSCTTR_translator_busy_R", False, self.APSCT_On_Off_timeout) + # -------- # Commands # -------- @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def APSCT_off(self): """ @@ -149,7 +156,7 @@ class APSCT(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def APSCT_200MHz_on(self): """ @@ -159,7 +166,7 @@ class APSCT(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def APSCT_160MHz_on(self): """ diff --git a/tangostationcontrol/tangostationcontrol/devices/apspu.py b/tangostationcontrol/tangostationcontrol/devices/apspu.py index ff32c3c9d9befb2563be1999f7ab3616fcf51456..08c43f5a4362b22bd15ab0065e243f741add98ae 100644 --- a/tangostationcontrol/tangostationcontrol/devices/apspu.py +++ b/tangostationcontrol/tangostationcontrol/devices/apspu.py @@ -63,7 +63,7 @@ class APSPU(opcua_device): # ---------- # Summarising Attributes # ---------- - APSPU_error_R = attribute(dtype=bool) + APSPU_error_R = attribute(dtype=bool, fisallowed="is_attribute_access_allowed") def read_APSPU_error_R(self): return ((self.read_attribute("APSPUTR_I2C_error_R") > 0) @@ -98,6 +98,9 @@ class APSPU(opcua_device): # overloaded functions # -------- + def _disable_hardware(self): + """ Disable the APSPU hardware. """ + super()._disable_hardware() # -------- # Commands diff --git a/tangostationcontrol/tangostationcontrol/devices/beam_device.py b/tangostationcontrol/tangostationcontrol/devices/beam_device.py index 3bf7d49932742dde3140d532b1b2c07aebcd991e..c8ccf78e649b194b9efd0a2e97231310cf091581 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam_device.py @@ -9,8 +9,10 @@ import datetime import numpy +import time from json import loads from threading import Thread, Lock, Condition +from statistics import median # PyTango imports from tango.server import attribute, command, device_property @@ -20,6 +22,7 @@ from tango import AttrWriteType, DebugIt, DevVarStringArray, DevVarDoubleArray, from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.measures import get_measures_directory, get_available_measures_directories, download_measures, use_measures_directory, restart_python from tangostationcontrol.common.lofar_logging import log_exceptions +from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES from tangostationcontrol.devices.device_decorators import TimeIt, only_in_states, fault_on_error from tangostationcontrol.beam.delays import delay_calculator from tangostationcontrol.devices.lofar_device import lofar_device @@ -42,11 +45,18 @@ class beam_device(lofar_device): default_value = 10.0 ) + Beam_tracking_application_offset = device_property( + dtype='DevFloat', + doc='Amount of time to send the weights earlier than the interval, to allow the hardware to get apply them in time [seconds]', + mandatory=False, + default_value = 0.05 + ) + Beam_tracking_preparation_period = device_property( dtype='DevFloat', doc='Preparation time [seconds] needed before starting update operation', mandatory=False, - default_value = 0.25 + default_value = 0.4 ) Tracking_enabled_RW_default = device_property( @@ -93,8 +103,20 @@ class beam_device(lofar_device): dtype=bool, fget=lambda self: self._tracking_enabled_rw) - Duration_update_pointing_R = attribute(access=AttrWriteType.READ, - dtype=numpy.float64, fget=lambda self: self.update_pointing.statistics["last"] or 0) + Duration_compute_weights_R = attribute(access=AttrWriteType.READ, + doc="Time it took to compute weights", + unit="s", + dtype=numpy.float64, fget=lambda self: self._compute_weights.statistics["last"] or 0) + + Duration_preparation_period_slack_R = attribute(access=AttrWriteType.READ, + doc="Slack between computing and applying weights", + unit="s", + dtype=numpy.float64, fget=lambda self: self._wait_to_apply_weights.statistics["last"] or 0) + + Duration_apply_weights_R = attribute(access=AttrWriteType.READ, + doc="Time it took to upload weights", + unit="s", + dtype=numpy.float64, fget=lambda self: self._apply_weights.statistics["last"] or 0) def write_Pointing_direction_RW(self, value): """ Setter method for attribute Pointing_direction_RW """ @@ -122,9 +144,8 @@ class beam_device(lofar_device): self.Beam_tracker.stop() - @TimeIt() def update_pointing(self, timestamp: datetime.datetime): - """ Update the weights for the configured pointings, for the given timestamp. """ + """ Update the weights for the configured pointings, for the given timestamp, at the given timestamp. """ self._set_pointing(self._pointing_direction_rw, timestamp) # -------- @@ -138,6 +159,36 @@ class beam_device(lofar_device): raise NotImplementedError + @TimeIt() + def _compute_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime) -> numpy.array: + """ + Calculate and he hardware-specific delay weights based on the 2D pointing list (num_pointings x 3) and the timestamp + """ + + raise NotImplementedError + + @TimeIt() + def _apply_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime, weights: numpy.array) -> numpy.array: + """ + Upload the hardware-specific delay weights based on the 2D pointing list (num_pointings x 3) and the timestamp + + Also updates _pointing_direction_r and _pointing_timestamp_r according to which anetennas got their pointing updated. + """ + + raise NotImplementedError + + @TimeIt() + def _wait_to_apply_weights(self, timestamp: datetime.datetime): + # expected time required to upload weights to hardware (use last 10 measured durations) + expected_application_time = median(self._apply_weights.statistics["history"][-10:] or [0.1]) + + # wait until provided time occurs, but don't start sleeping long here + sleep_time = (timestamp - datetime.datetime.now()).total_seconds() - expected_application_time - self.Beam_tracking_application_offset + if sleep_time > 1: + raise ValueError(f"Provided timestamp is too far into the future to apply at real time: {sleep_time} seconds from now.") + + time.sleep(max(0, sleep_time)) + def _set_pointing(self, pointing_direction: numpy.array, timestamp: datetime.datetime): """ Calculate and Upload the hardware-specific delay weights based on the 2D pointing list (num_pointings x 3) and the timestamp @@ -145,7 +196,14 @@ class beam_device(lofar_device): Also updates _pointing_direction_r and _pointing_timestamp_r according to which anetennas got their pointing updated. """ - raise NotImplementedError + # prepare weights + weights = self._compute_weights(pointing_direction, timestamp) + + # wait until we can apply them + self._wait_to_apply_weights(timestamp) + + # upload weights + self._apply_weights(pointing_direction, timestamp, weights) # -------- # overloaded functions @@ -206,7 +264,7 @@ class beam_device(lofar_device): @command(dtype_in=DevVarStringArray) @DebugIt() @log_exceptions() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def set_pointing(self, pointing_direction: list): """ Compute and uploads the hardware-specific delays based on a given pointing direction @@ -220,7 +278,7 @@ class beam_device(lofar_device): @command(dtype_in = DevString) @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def set_pointing_for_specific_time(self, parameters: DevString = None): """ Compute and uploads the hardware-specific delays based on a given pointing direction @@ -245,7 +303,7 @@ class beam_device(lofar_device): @command(dtype_in=DevVarStringArray, dtype_out=DevVarDoubleArray) @DebugIt() @log_exceptions() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def delays(self, pointing_direction: numpy.array): """ Calculate the delays based on the pointing list and the timestamp @@ -404,8 +462,18 @@ class BeamTracker(): # Check if flag beamtracking is true with self.update_lock: while not self.done: + + # apply next pointing at next interval, + # or immediately if it is stale (just changed). + now = datetime.datetime.now() + if self.stale_pointing: + next_update_in = now + else: + next_update_in = now + datetime.timedelta(seconds=now.timestamp() % self.interval) + + # update pointing at requested time self.stale_pointing = False - self.update_pointing_callback(datetime.datetime.now()) + self.update_pointing_callback(next_update_in) # sleep until the next update, or when interrupted (this releases the lock, allowing for notification) # note that we need wait_for as conditions can be triggered multiple times in succession diff --git a/tangostationcontrol/tangostationcontrol/devices/boot.py b/tangostationcontrol/tangostationcontrol/devices/boot.py index d5834e231da0423df69ba48050da5abd08d8865c..cb172c34d3b4249691518609f18bd5a7ceced613 100644 --- a/tangostationcontrol/tangostationcontrol/devices/boot.py +++ b/tangostationcontrol/tangostationcontrol/devices/boot.py @@ -26,6 +26,7 @@ from tangostationcontrol.devices.device_decorators import only_in_states from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions +from tangostationcontrol.common.states import OPERATIONAL_STATES import logging logger = logging.getLogger() @@ -153,7 +154,7 @@ class DevicesInitialiser(object): continue if self.is_available(device): - if self.reboot or self.devices[device].state() not in lofar_device.OPERATIONAL_STATES: + if self.reboot or self.devices[device].state() not in OPERATIONAL_STATES: self.stop_device(device) self.boot_device(device) @@ -204,7 +205,7 @@ class DevicesInitialiser(object): else: proxy.warm_boot() - if proxy.state() not in lofar_device.OPERATIONAL_STATES: + if proxy.state() not in OPERATIONAL_STATES: raise InitialisationException(f"Could not boot device {device_name}. It reports status: {proxy.status()}") self.set_status(f"[restarting {device_name}] Succesfully booted.") @@ -234,6 +235,7 @@ class Boot(lofar_device): mandatory=False, default_value=["STAT/Docker/1", # Docker controls the device containers, so it goes before anything else "STAT/PSOC/1", # PSOC boot early to detect power delivery failure as fast as possible + "STAT/PCON/1", # PCON boot early because it is responsible for power delivery. "STAT/APSPU/1", # APS Power Units control other hardware we want to initialise "STAT/APSCT/1", "STAT/RECV/1", # RCUs are input for SDP, so initialise them first @@ -317,14 +319,14 @@ class Boot(lofar_device): @command() @DebugIt() - @only_in_states(lofar_device.OPERATIONAL_STATES) + @only_in_states(OPERATIONAL_STATES) @log_exceptions() def boot(self): self._boot(reboot=False, initialise_hardware=self.Initialise_Hardware) @command() @DebugIt() - @only_in_states(lofar_device.OPERATIONAL_STATES) + @only_in_states(OPERATIONAL_STATES) @log_exceptions() def reboot(self): self._boot(reboot=True, initialise_hardware=self.Initialise_Hardware) diff --git a/tangostationcontrol/tangostationcontrol/devices/docker_device.py b/tangostationcontrol/tangostationcontrol/devices/docker_device.py index 5066e9305014bb6ce3e98ba69491c01e5e6e8821..b98bf53e95c65a12c5cdbc224f43d4acd674f4de 100644 --- a/tangostationcontrol/tangostationcontrol/devices/docker_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/docker_device.py @@ -116,7 +116,7 @@ class Docker(lofar_device): async def _connect_docker(self): # tie attributes to client for i in self.attr_list(): - await i.async_set_comm_client(self.docker_client) + await i.async_set_comm_client(self, self.docker_client) await self.docker_client.start() diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index 0cc6fa0bbdafb6ae9ca6c424b4bee0930b1d3ba2..665caca7a4d0439e0a2195d9939210229b71cdb0 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -23,6 +23,7 @@ import textwrap from tangostationcontrol import __version__ as version from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.common.lofar_logging import log_exceptions +from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES, INITIALISED_STATES from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error from tangostationcontrol.toolkit.archiver import Archiver @@ -44,33 +45,26 @@ class lofar_device(Device, metaclass=DeviceMeta): ON = Device is fully configured, functional, controls the hardware, and is possibly actively running, ALARM = Device is operating but one of its attributes is out of range, FAULT = Device detected an unrecoverable error, and is thus malfunctional, + DISABLE = Device has shut down all its dependant hardware OFF = Device is turned off, drops connection to the hardware, The following state transitions are implemented: - boot -> OFF: Triggered by tango. Device will be instantiated, - OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), - INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, - STANDBY -> ON: Triggered by user. Device reports to be functional, - ON -> ALARM: Triggered by tango. Device has attribute(s) with value(s) exceeding their alarm treshold, - * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, - * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, - * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, - FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, + boot -> OFF: Triggered by tango. Device will be instantiated, + OFF -> INIT: Triggered by device. Device will initialise (connect to hardware, other devices), + INIT -> STANDBY: Triggered by device. Device is initialised, and is ready for additional configuration by the user, + STANDBY -> ON: Triggered by user. Device reports to be functional, + STANDBY -> DISABLE: Triggered by user. Device has shut down its hardware. Triggered by the disable_hardware() command, + ON -> DISABLE: Triggered by user. Device has shut down its hardware. Triggered by the disable_hardware() command, + ALARM -> DISABLE: Triggered by user. Device has shut down its hardware. Triggered by the disable_hardware() command, + ON -> ALARM: Triggered by tango. Device has attribute(s) with value(s) exceeding their alarm treshold, + * -> FAULT: Triggered by device. Device has degraded to malfunctional, for example because the connection to the hardware is lost, + * -> FAULT: Triggered by user. Emulate a forced malfunction for integration testing purposes, + * -> OFF: Triggered by user. Device is turned off. Triggered by the Off() command, + FAULT -> INIT: Triggered by user. Device is reinitialised to recover from an error, The user triggers their transitions by the commands reflecting the target state (Initialise(), On(), Fault()). """ - # The Device states in which we consider our device operational, - # and thus allow interaction. - OPERATIONAL_STATES = [DevState.ON, DevState.ALARM] - - # States in which Initialise() has happened, and the hardware - # can thus be configured or otherwise interacted with. - INITIALISED_STATES = OPERATIONAL_STATES + [DevState.STANDBY] - - # States in which most commands are allowed - DEFAULT_COMMAND_STATES = INITIALISED_STATES - # ---------- # Attributes # ---------- @@ -88,15 +82,15 @@ class lofar_device(Device, metaclass=DeviceMeta): """ Return a list of all the attribute_wrapper members of this class. """ return [v for k, v in cls.__dict__.items() if type(v) == attribute_wrapper] - def setup_value_dict(self): - """ set the initial value for all the attribute wrapper objects""" + def setup_attribute_wrapper(self): + """ prepare the caches for attribute wrapper objects""" - self.value_dict = {i: i.initial_value() for i in self.attr_list()} + self._attribute_wrapper_io = {} def is_attribute_access_allowed(self, req_type): """ Returns whether an attribute wrapped by the attribute_wrapper be accessed. """ - return self.get_state() in [DevState.STANDBY, DevState.ON, DevState.ALARM] + return self.get_state() in INITIALISED_STATES @log_exceptions() def init_device(self): @@ -153,7 +147,7 @@ class lofar_device(Device, metaclass=DeviceMeta): self.set_state(DevState.INIT) self.set_status("Device is in the INIT state.") - self.setup_value_dict() + self.setup_attribute_wrapper() # reload our class & device properties from the Tango database self.get_device_properties() @@ -231,7 +225,9 @@ class lofar_device(Device, metaclass=DeviceMeta): :return:None """ if self.get_state() == DevState.OFF: - raise Exception("IllegalCommand: Cannot go from FAULT -> OFF") + # Spurious FAULT + logger.warning("Requested to go to FAULT state, but am already in OFF state.") + return if self.get_state() == DevState.FAULT: # Already faulting. Don't complain. @@ -278,6 +274,15 @@ class lofar_device(Device, metaclass=DeviceMeta): try: default_value = getattr(self, f"{name}_default") + # properties are always 0D or 1D, but arrays can be 2D. + # in the latter case, we need to reshape the default. + attr = getattr(self, name) + max_dim_x, max_dim_y = attr.get_max_dim_x(), attr.get_max_dim_y() + + if max_dim_y > 1: + # 2D array -> reshape 1D default + default_value = numpy.array(default_value).reshape(max_dim_y, max_dim_x) + # set the attribute to the configured default logger.debug(textwrap.shorten(f"Setting attribute {name} to {default_value}", 150)) self.proxy.write_attribute(name, default_value) @@ -348,6 +353,24 @@ class lofar_device(Device, metaclass=DeviceMeta): # This is just the command version of _initialise_hardware(). self._initialise_hardware() + @only_in_states(INITIALISED_STATES) + @fault_on_error() + @command() + @DebugIt() + def disable_hardware(self): + """ Disable the hardware related to the device. """ + + if self.get_state() == DevState.DISABLE: + # Already disabled. + logger.warning("Requested to go to DISABLE state, but am already in DISABLE state.") + return + + self._disable_hardware() + + # Set state to DISABLE + self.set_state(DevState.DISABLE) + self.set_status("Device is in the DISABLE state.") + @only_in_states(DEFAULT_COMMAND_STATES) @command(dtype_out = DevDouble) def max_archiving_load(self): @@ -397,6 +420,10 @@ class lofar_device(Device, metaclass=DeviceMeta): """ Override this method to initialise any hardware after configuring it. """ pass + def _disable_hardware(self): + """ Override this method to disable any hardware related to the device. """ + pass + def read_attribute(self, attr_name): """ Read the value of a certain attribute (directly from the hardware). """ diff --git a/tangostationcontrol/tangostationcontrol/devices/mibs/ACC.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/ACC.mib new file mode 100644 index 0000000000000000000000000000000000000000..82cc818f37aa03bf335d58302b7f5c22bda9252a --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/mibs/ACC.mib @@ -0,0 +1,1092 @@ +-- ACC Mib v. 1.00 + +-- powecMIB MODULE-IDENTITY +-- LAST-UPDATED "0705111430Z" +-- ORGANIZATION "Power-One, Powec" +-- CONTACT-INFO "Arild Sageboe, arild.sageboe@power-one.com" +-- DESCRIPTION "System Information, ACC Power Systems" +-- ::= { enterprises 5961 } + +ACC-MIB DEFINITIONS ::= BEGIN + + IMPORTS + + enterprises, IpAddress FROM RFC1155-SMI + DisplayString FROM RFC1213-MIB + OBJECT-TYPE FROM RFC-1212 + TRAP-TYPE FROM RFC-1215; + + + + powecMIB OBJECT IDENTIFIER ::= { enterprises 5961 } + accPowerSystem OBJECT IDENTIFIER ::= { powecMIB 4 } + + systemInfo OBJECT IDENTIFIER ::= { accPowerSystem 1 } + powerSystem OBJECT IDENTIFIER ::= { accPowerSystem 2 } + alarmInfo OBJECT IDENTIFIER ::= { accPowerSystem 3 } + systemParameters OBJECT IDENTIFIER ::= { accPowerSystem 4 } + powecTrap OBJECT IDENTIFIER ::= { accPowerSystem 99 } + +-- SYSTEM INFO variables + +systemName OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-write + STATUS mandatory + DESCRIPTION + "System Type String for the DC System Site" + ::= { systemInfo 1 } + +powerSystemType OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "String containing the type of power supply control module (ACC)" + ::= { systemInfo 2 } + +powerSystemSoftVer OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "String containing the software version of the control module" + ::= { systemInfo 3 } + +powerSystemSerialNo OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Serial Number of the control module" + ::= { systemInfo 4 } + +outputData OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-write + STATUS mandatory + DESCRIPTION + "String containing a description of the DC Output" + ::= { systemInfo 5 } + +batteryDescript OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-write + STATUS mandatory + DESCRIPTION + "String containing a description of the Battery" + ::= { systemInfo 6 } + +batteryCapacity OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "An integer containing battery capacity in Ah" + + ::= { systemInfo 7 } + +-- POWER SYSTEM variables + +systemVoltage OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the system DC voltage (voltage * 100)" + ::= { powerSystem 1 } + +loadCurrent OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the system load current (current * 10)" + ::= { powerSystem 2 } + +batteryCurrent OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the battery current (current * 10)" + ::= { powerSystem 3 } + + +rectifierCurrent OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the rectifier current (current * 10)" + ::= { powerSystem 4 } + +battTemperature OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the battery temperature(Degree C)" + ::= { powerSystem 5 } + +acPhase1Voltage OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing AC input voltage for phase 1 (Volt), (-1) means measurement not available" + ::= { powerSystem 6 } + +acPhase2Voltage OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing AC input voltage for phase 2 (Volt), (-1) means measurement not available" + ::= { powerSystem 7 } + +acPhase3Voltage OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing AC input voltage for phase 3 (Volt), (-1) means measurement not available" + ::= { powerSystem 8 } + +remainBatteryTime OBJECT-TYPE + SYNTAX INTEGER + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A variable containing the remaining battery time in (minutes)" + ::= { powerSystem 9 } + +-- ALARM INFO variables (Alarm and Status Information) +-- The alarmData, extraAlarmData and statusData +-- are integers from 0-255 containing the byte value received +-- from the controlmodule. + +numbOfAlarms OBJECT-TYPE + SYNTAX INTEGER(0..100) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Variable containing the number of active alarms at the moment, when sent with a trap 1 indicate active, 0 indicate clear" + ::= { alarmInfo 1 } + +alarmData1 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing alarm data 1" + ::= { alarmInfo 2 } + +alarmData2 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing alarm data 2" + ::= { alarmInfo 3 } + +alarmData3 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing alarm data 3" + ::= { alarmInfo 4 } + +extraAlarmData1 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing extra alarm data 1" + ::= { alarmInfo 5 } + +extraAlarmData2 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing extra alarm data 2" + ::= { alarmInfo 6 } + +statusData1 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing status data 1" + ::= { alarmInfo 7 } + +statusData2 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing status data 2" + ::= { alarmInfo 8 } + +statusData3 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing status data 3" + ::= { alarmInfo 9 } + +statusData4 OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-only + STATUS mandatory + DESCRIPTION + "A byte containing status data 4" + ::= { alarmInfo 10 } + +extAlarm1Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 1 name" + ::= { alarmInfo 11 } + +extAlarm2Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 2 name" + ::= { alarmInfo 12 } + +extAlarm3Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 3 name" + ::= { alarmInfo 13 } + +extAlarm4Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 4 name" + ::= { alarmInfo 14 } + +extAlarm5Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 5 name" + ::= { alarmInfo 15 } + +extAlarm6Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 6 name" + ::= { alarmInfo 16 } + +extAlarm7Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 7 name" + ::= { alarmInfo 17 } + +extAlarm8Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 8 name" + ::= { alarmInfo 18 } + +extAlarm9Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 9 name" + ::= { alarmInfo 19 } + +extAlarm10Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 10 name" + ::= { alarmInfo 20 } + +extAlarm11Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 11 name" + ::= { alarmInfo 21 } + +extAlarm12Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 12 name" + ::= { alarmInfo 22 } + +extAlarm13Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 13 name" + ::= { alarmInfo 23 } + +extAlarm14Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 14 name" + ::= { alarmInfo 24 } + +extAlarm15Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 15 name" + ::= { alarmInfo 25 } + +extAlarm16Name OBJECT-TYPE + SYNTAX DisplayString + ACCESS read-only + STATUS mandatory + DESCRIPTION + "Ext.alarm 16 name" + ::= { alarmInfo 26 } + +-- SYSTEM PARAMETERS + +uFlag OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the operation mode Flag (normal, boost charge, test, spare)" + ::= { systemParameters 1 } + +u1NormalRef OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing U1 (Normal) Reference Voltage (Volt*10)" + ::= { systemParameters 2 } + +u2BoostRef OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing U2 (Boost) Reference Voltage (Volt*10)" + ::= { systemParameters 3 } + +u3TestRef OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing U3 (Test) Reference Voltage (Volt*10)" + ::= { systemParameters 4 } + +u4SpareRef OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing U4 (Spare) Reference Voltage (Volt*10)" + ::= { systemParameters 5 } + + +lowVoltLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Low Voltage Limit (Volt*10)" + ::= { systemParameters 6 } + + +lowVoltDisconVoltLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Low Voltage Disconnect Limit (Volt*10)" + ::= { systemParameters 7 } + +lowVoltReconLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Low Voltage Reconnect Limit (Volt*10)" + ::= { systemParameters 8 } + +partLoadDiscon1Limit OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Partial Load Disconnection Limit. The limit will be in (V*10), or + in (minutes) depending on the configuration of the ACC" + ::= { systemParameters 9 } + +partLoadDiscon2Limit OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Partial Load Disconnection Limit. The limit will be in (V*10), or + in (minutes) depending on the configuration of the ACC" + ::= { systemParameters 10 } + +partLoadDiscon3Limit OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Partial Load Disconnection Limit. The limit will be in (V*10), or + in (minutes) depending on the configuration of the ACC" + ::= { systemParameters 11 } + +highVoltLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the High Voltage Limit (Volt*10)" + ::= { systemParameters 12 } + +highVoltDisconLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the High Voltage Shutdown Limit (Volt*10)" + ::= { systemParameters 13 } + +autoBoostFlag OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Auto Boost Flag(1 for auto boost enabled, 0 disabled)." + ::= { systemParameters 14 } + +boostTime OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Boost Time value (hour*10)" + ::= { systemParameters 15 } + +boostInterval OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Boost Interval value (weeks*10)" + ::= { systemParameters 16 } + +boostTimeFactor OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Boost Time Factor" + ::= { systemParameters 17 } + +boostLimit1 OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Boost Limit1 value (Volt*10)" + ::= { systemParameters 18 } + +boostLimit2 OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Boost Limit2 value (Volt*10)" + ::= { systemParameters 19 } + + +noOfBatteries OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Number of Batteries" + ::= { systemParameters 20 } + +batType OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Type Of Battery" + ::= { systemParameters 21 } + +symLimit OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Symmetry Limit (Volt*10)" + ::= { systemParameters 22 } + +autoTestFlag OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the number of automatic battery tests performed per year." + ::= { systemParameters 23 } + +startHForTest OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Start Hour for Automatic Test" + ::= { systemParameters 24 } + +voltLimForTest OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Voltage Limit for Battery Test (Volt*10)" + ::= { systemParameters 25 } + +timeLimForTest OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Time Limit For Test (minutes)" + ::= { systemParameters 26 } + +ampLimForTest OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Amphour Limit For Test (Ah)" + ::= { systemParameters 27 } + +year OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Year" + ::= { systemParameters 28 } + +month OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Month" + ::= { systemParameters 29 } + +day OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Day" + ::= { systemParameters 30 } + +hour OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Hour" + ::= { systemParameters 31 } + +minute OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Minute" + ::= { systemParameters 32 } + +tempCompFlag OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Temperature Compensation Flag" + ::= { systemParameters 33 } + +tempCompFactor OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Temperature Compensation Factor (Volt*10/10C)" + ::= { systemParameters 34 } + +highTempLimit OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the High Temperature Limit (C)" + ::= { systemParameters 35 } + +lowTempLimit OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Low Temperature Limit (C)" + ::= { systemParameters 36 } + +highLoadLimit OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the High Load Limit (% of full capacity)" + ::= { systemParameters 37 } + +signals OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS write-only + STATUS mandatory + DESCRIPTION + "A variable containing the Signal byte + Bit 0 - Reset Alarms + Bit 1 - Reconfigure System" + ::= { systemParameters 38 } + +shuntA OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Shunt Current Rating (A)" + ::= { systemParameters 39 } + +shuntmV OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Shunt mV Rating (mV)" + ::= { systemParameters 40 } + +batCurrLimEnabled OBJECT-TYPE + SYNTAX INTEGER(0..255) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "Battery Current Limit Enabled + " + ::= { systemParameters 41 } + +currLimRef OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the battery Current Limit reference (Amp*10)" + ::= { systemParameters 42 } + +highACVoltLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the High AC Voltage Limit (Volt)" + ::= { systemParameters 43 } + +lowACVoltLim OBJECT-TYPE + SYNTAX INTEGER(0..30000) + ACCESS read-write + STATUS mandatory + DESCRIPTION + "A variable containing the Low AC Voltage Limit (Volt)" + ::= { systemParameters 44 } + + +-- Traps + +lowVoltTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Low DC Voltage Alarm " + ::= 1 + + +highVoltTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "High DC Voltage Alarm" + ::= 2 + +loadBattDisconTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Load/Battery Disconnected" + ::= 3 +mainsFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Mains Failure Alarm" + ::= 4 +distrFuseTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Distribution Fuse Failure" + ::= 5 +lowACTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Low AC input voltage" + ::= 6 +battFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Battery Test Failure" + ::= 7 +rectifierFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Module Alarm" + ::= 8 + +battTempTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "High Battery Temperature Alarm" + ::= 9 +symmFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Battery Symmetry Alarm" + ::= 10 +battFuseTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Battery Fuse Failure" + ::= 11 +highLoadTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "High Load" + ::= 12 +highACTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "High AC input voltage" + ::= 13 +urgentModFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Urgent Module Alarm" + ::= 14 +comFailTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Communication Failure" + ::= 15 +partLoadDiscon1Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Partial Load Disconnect 1 Alarm" + ::= 16 +tempProbeTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Temperature probe fault" + ::= 17 +dischargingTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Battery on Discharge" + ::= 18 +ovShutdownTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Overvoltage Shutdown alarm" + ::= 19 +lowBattTempTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Low Battery Temperature" + ::= 20 +partLoadDiscon2Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Partial Load Disconnect 2 Alarm" + ::= 21 +partLoadDiscon3Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Partial Load Disconnect 3 Alarm" + ::= 22 +alarmsBlockedTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Alarms Blocked" + ::= 23 +extAlarm0Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 0" + ::= 24 +extAlarm1Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 1" + ::= 25 +extAlarm2Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 2" + ::= 26 +extAlarm3Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 3" + ::= 27 +extAlarm4Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 4" + ::= 28 +extAlarm5Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 5" + ::= 29 +extAlarm6Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 6" + ::= 30 +extAlarm7Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 7" + ::= 31 +extAlarm8Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 8" + ::= 32 +extAlarm9Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 9" + ::= 33 +extAlarm10Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 10" + ::= 34 +extAlarm11Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 11" + ::= 35 +extAlarm12Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 12" + ::= 36 +extAlarm13Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 13" + ::= 37 +extAlarm14Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 14" + ::= 38 +extAlarm15Trap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "Ext.alarm 15" + ::= 39 +u1NormalTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "System output reference changed to Normal" + ::= 40 +u2BoostTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "System output reference changed to Boost" + ::= 41 +u3TestTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "System output reference changed to Test" + ::= 42 +u4SpareTrap TRAP-TYPE + ENTERPRISE powecTrap + VARIABLES { + numbOfAlarms + } + DESCRIPTION + "System output reference changed to Spare" + ::= 43 + +END diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/PowerNet-MIB.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/PowerNet-MIB.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/PowerNet-MIB.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/PowerNet-MIB.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC-1212.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/RFC-1212.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC-1212.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/RFC-1212.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC-1215.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/RFC-1215.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC-1215.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/RFC-1215.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1155-SMI.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/RFC1155-SMI.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1155-SMI.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/RFC1155-SMI.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1158-MIB.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/RFC1158-MIB.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1158-MIB.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/RFC1158-MIB.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1213-MIB.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/RFC1213-MIB.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/RFC1213-MIB.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/RFC1213-MIB.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-CONF.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-CONF.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-CONF.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-CONF.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-MIB.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-MIB.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-MIB.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-MIB.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-SMI.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-SMI.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-SMI.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-SMI.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-TC.mib b/tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-TC.mib similarity index 100% rename from tangostationcontrol/tangostationcontrol/devices/psoc_mib/SNMPv2-TC.mib rename to tangostationcontrol/tangostationcontrol/devices/mibs/SNMPv2-TC.mib diff --git a/tangostationcontrol/tangostationcontrol/devices/mibs/__init__.py b/tangostationcontrol/tangostationcontrol/devices/mibs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py index bc30872734ba2006c1d7245281cfb04dfd6fb0ba..555000fca0ca13b2ca49efed0784a45be2f5287c 100644 --- a/tangostationcontrol/tangostationcontrol/devices/observation.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation.py @@ -91,25 +91,26 @@ class Observation(lofar_device): # Set a reference of AntennaField device that is correlated to this device util = Util.instance() - instance_number = self.get_name().split('/')[2] + #TODO(Stefano): set a proper policy for the devices instance number + # It cannot be inherited from the Observation instance number (i.e. Observation_id) self.antennafield_proxy = DeviceProxy( - f"{util.get_ds_inst_name()}/AntennaField/{instance_number}") + f"{util.get_ds_inst_name()}/AntennaField/1") self.antennafield_proxy.set_source(DevSource.DEV) # Set a reference of RECV device that is correlated to this device - self.recv_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/RECV/{instance_number}") + self.recv_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/RECV/1") self.recv_proxy.set_source(DevSource.DEV) # Set a reference of Beamlet device that is correlated to this device - self.beamlet_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Beamlet/{instance_number}") + self.beamlet_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Beamlet/1") self.beamlet_proxy.set_source(DevSource.DEV) # Set a reference of DigitalBeam device that is correlated to this device - self.digitalbeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/DigitalBeam/{instance_number}") + self.digitalbeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/DigitalBeam/1") self.digitalbeam_proxy.set_source(DevSource.DEV) # Set a reference of TileBeam device that is correlated to this device - self.tilebeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Tilebeam/{instance_number}") + self.tilebeam_proxy = DeviceProxy(f"{util.get_ds_inst_name()}/Tilebeam/1") self.tilebeam_proxy.set_source(DevSource.DEV) logger.info( diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py index f29c2032ca0df6e0275d1e594f7ff7ef4ecee9ca..2b7b6690b33fe7e6eaa5df4937cbcd4a3e2b37c6 100644 --- a/tangostationcontrol/tangostationcontrol/devices/observation_control.py +++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py @@ -5,17 +5,18 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -from datetime import datetime from json import loads +import jsonschema +from jsonschema import Draft7Validator, FormatChecker import logging import time +from datetime import datetime import numpy from tango import Except, DevFailed, DevState, AttrWriteType, DebugIt, DeviceProxy, Util, DevBoolean, DevString from tango.server import Device, command, attribute from tango import EventType -from tangostationcontrol import __version__ as version from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions from tangostationcontrol.devices.device_decorators import only_when_on, fault_on_error @@ -70,8 +71,45 @@ class ObservationControl(lofar_device): - array[int] running_observations - string version """ + # JSON Schema + OBSERVATION_SETTINGS_SCHEMA = { + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "required": [], + "properties": { + "observation_id": {"type": "number", "minimum": 1}, + "stop_time": {"type": "string", "format": "date-time"}, + "antenna_mask": {"type": "array"}, + "filter": {"type": "string"}, + "SAPs": {"type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "subbands": {"type": "array"}, + "pointing": {"type": "object", + "properties": { + "angle1" : {"type": "number"}, + "angle2" : {"type": "number"}, + "direction_type": {"type": "string"} + } + } + } + } + }, + "tile_beam": {"type": "object", + "properties": { + "angle1" : {"type": "number"}, + "angle2" : {"type": "number"}, + "direction_type": {"type": "string"} + } + }, + "first_beamlet": {"type": "number", "minimum": 0} + }, + } + VALIDATOR = Draft7Validator(OBSERVATION_SETTINGS_SCHEMA, format_checker=FormatChecker()) + # Attributes - version_R = attribute(dtype = str, access = AttrWriteType.READ, fget = lambda self: version) running_observations_R = attribute(dtype = (numpy.int64, ), access = AttrWriteType.READ) # Core functions @@ -118,7 +156,6 @@ class ObservationControl(lofar_device): @log_exceptions() def read_running_observations_R(self): obs = [ key for key in self.running_observations ] - logger.debug("{}".format(obs)) return obs @log_exceptions() @@ -132,7 +169,7 @@ class ObservationControl(lofar_device): """ if event.err: # Something is fishy with this event. - logger.warning("The Observation device {} sent an event but the event signals an error. It is advised to check the logs for any indication that something went wrong in that device. Event data={} ".format(event.device, event)) + logger.warning(f"The Observation device {event.device} sent an event but the event signals an error. It is advised to check the logs for any indication that something went wrong in that device. Event data={event}") return # Get the Observation ID from the sending device. @@ -142,10 +179,10 @@ class ObservationControl(lofar_device): running_obs = self.running_observations.copy() if not running_obs: # No obs is running??? - logger.warning("Received an observation_running event for the observation with ID={}. According to the records in ObservationControl, this observation is not supposed to run. Please check previous logs, especially around the time an observation with this ID was started. Will continue and ignore this event.".format(obs_id)) + logger.warning(f"Received an observation_running event for the observation with ID={obs_id}. According to the records in ObservationControl, this observation is not supposed to run. Please check previous logs, especially around the time an observation with this ID was started. Will continue and ignore this event.") return - if id in running_obs: + if obs_id in running_obs: # Get the Observation's stop_time from the Observation device. obs_stop_time = event.device.stop_time_R current_obs_time = event.attr_value.value @@ -157,8 +194,12 @@ class ObservationControl(lofar_device): # The observation has not finished yet and is # more than 1.0 seconds past its scheduled stop # time. Tell the observation to finish and clean up. - obs = running_obs[id] + obs = running_obs[obs_id] self.stop_observation(obs_id) + else: + # The observation that we are trying to process is not part of the running_obs dictionary + logger.warning(f"Received an observation_running event for the observation with ID={obs_id}. According to the records in ObservationControl, this observation is not supposed to run. Please check previous logs, especially around the time an observation with this ID was started. Will continue and ignore this event.") + return @only_when_on() @log_exceptions() @@ -173,57 +214,22 @@ class ObservationControl(lofar_device): # Convert the input parameter to a dict. parameter_dict = loads(parameters) - logger.debug("incoming parameter_array = {}, parameter_dict = {}".format(parameters, parameter_dict)) + logger.debug("incoming parameter_array = %s, parameter_dict = %s", parameters, parameter_dict) # Parameter check, do not execute an observation in case # the parameters are not sufficiently defined. obs_id = int(parameter_dict["observation_id"]) stop_datetime = datetime.fromisoformat(parameter_dict["stop_time"]) - # TODO(Jan David): Once ticket https://support.astron.nl/jira/browse/L2SS-254 - # is done, this needs to be replaced by a proper JSON - # verification against a schema. - if obs_id is None or obs_id < 1: - # Do not execute - error = "Cannot start an observation with ID={} because the observation ID is invalid. The ID must be any integer >= 1.".format(obs_id) - Except.throw_exception("IllegalCommand", error, __name__) - elif stop_datetime is None or stop_datetime <= datetime.now(): - error = "Cannot start an observation with ID={} because the parameter stop_time parameter value=\"{}\" is invalid. It needs to be expressed in ISO 8601 format.".format(obs_id, stop_datetime) + try: + self.VALIDATOR.validate(parameter_dict) + except jsonschema.exceptions.ValidationError as error: Except.throw_exception("IllegalCommand", error, __name__) - elif len(parameters) == 0: - error = "Cannot start an observation with ID={} because the parameter set is empty.".format(obs_id) + # Check further properties that cannot be validated through a JSON schema + if stop_datetime <= datetime.now(): + error = f"Cannot start an observation with ID={obs_id} because the parameter stop_time parameter value=\"{stop_datetime}\" is invalid. Set a stop_time parameter later in time than the start time." Except.throw_exception("IllegalCommand", error, __name__) return parameter_dict - def delete_dynamic_device(self, class_name: str = None, device_name: str = None): - """ - Remove a Tango device from the Tango DB. This calls delete_device(). - """ - if class_name is not None and device_name is not None: - try: - # Remove the device from the Tango DB. - self.tango_util.delete_device(class_name, device_name) - except DevFailed as ex: - # It is OK if this fails. This likely means that the device did - # never exist in the Tango DB. Still add a warning to the logs. - logger.warning("Something went wrong when it was attempted to remove the device {} from the Tango DB. You should better go and check the logs. Exception: {}".format(device_name, ex)) - pass - else: - logger.error("Cannot delete a device from the Tango DB if the device's class name or the device name are not provided: class_name={}, device_name={}".format(class_name, device_name)) - - def create_dynamic_device(self, class_name: str = None, device_name: str = None): - """ - Create a Tango device instance for a Device class in the Tango DB. - This will automatically instantiate the device and also call - init_device. - """ - try: - self.tango_util.create_device(class_name, device_name) - except DevFailed as ex: - self.delete_dynamic_device(class_name, device_name) - error_string = "Cannot start the device {} for the device class {}. Exception: {}".format(device_name, class_name, ex) - logger.exception(error_string) - Except.re_throw_exception(ex, "DevFailed", error_string, __name__) - # API @command(dtype_in = DevString) @only_when_on() @@ -245,11 +251,17 @@ class ObservationControl(lofar_device): try: # Create the Observation device and instantiate it. - self.create_dynamic_device(class_name, device_name) + self.tango_util.create_device(class_name, device_name) except DevFailed as ex: - error_string = "Cannot create the Observation device instance {} for ID={}. This means that the observation did not start.".format(device_name, observation_id) - logger.exception(error_string) - Except.re_throw_exception(ex, "DevFailed", error_string, __name__) + if ex.args[0].desc == f"The device {device_name.lower()} is already defined in the database" and self.is_observation_running(observation_id) is False : + self.tango_util.delete_device(class_name, device_name) + error_string = f"Cannot create the Observation device {device_name} because it is already present in the Database but it is not running. Try to re-run the start_observation command" + logger.exception(error_string) + Except.re_throw_exception(ex, "DevFailed", error_string, __name__) + else: + error_string = f"Cannot create the Observation device instance {device_name} for ID={observation_id}. This means that the observation did not start." + logger.exception(error_string) + Except.re_throw_exception(ex, "DevFailed", error_string, __name__) try: # Instantiate a dynamic Tango Device "Observation". @@ -270,8 +282,8 @@ class ObservationControl(lofar_device): device_proxy.On() except DevFailed as ex: # Remove the device again. - self.delete_dynamic_device(class_name, device_name) - error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}. This means that the observation likely did not start but certainly cannot be controlled and/or forcefully be stopped.".format(observation_id, class_name, device_name) + self.tango_util.delete_device(class_name, device_name) + error_string = f"Cannot access the Observation device instance for observation ID={observation_id} with device class name={class_name} and device instance name={device_name}. This means that the observation likely did not start but certainly cannot be controlled and/or forcefully be stopped." logger.exception(error_string) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) @@ -280,7 +292,7 @@ class ObservationControl(lofar_device): # # Generate the name for the Observation.observation_running # MP. - attribute_name = "{}/observation_running_R".format(device_name) + attribute_name = f"{device_name}/observation_running_R" observation["attribute_name"] = attribute_name # Turn on the polling for the attribute. @@ -304,9 +316,9 @@ class ObservationControl(lofar_device): self.running_observations[observation_id] = observation logger.info(f"Successfully started an observation with ID={observation_id}.") except DevFailed as ex: - self.delete_dynamic_device(class_name, device_name) - error_string = "Cannot access the Observation device instance for observation ID={} with device class name={} and device instance name={}. This means that the observation cannot be controlled and/or forcefully be stopped.".format(observation_id, Observation.__name__, device_name) - logger.exception(error_string) + self.tango_util.delete_device(class_name, device_name) + error_string = "Cannot access the Observation device instance for observation ID=%s with device class name=%s and device instance name=%s. This means that the observation cannot be controlled and/or forcefully be stopped." + logger.exception(error_string, observation_id, Observation.__name__, device_name) Except.re_throw_exception(ex, "DevFailed", error_string, __name__) @command(dtype_in = numpy.int64) @@ -317,13 +329,13 @@ class ObservationControl(lofar_device): # the parameters are not sufficient. if obs_id < 1: # Do not execute - error = "Cannot stop an observation with ID={}, because the observation ID is invalid.".format(obs_id) + error = f"Cannot stop an observation with ID={obs_id}, because the observation ID is invalid." Except.throw_exception("IllegalCommand", error, __name__) elif self.is_observation_running(obs_id) is False: - error = "Cannot stop an observation with ID={}, because the observation is not running.".format(obs_id) + error = f"Cannot stop an observation with ID={obs_id}, because the observation is not running." Except.throw_exception("IllegalCommand", error, __name__) - logger.info("Stopping the observation with ID={}.".format(obs_id)) + logger.info(f"Stopping the observation with ID={obs_id}.") # Fetch the obs data and remove it from the dict of # currently running observations. observation = self.running_observations.pop(obs_id) @@ -333,7 +345,7 @@ class ObservationControl(lofar_device): try: device_proxy.ping() except DevFailed: - logger.warning("The device for the Observation with ID={} has unexpectedly already disappeared. It is advised to check the logs up to 10s prior to this message to see what happened.".format(obs_id)) + logger.warning(f"The device for the Observation with ID={obs_id} has unexpectedly already disappeared. It is advised to check the logs up to 10s prior to this message to see what happened.") else: # Unsubscribe from the subscribed event. event_id = observation.pop("event_id") @@ -358,15 +370,15 @@ class ObservationControl(lofar_device): remaining_wait_time = remaining_wait_time - sleep_time # Check if the observation object is really in OFF state. if stopped: - logger.info("Successfully stopped the observation with ID={}.".format(obs_id)) + logger.info(f"Successfully stopped the observation with ID={obs_id}") else: - logger.warning("Could not shut down the Observation device (\"{}\") for observation ID={}. This means that there is a chance for a memory leak. Will continue anyway and forcefully delete the Observation object.".format(observation["device_name"], obs_id)) + logger.warning(f"Could not shut down the Observation device ( {observation['device_name']} ) for observation ID={obs_id}. This means that there is a chance for a memory leak. Will continue anyway and forcefully delete the Observation object.") # Finally remove the device object from the Tango DB. try: - self.delete_dynamic_device(observation["class_name"], observation["device_name"]) + self.tango_util.delete_device(observation["class_name"], observation["device_name"]) except DevFailed: - logger.warning("Something went wrong when the device {} was removed from the Tango DB. There is nothing that can be done about this here at this moment but you should check the Tango DB yourself.".format(observation["device_name"])) + logger.warning(f"Something went wrong when the device {observation['device_name']} was removed from the Tango DB. There is nothing that can be done about this here at this moment but you should check the Tango DB yourself.") @command() @only_when_on() @@ -388,16 +400,10 @@ class ObservationControl(lofar_device): # Parameter check, do not execute if obs_id is invalid if obs_id < 1: # Do not execute - error = "Cannot check if an observation with ID={} is running, because the observation ID is invalid".format(obs_id) + error = f"Cannot check if an observation with ID={obs_id} is running, because the observation ID is invalid" Except.throw_exception("IllegalCommand", error, __name__) - observation = self.running_observations.get(obs_id) - info = "An observation with ID={} is".format(obs_id) - if observation is not None: - logger.debug("{} running.".format(info)) - return True - logger.debug("{} not running.".format(info)) - return False + return observation is not None @command(dtype_out = DevBoolean) @only_when_on() diff --git a/tangostationcontrol/tangostationcontrol/devices/opcua_device.py b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py index eb2e508c35cf6923fb9d121f729465a4eca621e3..c5fa4d1a791bc2511ae93f9e30091c0c1beba2c8 100644 --- a/tangostationcontrol/tangostationcontrol/devices/opcua_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py @@ -105,10 +105,10 @@ class opcua_device(lofar_device): for i in self.attr_list(): try: if not i.comms_id or i.comms_id == OPCUAConnection: - await i.async_set_comm_client(self.opcua_connection) + await i.async_set_comm_client(self, self.opcua_connection) except Exception as e: # use the pass function instead of setting read/write fails - i.set_pass_func() + i.set_pass_func(self) self.opcua_missing_attributes.append(",".join(self.opcua_connection.get_node_path(i.comms_annotation))) logger.warning(f"Error while setting the attribute {i.comms_annotation} read/write function.", exc_info=True) diff --git a/tangostationcontrol/tangostationcontrol/devices/pcon.py b/tangostationcontrol/tangostationcontrol/devices/pcon.py new file mode 100644 index 0000000000000000000000000000000000000000..02bf7d9d70f33a5ac6a52ca4fa966fa9b20cde42 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/pcon.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" PCON Device Server for LOFAR2.0 + +""" + +# Additional import +from tangostationcontrol.common.entrypoint import entry +from tangostationcontrol.common.lofar_logging import device_logging_to_python + +import logging +from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper +from tangostationcontrol.devices.snmp_device import snmp_device + +import numpy + +from pysmi import debug + +debug.setLogger(debug.Debug('searcher', "compiler", "borrower", "reader")) + +logger = logging.getLogger() + +__all__ = ["PCON", "main"] + + +@device_logging_to_python() +class PCON(snmp_device): + + # ---------- + # Attributes + # ---------- + systemVoltage_R = attribute_wrapper(comms_annotation={"mib": "ACC-MIB", "name": "systemVoltage", "scaling_factor": 0.01}, datatype=numpy.double) + + rectifierCurrent_R = attribute_wrapper(comms_annotation={"mib": "ACC-MIB", "name": "rectifierCurrent", "scaling_factor": 0.1}, datatype=numpy.double) + loadCurrent_R = attribute_wrapper(comms_annotation={"mib": "ACC-MIB", "name": "loadCurrent", "scaling_factor": 0.1}, datatype=numpy.double) + batteryCurrent_R = attribute_wrapper(comms_annotation={"mib": "ACC-MIB", "name": "batteryCurrent", "scaling_factor": 0.1}, datatype=numpy.double) + + battTemperature_R = attribute_wrapper(comms_annotation={"mib": "ACC-MIB", "name": "battTemperature"}, datatype=numpy.double) + + +# ---------- +# Run server +# ---------- +def main(**kwargs): + """Main function of the PCON module.""" + return entry(PCON, **kwargs) diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc.py b/tangostationcontrol/tangostationcontrol/devices/psoc.py index e599563fd4f1ad3120d86da937aac31f27dcc53e..0c83360c78b570f27da96fcb3e8316f0cdd72334 100644 --- a/tangostationcontrol/tangostationcontrol/devices/psoc.py +++ b/tangostationcontrol/tangostationcontrol/devices/psoc.py @@ -9,18 +9,16 @@ # Additional import from tangostationcontrol.common.entrypoint import entry -from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions from tango.server import device_property, command -import os import logging from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper -from tangostationcontrol.clients.snmp_client import SNMP_client, mib_loader, snmp_attribute +from tangostationcontrol.clients.snmp_client import snmp_attribute +from tangostationcontrol.devices.snmp_device import snmp_device import numpy -import pkg_resources from datetime import timedelta from pysmi import debug @@ -33,35 +31,10 @@ __all__ = ["PSOC", "main"] @device_logging_to_python() -class PSOC(lofar_device): +class PSOC(snmp_device): # ----------------- # Device Properties # ----------------- - SNMP_community = device_property( - dtype='DevString', - mandatory=True - ) - - SNMP_host = device_property( - dtype='DevString', - mandatory=True - ) - - SNMP_mib_dir = device_property( - dtype='DevString', - mandatory=True - ) - - SNMP_timeout = device_property( - dtype='DevDouble', - mandatory=True - ) - - SNMP_version = device_property( - dtype='DevULong', - mandatory=True - ) - PSOC_sockets = device_property( dtype=[str], mandatory=True @@ -70,9 +43,9 @@ class PSOC(lofar_device): # ---------- # Attributes # ---------- - sockets_state_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "sPSOCOutletCtl", "index": 1}, dims=(8,), datatype=str) - master_state_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "sPSOCMasterState"}, datatype=str) - current_load_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "rPSOCLoadStatusLoad", "index": 1}, datatype=numpy.int64) + sockets_state_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "sPDUOutletCtl", "index": 1}, dims=(8,), datatype=str) + master_state_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "sPDUMasterState"}, datatype=str) + current_load_R = attribute_wrapper(comms_annotation={"mib": "PowerNet-MIB", "name": "rPDULoadStatusLoad", "index": 1}, datatype=numpy.int64) uptime_R = attribute_wrapper(comms_annotation={"mib": "SNMPv2-MIB", "name": "sysUpTime"}, datatype=numpy.int64) # -------- @@ -94,26 +67,11 @@ class PSOC(lofar_device): self.socket_dict = {self.PSOC_sockets[f]: f + 1 for f in range(len(self.PSOC_sockets))} logger.debug(f"Configured PSOC with the following socket names: {self.PSOC_sockets}") - # set up the SNMP ua client - self.snmp_manager = SNMP_client(self.SNMP_community, self.SNMP_host, self.SNMP_timeout, self.SNMP_version, self.Fault, self) - - # map an access helper class - for i in self.attr_list(): - try: - i.set_comm_client(self.snmp_manager) - except Exception as e: - # use the pass function instead of setting read/write fails - i.set_pass_func() - logger.warning("error while setting the SNMP attribute {} read/write function. {}".format(i, e)) - - self.snmp_manager.start() - + super().configure_for_initialise() # prepares this object for the readable_uptime command self.uptime_attr = snmp_attribute(self.snmp_manager.SNMP_comm, "SNMPv2-MIB", name="sysUpTime", idx=0, dtype=numpy.int64, dim_x=1, dim_y=0) - super().configure_for_initialise() - def _toggle_socket(self, socket_name, on: bool): """ This function is tailored to the "APS switched rack PSOC", changing the psoc will require some changes to this function @@ -131,7 +89,7 @@ class PSOC(lofar_device): socket_set = "outletOff" # create the snmp_attribute for the correct socket - attr = snmp_attribute(self.snmp_manager.SNMP_comm, "PowerNet-MIB", name="sPSOCOutletCtl", idx=socket_nr, dtype=str, dim_x=1, dim_y=0) + attr = snmp_attribute(self.snmp_manager.SNMP_comm, "PowerNet-MIB", name="sPDUOutletCtl", idx=socket_nr, dtype=str, dim_x=1, dim_y=0) # write the correct value attr.write_function([socket_set]) @@ -155,36 +113,6 @@ class PSOC(lofar_device): # for whatever reason, the uptime is given in hundredts of a second return str(timedelta(seconds=self.uptime_attr.read_function()/100)) - - @log_exceptions() - def configure_for_on(self): - super().configure_for_on() - - @log_exceptions() - def configure_for_off(self): - super().configure_for_off() - - def get_mib_dir(self): - mib_filename_path = pkg_resources.resource_filename('tangostationcontrol', self.SNMP_mib_dir) - mib_path = os.path.dirname(mib_filename_path) - - return mib_path - - def init_device(self): - super().init_device() - - # create the mib_loader and set the mib path - self.loader = mib_loader(self.get_mib_dir()) - - for i in self.attr_list(): - try: - # for all of the attributes attempt to load the pre-compiled MIB. Skips already loaded ones - self.loader.load_pymib(i.comms_annotation["mib"]) - except Exception as e: - raise Exception( - f"Failed to load MIB file: {i.comms_annotation.get('mib')} for attribute {i.name} in directory {self.get_mib_dir()} ") from e - - # ---------- # Run server # ---------- diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index 96da333820f69238ad34e2159752fac2cf0263e4..037b8adb69fa2515f5a3e2dbaa4ed00c8a2ab43b 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -22,10 +22,10 @@ import numpy # Additional import from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import device_logging_to_python +from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.devices.device_decorators import only_in_states from tangostationcontrol.devices.opcua_device import opcua_device -from tangostationcontrol.devices.lofar_device import lofar_device import logging logger = logging.getLogger() @@ -54,7 +54,7 @@ class RECV(opcua_device): ANT_mask_RW_default = device_property( dtype='DevVarBooleanArray', mandatory=False, - default_value=[[True] * 3] * 32 + default_value=[True] * 96 ) RCU_mask_RW_default = device_property( @@ -66,13 +66,13 @@ class RECV(opcua_device): RCU_band_select_RW_default = device_property( dtype='DevVarLong64Array', mandatory=False, - default_value=[[0] * 3] * 32 + default_value=[0] * 96 ) RCU_PWR_ANT_on_RW = device_property( dtype='DevVarLong64Array', mandatory=False, - default_value=[[False] * 3] * 32 # 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 ) RECVTR_monitor_rate_RW_default = device_property( @@ -128,26 +128,26 @@ class RECV(opcua_device): # ---------- # Attributes # ---------- - ANT_mask_RW = attribute_wrapper(comms_annotation=["ANT_mask_RW" ],datatype=bool , dims=(3,32), access=AttrWriteType.READ_WRITE) + ANT_mask_RW = attribute_wrapper(comms_annotation=["ANT_mask_RW" ],datatype=bool , dims=(96,), access=AttrWriteType.READ_WRITE) # The HBAT beamformer delays represent 32 delays for each of the 96 inputs. # The 32 delays deconstruct as delays[polarisation][dipole], and each delay is the number of 'delay steps' to apply (0.5ns for HBAT1). - HBAT_BF_delay_steps_R = attribute_wrapper(comms_annotation=["HBAT_BF_delay_steps_R" ],datatype=numpy.int64 , dims=(32,96)) - HBAT_BF_delay_steps_RW = attribute_wrapper(comms_annotation=["HBAT_BF_delay_steps_RW" ],datatype=numpy.int64 , dims=(32,96), access=AttrWriteType.READ_WRITE) - HBAT_LED_on_R = attribute_wrapper(comms_annotation=["HBAT_LED_on_R" ],datatype=bool , dims=(32,96)) - HBAT_LED_on_RW = attribute_wrapper(comms_annotation=["HBAT_LED_on_RW" ],datatype=bool , dims=(32,96), access=AttrWriteType.READ_WRITE) - HBAT_PWR_LNA_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_R" ],datatype=bool , dims=(32,96)) - HBAT_PWR_LNA_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_RW" ],datatype=bool , dims=(32,96), access=AttrWriteType.READ_WRITE) - HBAT_PWR_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_on_R" ],datatype=bool , dims=(32,96)) - HBAT_PWR_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_on_RW" ],datatype=bool , dims=(32,96), access=AttrWriteType.READ_WRITE) - RCU_ADC_locked_R = attribute_wrapper(comms_annotation=["RCU_ADC_locked_R" ],datatype=bool , dims=(3,32)) - RCU_attenuator_dB_R = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_R" ],datatype=numpy.int64 , dims=(3,32)) - RCU_attenuator_dB_RW = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) - RCU_band_select_R = attribute_wrapper(comms_annotation=["RCU_band_select_R" ],datatype=numpy.int64 , dims=(3,32)) - RCU_band_select_RW = attribute_wrapper(comms_annotation=["RCU_band_select_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) - RCU_DTH_freq_R = attribute_wrapper(comms_annotation=["RCU_DTH_freq_R" ],datatype=numpy.int64 , dims=(3,32)) - RCU_DTH_freq_RW = attribute_wrapper(comms_annotation=["RCU_DTH_freq_RW" ],datatype=numpy.int64 , dims=(3,32), access=AttrWriteType.READ_WRITE) - RCU_DTH_on_R = attribute_wrapper(comms_annotation=["RCU_DTH_on_R" ],datatype=bool , dims=(3,32)) + HBAT_BF_delay_steps_R = attribute_wrapper(comms_annotation=["HBAT_BF_delay_steps_R" ],datatype=numpy.int64 , dims=(96,2,16)) + HBAT_BF_delay_steps_RW = attribute_wrapper(comms_annotation=["HBAT_BF_delay_steps_RW" ],datatype=numpy.int64 , dims=(96,2,16), access=AttrWriteType.READ_WRITE) + HBAT_LED_on_R = attribute_wrapper(comms_annotation=["HBAT_LED_on_R" ],datatype=bool , dims=(96,2,16)) + HBAT_LED_on_RW = attribute_wrapper(comms_annotation=["HBAT_LED_on_RW" ],datatype=bool , dims=(96,2,16), access=AttrWriteType.READ_WRITE) + HBAT_PWR_LNA_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_R" ],datatype=bool , dims=(96,2,16)) + HBAT_PWR_LNA_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_LNA_on_RW" ],datatype=bool , dims=(96,2,16), access=AttrWriteType.READ_WRITE) + HBAT_PWR_on_R = attribute_wrapper(comms_annotation=["HBAT_PWR_on_R" ],datatype=bool , dims=(96,2,16)) + HBAT_PWR_on_RW = attribute_wrapper(comms_annotation=["HBAT_PWR_on_RW" ],datatype=bool , dims=(96,2,16), access=AttrWriteType.READ_WRITE) + RCU_ADC_locked_R = attribute_wrapper(comms_annotation=["RCU_ADC_locked_R" ],datatype=bool , dims=(96,)) + RCU_attenuator_dB_R = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_R" ],datatype=numpy.int64 , dims=(96,)) + RCU_attenuator_dB_RW = attribute_wrapper(comms_annotation=["RCU_attenuator_dB_RW" ],datatype=numpy.int64 , dims=(96,), access=AttrWriteType.READ_WRITE) + RCU_band_select_R = attribute_wrapper(comms_annotation=["RCU_band_select_R" ],datatype=numpy.int64 , dims=(96,)) + RCU_band_select_RW = attribute_wrapper(comms_annotation=["RCU_band_select_RW" ],datatype=numpy.int64 , dims=(96,), access=AttrWriteType.READ_WRITE) + RCU_DTH_freq_R = attribute_wrapper(comms_annotation=["RCU_DTH_freq_R" ],datatype=numpy.int64 , dims=(96,)) + RCU_DTH_freq_RW = attribute_wrapper(comms_annotation=["RCU_DTH_freq_RW" ],datatype=numpy.int64 , dims=(96,), access=AttrWriteType.READ_WRITE) + RCU_DTH_on_R = attribute_wrapper(comms_annotation=["RCU_DTH_on_R" ],datatype=bool , dims=(96,)) RCU_LED_green_on_R = attribute_wrapper(comms_annotation=["RCU_LED_green_on_R" ],datatype=bool , dims=(32,)) RCU_LED_green_on_RW = attribute_wrapper(comms_annotation=["RCU_LED_green_on_RW" ],datatype=bool , dims=(32,), access=AttrWriteType.READ_WRITE) RCU_LED_red_on_R = attribute_wrapper(comms_annotation=["RCU_LED_red_on_R" ],datatype=bool , dims=(32,)) @@ -160,11 +160,11 @@ class RECV(opcua_device): RCU_PWR_2V5_R = attribute_wrapper(comms_annotation=["RCU_PWR_2V5_R" ],datatype=numpy.float64, dims=(32,)) RCU_PWR_3V3_R = attribute_wrapper(comms_annotation=["RCU_PWR_3V3_R" ],datatype=numpy.float64, dims=(32,)) RCU_PWR_ANALOG_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANALOG_on_R" ],datatype=bool , dims=(32,)) - RCU_PWR_ANT_IOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_IOUT_R" ],datatype=numpy.float64, dims=(3,32)) - RCU_PWR_ANT_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_R" ],datatype=bool , dims=(3,32)) - RCU_PWR_ANT_on_RW = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_RW" ],datatype=bool , dims=(3,32), access=AttrWriteType.READ_WRITE) - RCU_PWR_ANT_VIN_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VIN_R" ],datatype=numpy.float64, dims=(3,32)) - RCU_PWR_ANT_VOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VOUT_R" ],datatype=numpy.float64, dims=(3,32)) + RCU_PWR_ANT_IOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_IOUT_R" ],datatype=numpy.float64, dims=(96,)) + RCU_PWR_ANT_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_R" ],datatype=bool , dims=(96,)) + RCU_PWR_ANT_on_RW = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_on_RW" ],datatype=bool , dims=(96,), access=AttrWriteType.READ_WRITE) + RCU_PWR_ANT_VIN_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VIN_R" ],datatype=numpy.float64, dims=(96,)) + RCU_PWR_ANT_VOUT_R = attribute_wrapper(comms_annotation=["RCU_PWR_ANT_VOUT_R" ],datatype=numpy.float64, dims=(96,)) RCU_PWR_DIGITAL_on_R = attribute_wrapper(comms_annotation=["RCU_PWR_DIGITAL_on_R" ],datatype=bool , dims=(32,)) RCU_PWR_good_R = attribute_wrapper(comms_annotation=["RCU_PWR_good_R" ],datatype=bool , dims=(32,)) RCU_TEMP_R = attribute_wrapper(comms_annotation=["RCU_TEMP_R" ],datatype=numpy.float64, dims=(32,)) @@ -181,7 +181,7 @@ class RECV(opcua_device): return (2 * self.read_attribute("RCU_LED_green_on_R") + 4 * self.read_attribute("RCU_LED_red_on_R")).astype(numpy.uint32) RCU_error_R = attribute(dtype=(bool,), max_dim_x=32, fisallowed="is_attribute_access_allowed") - ANT_error_R = attribute(dtype=((bool,),), max_dim_x=3, max_dim_y=32, fisallowed="is_attribute_access_allowed") + ANT_error_R = attribute(dtype=(bool,), max_dim_x=96, fisallowed="is_attribute_access_allowed") def read_RCU_error_R(self): return self.read_attribute("RCU_mask_RW") & ( @@ -194,14 +194,14 @@ class RECV(opcua_device): ~self.read_attribute("RCU_ADC_locked_R") ) - RECV_IOUT_error_R = attribute(dtype=(bool,), max_dim_x=32) - RECV_TEMP_error_R = attribute(dtype=(bool,), max_dim_x=32, polling_period=1000) - RECV_VOUT_error_R = attribute(dtype=(bool,), max_dim_x=32) + RECV_IOUT_error_R = attribute(dtype=(bool,), max_dim_x=96, fisallowed="is_attribute_access_allowed") + RECV_TEMP_error_R = attribute(dtype=(bool,), max_dim_x=32, fisallowed="is_attribute_access_allowed", polling_period=1000) + RECV_VOUT_error_R = attribute(dtype=(bool,), max_dim_x=32, fisallowed="is_attribute_access_allowed") def read_RECV_IOUT_error_R(self): - return (self.read_attribute("ANT_mask_RW") & ( + return self.read_attribute("ANT_mask_RW") & ( self.alarm_val("RCU_PWR_ANT_IOUT_R") - )).any(axis=1) + ) def read_RECV_TEMP_error_R(self): # Don't apply the mask here --- we always want to know if things get too hot! @@ -213,7 +213,7 @@ class RECV(opcua_device): return (self.read_attribute("ANT_mask_RW") & ( self.alarm_val("RCU_PWR_ANT_VIN_R") | self.alarm_val("RCU_PWR_ANT_VOUT_R") - )).any(axis=1) | (self.read_attribute("RCU_mask_RW") & ( + )).reshape(32,3).any(axis=1) | (self.read_attribute("RCU_mask_RW") & ( self.alarm_val("RCU_PWR_1V8_R") | self.alarm_val("RCU_PWR_2V5_R") | self.alarm_val("RCU_PWR_3V3_R") @@ -235,6 +235,28 @@ class RECV(opcua_device): # by a fixed amount, the average of all steps. Doing so should result # in positive delays regardless of the pointing direction. self.HBAT_bf_delay_offset = numpy.mean(self.HBAT_bf_delay_step_delays) + + def _initialise_hardware(self): + """ Initialise the RCU hardware. """ + + # Cycle RCUs + self.RCU_off() + self.wait_attribute("RECVTR_translator_busy_R", False, self.RCU_On_Off_timeout) + self.RCU_on() + self.wait_attribute("RECVTR_translator_busy_R", False, self.RCU_On_Off_timeout) + + def _disable_hardware(self): + """ Disable the RECV hardware. """ + + # Save actual mask values + RCU_mask = self.proxy.RCU_mask_RW + # Set the mask to all Trues + self.RCU_mask_RW = [True] * 32 + # Turn off the RCUs + self.RCU_off() + self.wait_attribute("RECVTR_translator_busy_R", False, self.RCU_On_Off_timeout) + # Restore the mask + self.RCU_mask_RW = RCU_mask # -------- # internal functions @@ -281,7 +303,7 @@ class RECV(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def RCU_off(self): """ @@ -291,7 +313,7 @@ class RECV(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def RCU_on(self): """ @@ -301,7 +323,7 @@ class RECV(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def RCU_DTH_off(self): """ @@ -311,7 +333,7 @@ class RECV(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def RCU_DTH_on(self): """ @@ -319,15 +341,6 @@ class RECV(opcua_device): """ self.opcua_connection.call_method(["RCU_DTH_on"]) - def _initialise_hardware(self): - """ Initialise the RCU hardware. """ - - # Cycle RCUs - self.RCU_off() - self.wait_attribute("RECVTR_translator_busy_R", False, self.RCU_On_Off_timeout) - self.RCU_on() - self.wait_attribute("RECVTR_translator_busy_R", False, self.RCU_On_Off_timeout) - # ---------- # Run server # ---------- diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py index 9b72ce655249770b84b99a9b43ce2777f84f785e..3a302239c317b83d1f1598a27b8fe1a5222d3858 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/beamlet.py @@ -37,6 +37,7 @@ class Beamlet(opcua_device): N_BEAMLETS_CTRL = 488 N_BEAMSETS_CTRL = 2 N_POL_BF = 2 + P_SUM = 2 # number of hops that the data of the stream has traveled to reach the BSN aligner on this node # ----------------- # Device Properties @@ -72,7 +73,7 @@ class Beamlet(opcua_device): FPGA_bf_weights_xy_yx_RW_default = device_property( dtype='DevVarULongArray', mandatory=False, - default_value = [[0] * A_PN * N_POL * N_BEAMLETS_CTRL] * N_PN + default_value = [0] * N_PN * A_PN * N_POL * N_BEAMLETS_CTRL ) subband_select_RW_default = device_property( @@ -93,43 +94,43 @@ class Beamlet(opcua_device): # Attributes # ---------- - FPGA_beamlet_output_enable_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_R"], datatype=bool, dims=(16,)) - FPGA_beamlet_output_enable_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_RW"], datatype=bool, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_beamlet_output_hdr_eth_destination_mac_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_R"], datatype=str, dims=(16,)) - FPGA_beamlet_output_hdr_eth_destination_mac_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_RW"], datatype=str, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_beamlet_output_hdr_ip_destination_address_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_R"], datatype=str, dims=(16,)) - FPGA_beamlet_output_hdr_ip_destination_address_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_RW"], datatype=str, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_beamlet_output_hdr_udp_destination_port_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(16,)) - FPGA_beamlet_output_hdr_udp_destination_port_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_beamlet_output_scale_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_R"], datatype=numpy.double, dims=(16,)) - FPGA_beamlet_output_scale_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_RW"], datatype=numpy.double, dims=(16,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_enable_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_R"], datatype=bool, dims=(N_PN,)) + FPGA_beamlet_output_enable_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_enable_RW"], datatype=bool, dims=(N_PN,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_eth_destination_mac_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_R"], datatype=str, dims=(N_PN,)) + FPGA_beamlet_output_hdr_eth_destination_mac_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_eth_destination_mac_RW"], datatype=str, dims=(N_PN,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_ip_destination_address_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_R"], datatype=str, dims=(N_PN,)) + FPGA_beamlet_output_hdr_ip_destination_address_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_ip_destination_address_RW"], datatype=str, dims=(N_PN,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_hdr_udp_destination_port_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_R"], datatype=numpy.uint16, dims=(N_PN,)) + FPGA_beamlet_output_hdr_udp_destination_port_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_hdr_udp_destination_port_RW"], datatype=numpy.uint16, dims=(N_PN,), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_output_scale_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_R"], datatype=numpy.double, dims=(N_PN,)) + FPGA_beamlet_output_scale_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_scale_RW"], datatype=numpy.double, dims=(N_PN,), access=AttrWriteType.READ_WRITE) FPGA_beamlet_output_bsn_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_bsn_R"], datatype=numpy.int64, dims=(N_PN, N_BEAMSETS_CTRL)) - FPGA_beamlet_output_nof_packets_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_nof_packets_R"], datatype=numpy.int32, dims=(N_BEAMSETS_CTRL, N_PN)) - FPGA_beamlet_output_nof_valid_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_nof_valid_R"], datatype=numpy.int32, dims=(N_BEAMSETS_CTRL, N_PN)) + FPGA_beamlet_output_nof_packets_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_nof_packets_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL)) + FPGA_beamlet_output_nof_valid_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_nof_valid_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL)) # boolean[N_PN][N_BEAMSETS_CTRL] - FPGA_beamlet_output_ready_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_ready_R"], datatype=bool, dims=(N_BEAMSETS_CTRL, N_PN)) + FPGA_beamlet_output_ready_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_ready_R"], datatype=bool, dims=(N_PN, N_BEAMSETS_CTRL)) # boolean[N_PN][N_BEAMSETS_CTRL] - FPGA_beamlet_output_xon_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_xon_R"], datatype=bool, dims=(N_BEAMSETS_CTRL, N_PN)) + FPGA_beamlet_output_xon_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_output_xon_R"], datatype=bool, dims=(N_PN, N_BEAMSETS_CTRL)) # uint16[N_PN][A_PN][N_POL][N_BEAMLETS_CTRL] # Select subband per dual-polarisation beamlet. # 0 for antenna polarization X in beamlet polarization X, # 1 for antenna polarization Y in beamlet polarization Y. - FPGA_beamlet_subband_select_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_subband_select_R"], datatype=numpy.uint32, dims=(A_PN * N_POL * N_BEAMLETS_CTRL, N_PN)) - FPGA_beamlet_subband_select_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_subband_select_RW"], datatype=numpy.uint32, dims=(A_PN * N_POL * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) + FPGA_beamlet_subband_select_R = attribute_wrapper(comms_annotation=["FPGA_beamlet_subband_select_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_POL, N_BEAMLETS_CTRL)) + FPGA_beamlet_subband_select_RW = attribute_wrapper(comms_annotation=["FPGA_beamlet_subband_select_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_POL, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) # uint32[N_PN][N_beamset_ctrl] - FPGA_bf_ring_nof_transport_hops_R = attribute_wrapper(comms_annotation=["FPGA_bf_ring_nof_transport_hops_R"], datatype=numpy.uint32, dims=(N_BEAMSETS_CTRL, N_PN)) - FPGA_bf_ring_nof_transport_hops_RW = attribute_wrapper(comms_annotation=["FPGA_bf_ring_nof_transport_hops_RW"], datatype=numpy.uint32, dims=(N_BEAMSETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) + FPGA_bf_ring_nof_transport_hops_R = attribute_wrapper(comms_annotation=["FPGA_bf_ring_nof_transport_hops_R"], datatype=numpy.uint32, dims=(N_PN, N_BEAMSETS_CTRL)) + FPGA_bf_ring_nof_transport_hops_RW = attribute_wrapper(comms_annotation=["FPGA_bf_ring_nof_transport_hops_RW"], datatype=numpy.uint32, dims=(N_PN, N_BEAMSETS_CTRL), access=AttrWriteType.READ_WRITE) # cint16[N_PN][A_PN][N_POL][N_BEAMLETS_CTRL] # Co-polarization BF weights. The N_POL = 2 parameter index is: # 0 for antenna polarization X in beamlet polarization X, # 1 for antenna polarization Y in beamlet polarization Y. - FPGA_bf_weights_xx_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_yy_R"], datatype=numpy.uint32, dims=(A_PN * N_POL * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_xx_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_yy_RW"], datatype=numpy.uint32, dims=(A_PN * N_POL * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) + FPGA_bf_weights_xx_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_yy_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_POL, N_BEAMLETS_CTRL)) + FPGA_bf_weights_xx_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_yy_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_POL, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) # cint16[N_PN][A_PN][N_POL][N_BEAMLETS_CTRL] # Cross-polarization BF weights. The N_POL = 2 parameter index is (note that index pol in range 0:N_POL-1 is the antenna polarization, so index !pol is the beamlet polarization): @@ -140,24 +141,51 @@ class Beamlet(opcua_device): # cint16[N_PN][N_POL_BF][A_PN][N_POL][N_BEAMLETS_CTRL] # Full Jones matrix of BF weights. - FPGA_bf_weights_xx_xy_yx_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_xy_yx_yy_R"], datatype=numpy.uint32, dims=(N_POL_BF * A_PN * N_POL * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_xx_xy_yx_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_xy_yx_yy_RW"], datatype=numpy.uint32, dims=(N_POL_BF * A_PN * N_POL * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) + FPGA_bf_weights_xx_xy_yx_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_xy_yx_yy_R"], datatype=numpy.uint32, dims=(N_PN, N_POL_BF, A_PN, N_POL, N_BEAMLETS_CTRL)) + FPGA_bf_weights_xx_xy_yx_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_xy_yx_yy_RW"], datatype=numpy.uint32, dims=(N_PN, N_POL_BF, A_PN, N_POL, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) # cint16[N_PN][A_PN][N_BEAMLETS_CTRL] # BF weights for separate access to respectively w_xx, w_xy, w_yx, and w_yy. - FPGA_bf_weights_xx_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_R"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_xx_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_RW"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) - FPGA_bf_weights_xy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xy_R"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_xy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xy_RW"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) - FPGA_bf_weights_yx_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yx_R"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_yx_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yx_RW"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) - FPGA_bf_weights_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_R"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN)) - FPGA_bf_weights_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_RW"], datatype=numpy.uint32, dims=(A_PN * N_BEAMLETS_CTRL, N_PN), access=AttrWriteType.READ_WRITE) - + FPGA_bf_weights_xx_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL)) + FPGA_bf_weights_xx_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xx_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN * N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) + FPGA_bf_weights_xy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xy_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL)) + FPGA_bf_weights_xy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_xy_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) + FPGA_bf_weights_yx_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yx_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL)) + FPGA_bf_weights_yx_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yx_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) + FPGA_bf_weights_yy_R = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_R"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL)) + FPGA_bf_weights_yy_RW = attribute_wrapper(comms_annotation=["FPGA_bf_weights_yy_RW"], datatype=numpy.uint32, dims=(N_PN, A_PN, N_BEAMLETS_CTRL), access=AttrWriteType.READ_WRITE) + + # boolean[N_pn][N_beamsets_ctrl][P_sum] + FPGA_bf_rx_align_stream_enable_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_stream_enable_R"], datatype=bool, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + FPGA_bf_rx_align_stream_enable_RW = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_stream_enable_RW"], datatype=bool, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM), access=AttrWriteType.READ_WRITE) + + # int64[N_pn][N_beamsets_ctrl][P_sum] + FPGA_bf_rx_align_bsn_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_bsn_R"], datatype=numpy.int64, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + + #int32[N_pn][N_beamsets_ctrl][P_sum] + FPGA_bf_rx_align_nof_packets_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_nof_packets_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + FPGA_bf_rx_align_nof_valid_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_nof_valid_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + FPGA_bf_rx_align_latency_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_latency_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + FPGA_bf_rx_align_nof_replaced_packets_R = attribute_wrapper(comms_annotation=["FPGA_bf_rx_align_nof_replaced_packets_R"], datatype=numpy.int32, dims=(N_PN, N_BEAMSETS_CTRL, P_SUM)) + subband_select_RW = attribute(dtype=(numpy.uint32,), max_dim_x=N_BEAMLETS_CTRL, access=AttrWriteType.READ_WRITE, fisallowed="is_attribute_access_allowed") def read_subband_select_RW(self): - return self._subband_select + # We can only return a single value, so we assume the FPGA is configured coherently. Which is something + # that is to be checked by an independent monitoring system anyway. + mask = self.sdp_proxy.TR_fpga_mask_RW + subbands = self.read_attribute("FPGA_beamlet_subband_select_RW") + subbands_in_mask = [s for idx,s in enumerate(subbands) if mask[idx]] + + # If there are no FPGAs selected at all, just return a sane default. + if not subbands_in_mask: + return self.subband_select_RW_default + + # We return the first setting within the mask. Convert into actual shape for a single FPGA + mask_for_all_inputs = subbands_in_mask[0].reshape(self.A_PN, self.N_POL, self.N_BEAMLETS_CTRL) + + # Return the first setting (antenna, pol) within this FPGA + return mask_for_all_inputs[0,0] def write_subband_select_RW(self, subbands): # Use the same subband for all inputs and polarisations of a beamlet @@ -165,9 +193,6 @@ class Beamlet(opcua_device): self.cache_clear() - # Store new value - self._subband_select = subbands - # ---------- # Summarising Attributes # ---------- @@ -179,8 +204,6 @@ class Beamlet(opcua_device): def configure_for_initialise(self): super().configure_for_initialise() - self._subband_select = numpy.zeros(self.N_BEAMLETS_CTRL, dtype=numpy.uint32) - self.sdp_proxy = DeviceProxy("STAT/SDP/1") self.sdp_proxy.set_source(DevSource.DEV) diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py index 3ff59ef6cdeb5ba5206c17a9414d46eee260b386..e39782b48b68d773f968a89b3b568849f8d183ef 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/bst.py @@ -17,7 +17,7 @@ from tango import AttrWriteType from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.clients.opcua_client import OPCUAConnection -from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.clients.statistics.client import StatisticsClient from tangostationcontrol.devices.sdp.statistics import Statistics from tangostationcontrol.devices.sdp.statistics_collector import BSTCollector @@ -85,7 +85,7 @@ class BST(Statistics): # number of packets with invalid payloads nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(BSTCollector.MAX_FPGAS,), datatype=numpy.uint64) # latest BSTs - bst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "bst_values"}, dims=(BSTCollector.MAX_BEAMLETS, BSTCollector.MAX_BLOCKS), datatype=numpy.uint64) + bst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "bst_values"}, dims=(BSTCollector.MAX_BLOCKS, BSTCollector.MAX_BEAMLETS), datatype=numpy.uint64) # reported timestamp # for each row in the latest BSTs bst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "bst_timestamps"}, dims=(BSTCollector.MAX_BLOCKS,), datatype=numpy.uint64) diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py index 7bb139292584d117442cfff418023f100d0dcf5f..256d156a4337bffb46e2ed085cbcc151d6d4f5b2 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/digitalbeam.py @@ -99,7 +99,7 @@ class DigitalBeam(beam_device): def write_antenna_select_RW(self, antennas): for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping): - if antenna_nr >= 0: + if self.antennafield_proxy.Antenna_Usage_Mask_R[input_nr] and antenna_nr >= 0: self._input_select[input_nr] = antennas[antenna_nr] # ---------- @@ -120,6 +120,7 @@ class DigitalBeam(beam_device): self.antennafield_proxy = DeviceProxy(self.AntennaField_Device) 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.set_source(DevSource.DEV) @@ -132,7 +133,7 @@ class DigitalBeam(beam_device): # Use reference position for any missing antennas so they always get a delay of 0 input_itrf = numpy.array([reference_itrf] * self.NUM_INPUTS) for input_nr, antenna_nr in enumerate(self.Input_to_Antenna_Mapping): - if antenna_nr >= 0: + if antenna_usage_mask[input_nr] and antenna_nr >= 0: input_itrf[input_nr] = antenna_itrf[antenna_nr] # a delay calculator @@ -142,7 +143,8 @@ class DigitalBeam(beam_device): self.relative_input_positions = input_itrf - reference_itrf # use all antennas in the mapping for all beamlets, unless specified otherwise - input_select = numpy.repeat((numpy.array(self.Input_to_Antenna_Mapping) >= 0), self.NUM_BEAMLETS).reshape(self.NUM_INPUTS, self.NUM_BEAMLETS) + input_select_mask = numpy.logical_and(numpy.array(self.Input_to_Antenna_Mapping) >= 0, antenna_usage_mask) + input_select = numpy.repeat(input_select_mask, self.NUM_BEAMLETS).reshape(self.NUM_INPUTS, self.NUM_BEAMLETS) self.write_input_select_RW(input_select) # -------- @@ -189,7 +191,8 @@ class DigitalBeam(beam_device): return result - def _set_pointing(self, pointing_direction: numpy.array, timestamp: datetime.datetime): + @TimeIt() + def _compute_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime) -> numpy.array: """ Uploads beam weights based on a given pointing direction 2D array (96 tiles x 3 parameters) """ @@ -206,6 +209,13 @@ class DigitalBeam(beam_device): # Filter out unwanted antennas beam_weights *= self._map_inputs_on_polarised_inputs(self._input_select) + return beam_weights + + @TimeIt() + def _apply_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime, beam_weights: numpy.array): + """ + Uploads beam weights based on a given pointing direction 2D array (96 tiles x 3 parameters) + """ # Write weights to SDP self.beamlet_proxy.FPGA_bf_weights_xx_yy_RW = beam_weights diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py index d4c4c1c4a2cea160c676af37d399a795d4a80476..2a7ca8991f168f6bf5ba673207da6dc9dbb09817 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sdp.py @@ -110,52 +110,57 @@ class SDP(opcua_device): # Attributes # ---------- - FPGA_firmware_version_R = attribute_wrapper(comms_annotation=["FPGA_firmware_version_R"], datatype=str, dims=(16,)) - FPGA_boot_image_R = attribute_wrapper(comms_annotation=["FPGA_boot_image_R"], datatype=numpy.int32, dims=(16,), doc="Active FPGA image (0=factory, 1=user)") - FPGA_boot_image_RW = attribute_wrapper(comms_annotation=["FPGA_boot_image_RW"], datatype=numpy.int32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_global_node_index_R = attribute_wrapper(comms_annotation=["FPGA_global_node_index_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["FPGA_hardware_version_R"], datatype=str, dims=(16,)) - FPGA_pps_present_R = attribute_wrapper(comms_annotation=["FPGA_pps_present_R"], datatype=bool, dims=(16,)) - FPGA_pps_capture_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_capture_cnt_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_pps_expected_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_pps_expected_cnt_RW = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["FPGA_processing_enable_R"], datatype=bool, dims=(16,)) - FPGA_processing_enable_RW = attribute_wrapper(comms_annotation=["FPGA_processing_enable_RW"], datatype=bool, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_ring_node_offset_R = attribute_wrapper(comms_annotation=["FPGA_ring_node_offset_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_ring_node_offset_RW = attribute_wrapper(comms_annotation=["FPGA_ring_node_offset_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_ring_nof_nodes_R = attribute_wrapper(comms_annotation=["FPGA_ring_nof_nodes_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_ring_nof_nodes_RW = attribute_wrapper(comms_annotation=["FPGA_ring_nof_nodes_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_ring_use_cable_to_next_rn_R = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_next_rn_R"], datatype=bool, dims=(16,)) - FPGA_ring_use_cable_to_next_rn_RW = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_next_rn_RW"], datatype=bool, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_ring_use_cable_to_previous_rn_R = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_previous_rn_R"], datatype=bool, dims=(16,)) - FPGA_ring_use_cable_to_previous_rn_RW = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_previous_rn_RW"], datatype=bool, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_scrap_R = attribute_wrapper(comms_annotation=["FPGA_scrap_R"], datatype=numpy.int32, dims=(8192,)) - FPGA_scrap_RW = attribute_wrapper(comms_annotation=["FPGA_scrap_RW"], datatype=numpy.int32, dims=(8192,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_antenna_band_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_antenna_band_index_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_block_period_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_block_period_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_f_adc_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_f_adc_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_fsub_type_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_fsub_type_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_nyquist_sampling_zone_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_nyquist_sampling_zone_index_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_observation_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_observation_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_sdp_info_station_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_R"], datatype=numpy.uint32, dims=(16,)) - FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(16,), access=AttrWriteType.READ_WRITE) - FPGA_subband_weights_R = attribute_wrapper(comms_annotation=["FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(12 * 512, 16)) - FPGA_subband_weights_RW = attribute_wrapper(comms_annotation=["FPGA_subband_weights_RW"], datatype=numpy.uint32, dims=(12 * 512, 16), access=AttrWriteType.READ_WRITE) - FPGA_time_since_last_pps_R = attribute_wrapper(comms_annotation=["FPGA_time_since_last_pps_R"], datatype=numpy.float_, dims=(16,)) - FPGA_temp_R = attribute_wrapper(comms_annotation=["FPGA_temp_R"], datatype=numpy.float_, dims=(16,)) - FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(12, 16)) - FPGA_wg_amplitude_RW = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) - FPGA_wg_enable_R = attribute_wrapper(comms_annotation=["FPGA_wg_enable_R"], datatype=bool, dims=(12, 16)) - FPGA_wg_enable_RW = attribute_wrapper(comms_annotation=["FPGA_wg_enable_RW"], datatype=bool, dims=(12, 16), access=AttrWriteType.READ_WRITE) - FPGA_wg_frequency_R = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_R"], datatype=numpy.float_, dims=(12, 16)) - FPGA_wg_frequency_RW = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) - FPGA_wg_phase_R = attribute_wrapper(comms_annotation=["FPGA_wg_phase_R"], datatype=numpy.float_, dims=(12, 16)) - FPGA_wg_phase_RW = attribute_wrapper(comms_annotation=["FPGA_wg_phase_RW"], datatype=numpy.float_, dims=(12, 16), access=AttrWriteType.READ_WRITE) - TR_fpga_mask_R = attribute_wrapper(comms_annotation=["TR_fpga_mask_R"], datatype=bool, dims=(16,)) - TR_fpga_mask_RW = attribute_wrapper(comms_annotation=["TR_fpga_mask_RW"], datatype=bool, dims=(16,), access=AttrWriteType.READ_WRITE) - TR_fpga_communication_error_R = attribute_wrapper(comms_annotation=["TR_fpga_communication_error_R"], datatype=bool, dims=(16,)) + # TODO(needs to not be statically declared as this can change depending on the station and configuration) + S_pn = 12 # Number of ADC signal inputs per Processing Node (PN) FPGA. + N_pn = 16 # Number of FPGAs per antenna band that is controlled via the SC - SDP interface. + N_beamsets_ctrl = 2 + + FPGA_firmware_version_R = attribute_wrapper(comms_annotation=["FPGA_firmware_version_R"], datatype=str, dims=(N_pn,)) + FPGA_boot_image_R = attribute_wrapper(comms_annotation=["FPGA_boot_image_R"], datatype=numpy.int32, dims=(N_pn,), doc="Active FPGA image (0=factory, 1=user)") + FPGA_boot_image_RW = attribute_wrapper(comms_annotation=["FPGA_boot_image_RW"], datatype=numpy.int32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_global_node_index_R = attribute_wrapper(comms_annotation=["FPGA_global_node_index_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_hardware_version_R = attribute_wrapper(comms_annotation=["FPGA_hardware_version_R"], datatype=str, dims=(N_pn,)) + FPGA_pps_present_R = attribute_wrapper(comms_annotation=["FPGA_pps_present_R"], datatype=bool, dims=(N_pn,)) + FPGA_pps_capture_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_capture_cnt_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_pps_expected_cnt_R = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_pps_expected_cnt_RW = attribute_wrapper(comms_annotation=["FPGA_pps_expected_cnt_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_processing_enable_R = attribute_wrapper(comms_annotation=["FPGA_processing_enable_R"], datatype=bool, dims=(N_pn,)) + FPGA_processing_enable_RW = attribute_wrapper(comms_annotation=["FPGA_processing_enable_RW"], datatype=bool, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_ring_node_offset_R = attribute_wrapper(comms_annotation=["FPGA_ring_node_offset_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_ring_node_offset_RW = attribute_wrapper(comms_annotation=["FPGA_ring_node_offset_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_ring_nof_nodes_R = attribute_wrapper(comms_annotation=["FPGA_ring_nof_nodes_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_ring_nof_nodes_RW = attribute_wrapper(comms_annotation=["FPGA_ring_nof_nodes_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_ring_use_cable_to_next_rn_R = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_next_rn_R"], datatype=bool, dims=(N_pn,)) + FPGA_ring_use_cable_to_next_rn_RW = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_next_rn_RW"], datatype=bool, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_ring_use_cable_to_previous_rn_R = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_previous_rn_R"], datatype=bool, dims=(N_pn,)) + FPGA_ring_use_cable_to_previous_rn_RW = attribute_wrapper(comms_annotation=["FPGA_ring_use_cable_to_previous_rn_RW"], datatype=bool, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_scrap_R = attribute_wrapper(comms_annotation=["FPGA_scrap_R"], datatype=numpy.int32, dims=(N_pn, 512)) + FPGA_scrap_RW = attribute_wrapper(comms_annotation=["FPGA_scrap_RW"], datatype=numpy.int32, dims=(N_pn, 512), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_antenna_band_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_antenna_band_index_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_block_period_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_block_period_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_f_adc_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_f_adc_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_fsub_type_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_fsub_type_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_nyquist_sampling_zone_index_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_nyquist_sampling_zone_index_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_nyquist_sampling_zone_index_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_observation_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_observation_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_observation_id_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_sdp_info_station_id_R = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_R"], datatype=numpy.uint32, dims=(N_pn,)) + FPGA_sdp_info_station_id_RW = attribute_wrapper(comms_annotation=["FPGA_sdp_info_station_id_RW"], datatype=numpy.uint32, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + FPGA_subband_weights_R = attribute_wrapper(comms_annotation=["FPGA_subband_weights_R"], datatype=numpy.uint32, dims=(N_pn, S_pn, 512)) + FPGA_subband_weights_RW = attribute_wrapper(comms_annotation=["FPGA_subband_weights_RW"], datatype=numpy.uint32, dims=(N_pn, S_pn, 512), access=AttrWriteType.READ_WRITE) + FPGA_time_since_last_pps_R = attribute_wrapper(comms_annotation=["FPGA_time_since_last_pps_R"], datatype=numpy.float_, dims=(N_pn,)) + FPGA_temp_R = attribute_wrapper(comms_annotation=["FPGA_temp_R"], datatype=numpy.float_, dims=(N_pn,)) + FPGA_wg_amplitude_R = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_R"], datatype=numpy.float_, dims=(N_pn, S_pn)) + FPGA_wg_amplitude_RW = attribute_wrapper(comms_annotation=["FPGA_wg_amplitude_RW"], datatype=numpy.float_, dims=(N_pn, S_pn), access=AttrWriteType.READ_WRITE) + FPGA_wg_enable_R = attribute_wrapper(comms_annotation=["FPGA_wg_enable_R"], datatype=bool, dims=(N_pn, S_pn)) + FPGA_wg_enable_RW = attribute_wrapper(comms_annotation=["FPGA_wg_enable_RW"], datatype=bool, dims=(N_pn, S_pn), access=AttrWriteType.READ_WRITE) + FPGA_wg_frequency_R = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_R"], datatype=numpy.float_, dims=(N_pn, S_pn)) + FPGA_wg_frequency_RW = attribute_wrapper(comms_annotation=["FPGA_wg_frequency_RW"], datatype=numpy.float_, dims=(N_pn, S_pn), access=AttrWriteType.READ_WRITE) + FPGA_wg_phase_R = attribute_wrapper(comms_annotation=["FPGA_wg_phase_R"], datatype=numpy.float_, dims=(N_pn, S_pn)) + FPGA_wg_phase_RW = attribute_wrapper(comms_annotation=["FPGA_wg_phase_RW"], datatype=numpy.float_, dims=(N_pn, S_pn), access=AttrWriteType.READ_WRITE) + TR_fpga_mask_R = attribute_wrapper(comms_annotation=["TR_fpga_mask_R"], datatype=bool, dims=(N_pn,)) + TR_fpga_mask_RW = attribute_wrapper(comms_annotation=["TR_fpga_mask_RW"], datatype=bool, dims=(N_pn,), access=AttrWriteType.READ_WRITE) + TR_fpga_communication_error_R = attribute_wrapper(comms_annotation=["TR_fpga_communication_error_R"], datatype=bool, dims=(N_pn,)) TR_sdp_config_first_fpga_nr_R = attribute_wrapper(comms_annotation=["TR_sdp_config_first_fpga_nr_R"], datatype=numpy.uint32) TR_sdp_config_nof_beamsets_R = attribute_wrapper(comms_annotation=["TR_sdp_config_nof_beamsets_R"], datatype=numpy.uint32) TR_sdp_config_nof_fpgas_R = attribute_wrapper(comms_annotation=["TR_sdp_config_nof_fpgas_R"], datatype=numpy.uint32) @@ -163,26 +168,21 @@ class SDP(opcua_device): TR_start_time_R = attribute_wrapper(comms_annotation=["TR_start_time_R"], datatype=numpy.int64) TR_tod_R = attribute_wrapper(comms_annotation=["TR_tod_R"], datatype=numpy.int64, dims=(2,)) TR_tod_pps_delta_R = attribute_wrapper(comms_annotation=["TR_tod_pps_delta_R"], datatype=numpy.double) - - # TODO(needs to not be statically declared as this can change depending on the station and configuration) - S_pn = 12 # Number of ADC signal inputs per Processing Node (PN) FPGA. - N_pn = 16 # Number of FPGAs per antenna band that is controlled via the SC - SDP interface. - N_beamsets_ctrl = 2 # OPC-UA MP only points for AIT - FPGA_signal_input_mean_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_mean_R"], datatype=numpy.double , dims=(S_pn, N_pn)) - FPGA_signal_input_rms_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_rms_R"], datatype=numpy.double, dims=(S_pn, N_pn)) + FPGA_signal_input_mean_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_mean_R"], datatype=numpy.double , dims=(N_pn, S_pn)) + FPGA_signal_input_rms_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_rms_R"], datatype=numpy.double, dims=(N_pn, S_pn)) - FPGA_jesd204b_csr_rbd_count_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_rbd_count_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) - FPGA_jesd204b_csr_dev_syncn_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_dev_syncn_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) - FPGA_jesd204b_rx_err0_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err0_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) - FPGA_jesd204b_rx_err1_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err1_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) + FPGA_jesd204b_csr_rbd_count_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_rbd_count_R"], datatype=numpy.uint32, dims=(N_pn, S_pn)) + FPGA_jesd204b_csr_dev_syncn_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_csr_dev_syncn_R"], datatype=numpy.uint32, dims=(N_pn, S_pn)) + FPGA_jesd204b_rx_err0_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err0_R"], datatype=numpy.uint32, dims=(N_pn, S_pn)) + FPGA_jesd204b_rx_err1_R = attribute_wrapper(comms_annotation=["FPGA_jesd204b_rx_err1_R"], datatype=numpy.uint32, dims=(N_pn, S_pn)) FPGA_signal_input_bsn_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_bsn_R"], datatype=numpy.int64, dims=(N_pn,)) FPGA_signal_input_nof_blocks_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_nof_blocks_R"], datatype=numpy.int32, dims=(N_pn,)) FPGA_signal_input_nof_samples_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_nof_samples_R"], datatype=numpy.int32, dims=(N_pn,)) - FPGA_signal_input_samples_delay_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_samples_delay_R"], datatype=numpy.uint32, dims=(S_pn, N_pn)) - FPGA_signal_input_samples_delay_RW = attribute_wrapper(comms_annotation=["FPGA_signal_input_samples_delay_RW"], datatype=numpy.uint32, dims=(S_pn, N_pn), access=AttrWriteType.READ_WRITE) + FPGA_signal_input_samples_delay_R = attribute_wrapper(comms_annotation=["FPGA_signal_input_samples_delay_R"], datatype=numpy.uint32, dims=(N_pn, S_pn)) + FPGA_signal_input_samples_delay_RW = attribute_wrapper(comms_annotation=["FPGA_signal_input_samples_delay_RW"], datatype=numpy.uint32, dims=(N_pn, S_pn), access=AttrWriteType.READ_WRITE) FPGA_bst_offload_bsn_R = attribute_wrapper(comms_annotation=["FPGA_bst_offload_bsn_R"], datatype=numpy.int64, dims=(N_pn, N_beamsets_ctrl)) @@ -283,6 +283,17 @@ class SDP(opcua_device): # Wait for the firmware to be loaded (ignoring masked out elements) self.wait_attribute("FPGA_boot_image_R", lambda attr: ((attr == 1) | ~wait_for).all(), 60) + + def _disable_hardware(self): + """ Disable the SDP hardware. """ + # Save actual mask values + TR_fpga_mask = self.proxy.TR_fpga_mask_RW + # Set the mask to all Trues + self.TR_fpga_mask_RW = [True] * 16 + # Boot the boot image firmware + self.FPGA_boot_image_RW = [0] * self.N_pn + # Restore the mask + self.TR_fpga_mask_RW = TR_fpga_mask # -------- # Commands diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py index a1ae871efd63be5de397984584460de8a0012987..30946bdcda6c504d08d6c30a6e4e8490e6253c75 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/sst.py @@ -19,7 +19,7 @@ from tango import AttrWriteType from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.clients.opcua_client import OPCUAConnection -from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.clients.statistics.client import StatisticsClient from tangostationcontrol.devices.sdp.statistics import Statistics from tangostationcontrol.devices.sdp.statistics_collector import SSTCollector @@ -99,7 +99,7 @@ class SST(Statistics): # number of packets with invalid payloads nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(SSTCollector.MAX_FPGAS,), datatype=numpy.uint64) # latest SSTs - sst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_values"}, dims=(SSTCollector.MAX_SUBBANDS, SSTCollector.MAX_INPUTS), datatype=numpy.uint64) + sst_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_values"}, dims=(SSTCollector.MAX_INPUTS, SSTCollector.MAX_SUBBANDS), datatype=numpy.uint64) # reported timestamp # for each row in the latest SSTs sst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "sst_timestamps"}, dims=(SSTCollector.MAX_INPUTS,), datatype=numpy.uint64) diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py index 92ac1518c3b3f4b7d104f31bde253a2e2005a2b3..c9c3d76338d1b8b7da039a4571355c975dbc6f1e 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py @@ -18,7 +18,7 @@ from tango import DeviceProxy, DevSource # Additional import import asyncio -from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.clients.statistics.client import StatisticsClient from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.devices.opcua_device import opcua_device from tangostationcontrol.common.lofar_logging import log_exceptions @@ -133,10 +133,10 @@ class Statistics(opcua_device): for i in self.attr_list(): try: if i.comms_id == StatisticsClient: - await i.async_set_comm_client(self.statistics_client) + await i.async_set_comm_client(self, self.statistics_client) except Exception as e: # use the pass function instead of setting read/write fails - i.set_pass_func() + i.set_pass_func(self) logger.warning("error while setting the sst attribute {} read/write function. {}. using pass function instead".format(i, e)) await self.statistics_client.start() diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py index b611d6614b60775c6037da6d0371c9d3bd065e68..b330a120488de8acdab62e7b3ae8d88f4f341811 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics_collector.py @@ -1,17 +1,15 @@ -from queue import Queue -from threading import Thread import logging import numpy import datetime from .packet import SSTPacket, XSTPacket, BSTPacket from tangostationcontrol.common.baselines import nr_baselines, baseline_index, baseline_from_index -from tangostationcontrol.clients.statistics_client_thread import StatisticsClientThread from tango import DeviceProxy, DevFailed, DevState logger = logging.getLogger() + class StatisticsCollector: """ Base class to process statistics packets into parameters matrices. """ @@ -355,65 +353,3 @@ class BSTCollector(StatisticsCollector): self.parameters["bst_values"][block_index][:self.MAX_BEAMLETS] = fields.payload self.parameters["bst_timestamps"][block_index] = numpy.float64(fields.timestamp().timestamp()) self.parameters["integration_intervals"][block_index] = fields.integration_interval() - - -class StatisticsConsumer(Thread, StatisticsClientThread): - """ Base class to process statistics packets from a queue, asynchronously. """ - - # Maximum time to wait for the Thread to get unstuck, if we want to stop - DISCONNECT_TIMEOUT = 10.0 - - # No default options required, for now? - _DEFAULT_OPTIONS = {} - - def __init__(self, queue: Queue, collector: StatisticsCollector): - self.queue = queue - self.collector = collector - self.last_packet = None - - super().__init__() - self.start() - - @property - def _options(self) -> dict: - return StatisticsConsumer._DEFAULT_OPTIONS - - def run(self): - logger.info("Starting statistics thread") - - while True: - self.last_packet = self.queue.get() - - # This is the exception/slow path, but python doesn't allow us to optimise that - if self.last_packet is None: - # None is the magic marker to stop processing - break - - try: - self.collector.process_packet(self.last_packet) - except ValueError as e: - logger.exception("Could not parse statistics packet") - - # continue processing - - logger.info("Stopped statistics thread") - - def join(self, timeout=0): - # insert magic marker - self.queue.put(None) - logger.info("Sent shutdown to statistics thread") - - super().join(timeout) - - def disconnect(self): - # TODO(Corne): Prevent duplicate code across TCPReplicator, UDPReceiver - # and StatisticsConsumer. - if not self.is_alive(): - return - - # try to get the thread shutdown, but don't stall forever - self.join(self.DISCONNECT_TIMEOUT) - - if self.is_alive(): - # there is nothing we can do except wait (stall) longer, which could be indefinitely. - logger.error(f"Statistics thread did not shut down after {self.DISCONNECT_TIMEOUT} seconds, just leaving it dangling. Please attach a debugger to thread ID {self.ident}.") diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py index 19068ff768dcf0eda9782dc213cf293bcb0ed004..ccb39ccf6214e638c0eb47cea4aa8b53afd088f9 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py @@ -19,7 +19,7 @@ from tango import AttrWriteType from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.clients.opcua_client import OPCUAConnection -from tangostationcontrol.clients.statistics_client import StatisticsClient +from tangostationcontrol.clients.statistics.client import StatisticsClient from tangostationcontrol.devices.sdp.statistics import Statistics from tangostationcontrol.devices.sdp.statistics_collector import XSTCollector @@ -122,8 +122,8 @@ class XST(Statistics): FPGA_xst_ring_rx_clear_total_counts_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_ring_rx_clear_total_counts_RW"], datatype=bool, dims=(N_PN,), access=AttrWriteType.READ_WRITE) FPGA_xst_ring_rx_clear_total_counts_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_ring_rx_clear_total_counts_R"], datatype=bool, dims=(N_PN,)) - FPGA_xst_rx_align_stream_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_rx_align_stream_enable_RW"], datatype=numpy.uint32, dims=(N_PN,), access=AttrWriteType.READ_WRITE) - FPGA_xst_rx_align_stream_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_rx_align_stream_enable_R"], datatype=numpy.uint32, dims=(N_PN,)) + FPGA_xst_rx_align_stream_enable_RW = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_rx_align_stream_enable_RW"], datatype=bool, dims=(N_PN,P_SQ), access=AttrWriteType.READ_WRITE) + FPGA_xst_rx_align_stream_enable_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_rx_align_stream_enable_R"], datatype=bool, dims=(N_PN,P_SQ)) FPGA_xst_ring_rx_total_nof_packets_received_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_ring_rx_total_nof_packets_received_R"], datatype=numpy.uint32, dims=(N_PN,)) FPGA_xst_ring_rx_total_nof_packets_discarded_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_ring_rx_total_nof_packets_discarded_R"], datatype=numpy.uint32, dims=(N_PN,)) FPGA_xst_ring_rx_total_nof_sync_received_R = attribute_wrapper(comms_id=OPCUAConnection, comms_annotation=["FPGA_xst_ring_rx_total_nof_sync_received_R"], datatype=numpy.uint32, dims=(N_PN,)) @@ -151,9 +151,9 @@ class XST(Statistics): # number of packets with invalid payloads nof_payload_errors_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "nof_payload_errors"}, dims=(XSTCollector.MAX_FPGAS,), datatype=numpy.uint64) # latest XSTs - xst_blocks_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_blocks", "reshape": True}, dims=(XSTCollector.MAX_BLOCKS * XSTCollector.BLOCK_LENGTH * XSTCollector.BLOCK_LENGTH * XSTCollector.VALUES_PER_COMPLEX, XSTCollector.MAX_PARALLEL_SUBBANDS), datatype=numpy.int64) + xst_blocks_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_blocks", "reshape": True}, dims=(XSTCollector.MAX_PARALLEL_SUBBANDS, XSTCollector.MAX_BLOCKS, XSTCollector.BLOCK_LENGTH, XSTCollector.BLOCK_LENGTH, XSTCollector.VALUES_PER_COMPLEX), datatype=numpy.int64) # whether the values in the block are conjugated and transposed - xst_conjugated_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_conjugated", "reshape": True}, dims=(XSTCollector.MAX_BLOCKS, XSTCollector.MAX_PARALLEL_SUBBANDS), datatype=bool) + xst_conjugated_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_conjugated", "reshape": True}, dims=(XSTCollector.MAX_PARALLEL_SUBBANDS, XSTCollector.MAX_BLOCKS), datatype=bool) # reported timestamp for each subband in the latest XSTs xst_timestamp_R = attribute_wrapper(comms_id=StatisticsClient, comms_annotation={"type": "statistics", "parameter": "xst_timestamps"}, dims=(XSTCollector.MAX_PARALLEL_SUBBANDS,), datatype=numpy.uint64) # which subband the XSTs describe diff --git a/tangostationcontrol/tangostationcontrol/devices/snmp_device.py b/tangostationcontrol/tangostationcontrol/devices/snmp_device.py new file mode 100644 index 0000000000000000000000000000000000000000..15dbf2d24691f8b97e1d51cfe8c827ade4532930 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/devices/snmp_device.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +""" SNMP Device Server for LOFAR2.0 + +""" + +# Additional import +from tangostationcontrol.devices.lofar_device import lofar_device +from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions + +from tango.server import device_property +import os + +import logging +from tangostationcontrol.clients.snmp_client import SNMP_client, mib_loader + +import pkg_resources + +from pysmi import debug + +debug.setLogger(debug.Debug('searcher', "compiler", "borrower", "reader")) + +logger = logging.getLogger() + +__all__ = ["snmp_device"] + + +@device_logging_to_python() +class snmp_device(lofar_device): + # ----------------- + # Device Properties + # ----------------- + SNMP_community = device_property( + dtype='DevString', + mandatory=True + ) + + SNMP_host = device_property( + dtype='DevString', + mandatory=True + ) + + SNMP_mib_dir = device_property( + dtype='DevString', + mandatory=True + ) + + SNMP_timeout = device_property( + dtype='DevDouble', + mandatory=True + ) + + SNMP_version = device_property( + dtype='DevULong', + mandatory=True + ) + + @log_exceptions() + def configure_for_initialise(self): + + # set up the SNMP client + self.snmp_manager = SNMP_client(self.SNMP_community, self.SNMP_host, self.SNMP_timeout, self.SNMP_version, self.Fault, self) + + # map an access helper class + for i in self.attr_list(): + try: + i.set_comm_client(self, self.snmp_manager) + except Exception as e: + # use the pass function instead of setting read/write fails + i.set_pass_func(self) + logger.warning("error while setting the SNMP attribute {} read/write function. {}".format(i, e)) + + self.snmp_manager.start() + + super().configure_for_initialise() + + @log_exceptions() + def configure_for_on(self): + super().configure_for_on() + + @log_exceptions() + def configure_for_off(self): + super().configure_for_off() + + def get_mib_dir(self): + mib_filename_path = pkg_resources.resource_filename('tangostationcontrol', self.SNMP_mib_dir) + mib_path = os.path.dirname(mib_filename_path) + + return mib_path + + def init_device(self): + super().init_device() + + # create the mib_loader and set the mib path + self.loader = mib_loader(self.get_mib_dir()) + + for i in self.attr_list(): + try: + # for all of the attributes attempt to load the pre-compiled MIB. Skips already loaded ones + self.loader.load_pymib(i.comms_annotation["mib"]) + except Exception as e: + raise Exception( + f"Failed to load MIB file: {i.comms_annotation.get('mib')} for attribute {i.name} in directory {self.get_mib_dir()} ") from e diff --git a/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py b/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py index 912753f82c655a65d785af891fbf03222db86c3b..777343c80492bd03de0f84c21d5f1a1c29e09b90 100644 --- a/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py +++ b/tangostationcontrol/tangostationcontrol/devices/temperature_manager.py @@ -12,8 +12,8 @@ from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions - -from tango import Util, DeviceProxy, AttributeInfoEx, AttrDataFormat, EventType +from tango.server import command +from tango import Util, DeviceProxy, AttributeInfoEx, AttrDataFormat, EventType, DevSource, DebugIt from tango.server import attribute, device_property import numpy as np @@ -60,6 +60,12 @@ class TemperatureManager(lofar_device): default_value=[] ) + Shutdown_Device_List = device_property( + dtype=[str], + mandatory=False, + default_value=["STAT/SDP/1", "STAT/UNB2/1", "STAT/RECV/1", "STAT/APSCT/1", "STAT/APSPU/1"] + ) + # ---------- # Attributes # ---------- @@ -87,6 +93,7 @@ class TemperatureManager(lofar_device): # get the proxy to the device proxy = DeviceProxy(f"{ds_inst}/{proxy_name}/{instance_number}") + proxy.set_source(DevSource.DEV) # make sure the attribute is polled, otherwise we wont receive events if not proxy.is_attribute_polled(f"{attribute_name}"): @@ -142,19 +149,24 @@ class TemperatureManager(lofar_device): logger.warning(f"Detected a temperature alarm for {event.device}: {event.attr_value.name} := {event.attr_value.value}") self.auto_shutdown_hardware() + # -------- + # Commands + # -------- + @command() + @DebugIt() def auto_shutdown_hardware(self): """ This function automatically shuts down all hardware devices whenever a temperature alarm is detected - In the future there should be a strategy for turning off devices """ - DeviceProxy("STAT/SDP/1").off() - DeviceProxy("STAT/UNB2/1").off() - DeviceProxy("STAT/RECV/1").off() - DeviceProxy("STAT/APSCT/1").off() - DeviceProxy("STAT/APSPU/1").off() - DeviceProxy("STAT/PSOC/1").off() - logger.warning(f"Temperature alarm triggered auto shutdown of all hardware devices") + for dev_name in self.Shutdown_Device_List: + try: + proxy = DeviceProxy(dev_name) + proxy.disable_hardware() + except Exception as e: + logger.warning(f"Automatic hardware shutdown of device {dev_name} has failed: {e.args[0]}") + # TODO(Stefano): Add "STAT/PSOC/1" to the shutdown list and develop its behaviour + logger.warning(f"Temperature alarm triggered auto shutdown of all hardware devices") # ---------- # Run server diff --git a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py index 34a5a6924f99ef7bbc43c37bb1abbfabd3c3276b..d8e98ad75c550ed85b5d6a23cc0c8c7543637011 100644 --- a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py +++ b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py @@ -19,6 +19,7 @@ from tangostationcontrol.common.lofar_logging import device_logging_to_python, l from tangostationcontrol.beam.delays import delay_calculator from tangostationcontrol.beam.hba_tile import NUMBER_OF_ELEMENTS_PER_TILE from tangostationcontrol.devices.beam_device import beam_device +from tangostationcontrol.devices.device_decorators import TimeIt import logging logger = logging.getLogger() @@ -88,7 +89,8 @@ class TileBeam(beam_device): return delays - def _set_pointing(self, pointing_direction: numpy.array, timestamp: datetime.datetime): + @TimeIt() + def _compute_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime) -> numpy.array: """ Uploads beam weights based on a given pointing direction 2D array (96 tiles x 3 parameters) """ @@ -98,10 +100,14 @@ class TileBeam(beam_device): # Convert delays into beam weights delays = delays.flatten() - HBAT_bf_delay_steps = self.antennafield_proxy.calculate_HBAT_bf_delay_steps(delays) + bf_delay_steps = self.antennafield_proxy.calculate_HBAT_bf_delay_steps(delays) + + return bf_delay_steps + @TimeIt() + def _apply_weights(self, pointing_direction: numpy.array, timestamp: datetime.datetime, bf_delay_steps: numpy.array): # Write weights to RECV through the HBATToRecvMapper - self.antennafield_proxy.HBAT_bf_delay_steps_RW = HBAT_bf_delay_steps.reshape(self._nr_tiles, NUMBER_OF_ELEMENTS_PER_TILE * 2) + self.antennafield_proxy.HBAT_bf_delay_steps_RW = bf_delay_steps.reshape(self._nr_tiles, NUMBER_OF_ELEMENTS_PER_TILE * 2) # Record where we now point to, now that we've updated the weights. # Only the entries within the mask have been updated diff --git a/tangostationcontrol/tangostationcontrol/devices/unb2.py b/tangostationcontrol/tangostationcontrol/devices/unb2.py index 51cd13735a8f8b72a1ee8ed77efb1da6dc3fef2a..8ec767588ae0e9fe333411b763d47c765da20649 100644 --- a/tangostationcontrol/tangostationcontrol/devices/unb2.py +++ b/tangostationcontrol/tangostationcontrol/devices/unb2.py @@ -19,8 +19,8 @@ from tango import AttrWriteType, DebugIt from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.devices.opcua_device import opcua_device -from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python +from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES from tangostationcontrol.devices.device_decorators import only_in_states import numpy @@ -73,38 +73,38 @@ class UNB2(opcua_device): 'UNB2_mask_RW' ] - UNB2TR_I2C_bus_DDR4_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_DDR4_error_R"],datatype=numpy.int64 , dims=(4,2)) + 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_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_error_R" ],datatype=numpy.int64 , dims=(2,)) - UNB2TR_I2C_bus_FPGA_PS_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_FPGA_PS_error_R"],datatype=numpy.int64 , dims=(4,2)) + UNB2TR_I2C_bus_FPGA_PS_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_FPGA_PS_error_R"],datatype=numpy.int64 , dims=(2,4)) UNB2TR_I2C_bus_PS_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_PS_error_R" ],datatype=numpy.int64 , dims=(2,)) - UNB2TR_I2C_bus_QSFP_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_QSFP_error_R"],datatype=numpy.int64 , dims=(24,2)) + UNB2TR_I2C_bus_QSFP_error_R = attribute_wrapper(comms_annotation=["UNB2TR_I2C_bus_QSFP_error_R"],datatype=numpy.int64 , dims=(2,24)) UNB2TR_monitor_rate_RW = attribute_wrapper(comms_annotation=["UNB2TR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) UNB2TR_translator_busy_R = attribute_wrapper(comms_annotation=["UNB2TR_translator_busy_R" ],datatype=bool) UNB2_DC_DC_48V_12V_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_IOUT_R" ],datatype=numpy.float64, dims=(2,)) UNB2_DC_DC_48V_12V_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_TEMP_R" ],datatype=numpy.float64, dims=(2,)) UNB2_DC_DC_48V_12V_VIN_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_VIN_R" ],datatype=numpy.float64, dims=(2,)) UNB2_DC_DC_48V_12V_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_DC_DC_48V_12V_VOUT_R" ],datatype=numpy.float64, dims=(2,)) - UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_DDR4_SLOT_TEMP_R"],datatype=numpy.float64, dims=(8,2)) - UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_CORE_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_IOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_TEMP_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_VOUT_R" ],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_IOUT_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_TEMP_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_VOUT_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_IOUT_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_TEMP_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_VOUT_R"],datatype=numpy.float64, dims=(4,2)) - UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_LOS_R" ],datatype=numpy.int64 , dims=(24,2)) - UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_TEMP_R"],datatype=numpy.float64, dims=(24,2)) + UNB2_FPGA_DDR4_SLOT_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_DDR4_SLOT_TEMP_R"],datatype=numpy.float64, dims=(2,8)) + UNB2_FPGA_POL_CORE_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_IOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_CORE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_TEMP_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_CORE_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_CORE_VOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_ERAM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_IOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_ERAM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_TEMP_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_ERAM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_ERAM_VOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_HGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_IOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_HGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_TEMP_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_HGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_HGXB_VOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_PGM_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_IOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_PGM_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_TEMP_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_PGM_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_PGM_VOUT_R" ],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_RXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_IOUT_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_RXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_TEMP_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_RXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_RXGXB_VOUT_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_TXGXB_IOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_IOUT_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_TXGXB_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_TEMP_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_POL_TXGXB_VOUT_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_POL_TXGXB_VOUT_R"],datatype=numpy.float64, dims=(2,4)) + UNB2_FPGA_QSFP_CAGE_LOS_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_LOS_R" ],datatype=numpy.int64 , dims=(2,24)) + UNB2_FPGA_QSFP_CAGE_TEMP_R = attribute_wrapper(comms_annotation=["UNB2_FPGA_QSFP_CAGE_TEMP_R"],datatype=numpy.float64, dims=(2,24)) UNB2_Front_Panel_LED_colour_R = attribute_wrapper(comms_annotation=["UNB2_Front_Panel_LED_colour_R"],datatype=numpy.int64 , dims=(2,)) UNB2_Front_Panel_LED_colour_RW = attribute_wrapper(comms_annotation=["UNB2_Front_Panel_LED_colour_RW"],datatype=numpy.int64 , dims=(2,), access=AttrWriteType.READ_WRITE) UNB2_mask_RW = attribute_wrapper(comms_annotation=["UNB2_mask_RW" ],datatype=bool, dims=(2,), access=AttrWriteType.READ_WRITE) @@ -198,13 +198,35 @@ class UNB2(opcua_device): # overloaded functions # -------- + def _initialise_hardware(self): + """ Initialise the UNB2 hardware. """ + + # Cycle UNB2s + self.UNB2_off() + self.wait_attribute("UNB2TR_translator_busy_R", False, self.UNB2_On_Off_timeout) + self.UNB2_on() + self.wait_attribute("UNB2TR_translator_busy_R", False, self.UNB2_On_Off_timeout) + + def _disable_hardware(self): + """ Disable the UNB2 hardware. """ + + # Save actual mask values + UNB2_mask = self.proxy.UNB2_mask_RW + # Set the mask to all Trues + self.UNB2_mask_RW = [True] * 2 + # Turn off the uniboards + self.UNB2_off() + self.wait_attribute("UNB2TR_translator_busy_R", False, self.UNB2_On_Off_timeout) + # Restore the mask + self.UNB2_mask_RW = UNB2_mask + # -------- # Commands # -------- @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def UNB2_off(self): """ @@ -214,7 +236,7 @@ class UNB2(opcua_device): @command() @DebugIt() - @only_in_states(lofar_device.DEFAULT_COMMAND_STATES) + @only_in_states(DEFAULT_COMMAND_STATES) def UNB2_on(self): """ @@ -222,15 +244,6 @@ class UNB2(opcua_device): """ self.opcua_connection.call_method(["UNB2_on"]) - def _initialise_hardware(self): - """ Initialise the UNB2 hardware. """ - - # Cycle UNB2s - self.UNB2_off() - self.wait_attribute("UNB2TR_translator_busy_R", False, self.UNB2_On_Off_timeout) - self.UNB2_on() - self.wait_attribute("UNB2TR_translator_busy_R", False, self.UNB2_On_Off_timeout) - # ---------- # Run server # ---------- diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/client/test_opcua_client_against_server.py b/tangostationcontrol/tangostationcontrol/integration_test/default/client/test_opcua_client_against_server.py index 3ce57a0ae8660ac040a3cff0f5ef8680c5909ebc..e16ac79e1f991737910d689b6fdc406dce2a0e3d 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/client/test_opcua_client_against_server.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/client/test_opcua_client_against_server.py @@ -88,7 +88,7 @@ class TestClientServer(base.IntegrationAsyncTestCase): class attribute(object): dim_x = 1 dim_y = 0 - numpy_type = numpy.double + datatype = numpy.double prot_attr = await test_client.setup_protocol_attribute(["double_R"], attribute()) @@ -108,7 +108,7 @@ class TestClientServer(base.IntegrationAsyncTestCase): class attribute(object): dim_x = 1 dim_y = 0 - numpy_type = numpy.double + datatype = numpy.double prot_attr = await test_client.setup_protocol_attribute(["double_RW"], attribute()) 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 2b34767d6c74df2199017e1a884a0e2165af7fb0..f14a2838d489b3f40a03186d565b56d75190f950 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py @@ -7,8 +7,10 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +import numpy +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse from .base import AbstractTestBases class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): @@ -30,3 +32,41 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase): def test_property_recv_devices_has_one_receiver(self): result = self.proxy.get_property("RECV_devices") self.assertSequenceEqual(result["RECV_devices"], ["STAT/RECV/1"]) + + def test_HBAT_ANT_mask_RW_configured_after_Antenna_Usage_Mask(self): + """ Verify if HBAT_ANT_mask_RW values are correctly configured from Antenna_Usage_Mask values """ + recv_proxy = self.setup_recv_proxy() + antennafield_proxy = self.proxy + numpy.testing.assert_equal(numpy.array([True] * 96), recv_proxy.ANT_mask_RW) + + antenna_qualities = numpy.array([AntennaQuality.OK] * 96) + antenna_use = numpy.array([AntennaUse.ON] + [AntennaUse.AUTO] * 95) + antenna_properties = {'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use} + mapping_properties = {"RECV_devices": ["STAT/RECV/1"], + "HBAT_Power_to_RECV_mapping": [-1, -1] * 48, + "HBAT_Control_to_RECV_mapping": [1, 0 , 1, 1] + [-1, -1] * 46} + antennafield_proxy.off() + antennafield_proxy.put_property(antenna_properties) + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() # initialise hardware values as well + numpy.testing.assert_equal(numpy.array([True] * 96), antennafield_proxy.Antenna_Usage_Mask_R) + numpy.testing.assert_equal(numpy.array([True] * 2 + [False] * 46), antennafield_proxy.HBAT_ANT_mask_RW) + numpy.testing.assert_equal(numpy.array([True] * 2 + [False] * 46 + [False] * 48), recv_proxy.ANT_mask_RW) + + def test_HBAT_ANT_mask_RW_configured_after_Antenna_Usage_Mask_only_one_functioning_antenna(self): + """ Verify if HBAT_ANT_mask_RW values are correctly configured from Antenna_Usage_Mask values (only second antenna is OK)""" + recv_proxy = self.setup_recv_proxy() + antennafield_proxy = self.proxy + antenna_qualities = numpy.array([AntennaQuality.BROKEN] + [AntennaQuality.OK] + [AntennaQuality.BROKEN] * 94) + antenna_use = numpy.array([AntennaUse.AUTO] * 96) + antenna_properties = {'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use} + mapping_properties = {"RECV_devices": ["STAT/RECV/1"], + "HBAT_Power_to_RECV_mapping": [-1, -1] * 48, + "HBAT_Control_to_RECV_mapping": [1, 0 , 1, 1] + [-1, -1] * 46} + antennafield_proxy.off() + antennafield_proxy.put_property(antenna_properties) + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() # initialise hardware values as well + numpy.testing.assert_equal(numpy.array([False] + [True] + [False] * 94), antennafield_proxy.Antenna_Usage_Mask_R) + numpy.testing.assert_equal(numpy.array([False] + [True] + [False] * 46), antennafield_proxy.HBAT_ANT_mask_RW) + numpy.testing.assert_equal(numpy.array([False] + [True] + [False] * 46 + [False] * 48), recv_proxy.ANT_mask_RW) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py index d13b16a1ef4cdfbc8e2e02d22805fa92314fb3bf..f8857a0f52cb9c9e84394863b52c41e0cbf461b3 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py @@ -9,6 +9,7 @@ # See LICENSE.txt for more info. from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse from .base import AbstractTestBases @@ -16,6 +17,10 @@ import numpy class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase): + antenna_qualities_ok = numpy.array([AntennaQuality.OK] * 96) + antenna_qualities_only_second = numpy.array([AntennaQuality.BROKEN] + [AntennaQuality.OK] + [AntennaQuality.BROKEN] * 94) + antenna_use_ok = numpy.array([AntennaUse.AUTO] * 96) + def setUp(self): """Intentionally recreate the device object in each test""" super().setUp("STAT/DigitalBeam/1") @@ -36,8 +41,31 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase): beamlet_proxy.warm_boot() beamlet_proxy.set_defaults() return beamlet_proxy + + def setup_sdp_proxy(self): + # setup SDP, on which this device depends + sdp_proxy = TestDeviceProxy("STAT/SDP/1") + sdp_proxy.off() + sdp_proxy.warm_boot() + sdp_proxy.set_defaults() + return sdp_proxy + + def setup_antennafield_proxy(self, antenna_qualities, antenna_use): + # setup AntennaField + NR_TILES = 48 + antennafield_proxy = TestDeviceProxy("STAT/AntennaField/1") + control_mapping = [[1,i] for i in range(NR_TILES)] + antennafield_proxy.put_property({"RECV_devices": ["STAT/RECV/1"], + "HBAT_Control_to_RECV_mapping": numpy.array(control_mapping).flatten(), + 'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use}) + antennafield_proxy.off() + antennafield_proxy.boot() + return antennafield_proxy def test_pointing_to_zenith(self): + self.setup_antennafield_proxy(self.antenna_qualities_ok, self.antenna_use_ok) + self.setup_sdp_proxy() + self.setup_recv_proxy() # Setup beamlet configuration self.beamlet_proxy.clock_RW = 200 * 1000000 self.beamlet_proxy.subband_select = list(range(488)) @@ -51,3 +79,25 @@ class TestDeviceDigitalBeam(AbstractTestBases.TestDeviceBase): # beam weights should now be non-zero, we don't actually check their values for correctness self.assertNotEqual(0, sum(self.beamlet_proxy.FPGA_bf_weights_xx_yy_RW.flatten())) + + def test_input_select_with_all_antennas_ok(self): + """ Verify if input and antenna select are correctly calculated following Antennafield.Antenna_Usage_Mask """ + antennafield_proxy = self.setup_antennafield_proxy(self.antenna_qualities_ok, self.antenna_use_ok) + numpy.testing.assert_equal(numpy.array([True] * 96), antennafield_proxy.Antenna_Usage_Mask_R) + self.setUp() + self.proxy.warm_boot() + expected_input_select = numpy.array([[True] * 488 ] * 48 + [[False] * 488] * 48) # first 48 rows are True + numpy.testing.assert_equal(expected_input_select, self.proxy.input_select_RW) + expected_antenna_select = numpy.array([[True] * 488 ] * 48) + numpy.testing.assert_equal(expected_antenna_select, self.proxy.antenna_select_RW) + + def test_input_select_with_only_second_antenna_ok(self): + """ Verify if input and antenna select are correctly calculated following Antennafield.Antenna_Usage_Mask """ + antennafield_proxy = self.setup_antennafield_proxy(self.antenna_qualities_only_second, self.antenna_use_ok) + numpy.testing.assert_equal(numpy.array([False] + [True] + [False] * 94), antennafield_proxy.Antenna_Usage_Mask_R) + self.setUp() + self.proxy.warm_boot() + expected_input_select = numpy.array([[False] * 488 ] + [[True] * 488] + [[False] * 488] * 46 + [[False] * 488] * 48) # first 48 rows are True + numpy.testing.assert_equal(expected_input_select, self.proxy.input_select_RW) + expected_antenna_select = numpy.array([[False] * 488 ] + [[True] * 488] + [[False] * 488] * 46) + numpy.testing.assert_equal(expected_antenna_select, self.proxy.antenna_select_RW) 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 ce09175933edc18baf555ba091bb8b72ebdb1781..d3f70dd07cf441be3f6deb75ce8358c861987194 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation.py @@ -14,6 +14,7 @@ from tango import DevState, DevFailed from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy from tangostationcontrol.test.devices.test_observation_base import TestObservationBase +from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse from .base import AbstractTestBases class TestDeviceObservation(AbstractTestBases.TestDeviceBase): @@ -69,11 +70,13 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase): # setup AntennaField antennafield_proxy = TestDeviceProxy("STAT/AntennaField/1") control_mapping = [[1,i] for i in range(self.NUM_TILES)] + antenna_qualities = numpy.array([AntennaQuality.OK] * 96) + antenna_use = numpy.array([AntennaUse.AUTO] * 96) antennafield_proxy.put_property({"RECV_devices": ["STAT/RECV/1"], - "HBAT_Power_to_RECV_mapping": numpy.array(control_mapping).flatten()}) + "HBAT_Control_to_RECV_mapping": numpy.array(control_mapping).flatten(), + 'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use}) antennafield_proxy.off() - antennafield_proxy.warm_boot() - antennafield_proxy.set_defaults() + antennafield_proxy.boot() return antennafield_proxy def setup_beamlet_proxy(self): diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py new file mode 100644 index 0000000000000000000000000000000000000000..055c86470de831df946b7994bfdc6d760624b83c --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py @@ -0,0 +1,239 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tango import DevState +from tango import DevFailed + +import numpy +import json +from datetime import datetime +from datetime import timedelta + +from tangostationcontrol.test.devices.test_observation_base import TestObservationBase +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from .base import AbstractTestBases + +class TestObservationControlDevice(AbstractTestBases.TestDeviceBase): + + NUM_TILES = 48 + NUM_BEAMLETS_CTRL = 488 + NUM_INPUTS = 96 + INPUT_TO_ANTENNA_MAPPING = [ + "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "10", "11", + "12", "13", "14", "15", "16", "17", + "18", "19", "20", "21", "22", "23", + "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", + "36", "37", "38", "39", "40", "41", + "42", "43", "44", "45", "46", "47", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1", + "-1", "-1", "-1", "-1", "-1", "-1" + ] + + def setUp(self): + super().setUp("STAT/ObservationControl/1") + self.VALID_JSON = TestObservationBase.VALID_JSON + self.recv_proxy = self.setup_recv_proxy() + self.antennafield_proxy = self.setup_antennafield_proxy() + self.beamlet_proxy = self.setup_beamlet_proxy() + self.sdp_proxy = self.setup_sdp_proxy() + self.digitalbeam_proxy = self.setup_digitalbeam_proxy() + self.tilebeam_proxy = self.setup_tilebeam_proxy() + + def setup_recv_proxy(self): + # setup RECV + recv_proxy = TestDeviceProxy("STAT/RECV/1") + recv_proxy.off() + recv_proxy.warm_boot() + recv_proxy.set_defaults() + return recv_proxy + + def setup_sdp_proxy(self): + # setup SDP + sdp_proxy = TestDeviceProxy("STAT/SDP/1") + sdp_proxy.off() + sdp_proxy.warm_boot() + return sdp_proxy + + def setup_antennafield_proxy(self): + # setup AntennaField + antennafield_proxy = TestDeviceProxy("STAT/AntennaField/1") + control_mapping = [[1,i] for i in range(self.NUM_TILES)] + antennafield_proxy.put_property({"RECV_devices": ["STAT/RECV/1"], + "HBAT_Power_to_RECV_mapping": numpy.array(control_mapping).flatten()}) + antennafield_proxy.off() + antennafield_proxy.warm_boot() + antennafield_proxy.set_defaults() + return antennafield_proxy + + def setup_beamlet_proxy(self): + # setup Digitalbeam + beamlet_proxy = TestDeviceProxy("STAT/Beamlet/1") + beamlet_proxy.off() + beamlet_proxy.warm_boot() + beamlet_proxy.set_defaults() + return beamlet_proxy + + def setup_digitalbeam_proxy(self): + # setup Digitalbeam + digitalbeam_proxy = TestDeviceProxy("STAT/DigitalBeam/1") + digitalbeam_proxy.put_property({"Input_to_Antenna_Mapping": numpy.array(self.INPUT_TO_ANTENNA_MAPPING).flatten()}) + digitalbeam_proxy.off() + digitalbeam_proxy.warm_boot() + digitalbeam_proxy.set_defaults() + return digitalbeam_proxy + + def setup_tilebeam_proxy(self): + # Setup Tilebeam + tilebeam_proxy = TestDeviceProxy("STAT/TileBeam/1") + tilebeam_proxy.off() + tilebeam_proxy.warm_boot() + tilebeam_proxy.set_defaults() + return tilebeam_proxy + + def on_device_assert(self, proxy): + """Transition the device to ON and assert intermediate states""" + + proxy.Off() + self.assertEqual(DevState.OFF, proxy.state()) + proxy.Initialise() + self.assertEqual(DevState.STANDBY, proxy.state()) + proxy.On() + self.assertEqual(DevState.ON, proxy.state()) + + def test_device_on(self): + """Transition the ObservationControl device to ON state""" + self.on_device_assert(self.proxy) + + def test_no_observation_running(self): + """Assert no current observations on fresh boot""" + + self.on_device_assert(self.proxy) + self.assertFalse(self.proxy.is_any_observation_running()) + self.assertFalse(self.proxy.is_observation_running(12345)) + self.assertFalse(self.proxy.is_observation_running(54321)) + + def test_check_and_convert_parameters_invalid_id(self): + """Test invalid parameter detection""" + + parameters = json.loads(self.VALID_JSON) + parameters['observation_id'] = -1 + + + self.on_device_assert(self.proxy) + self.assertRaises( + DevFailed, self.proxy.start_observation, json.dumps(parameters)) + + def test_check_and_convert_parameters_invalid_time(self): + """Test invalid parameter detection""" + + parameters = json.loads(self.VALID_JSON) + parameters['stop_time'] = (datetime.now() - timedelta(seconds=1)).isoformat() + + self.on_device_assert(self.proxy) + self.assertRaises( + DevFailed, self.proxy.start_observation, json.dumps(parameters)) + + def test_check_and_convert_parameters_invalid_empty(self): + """Test empty parameter detection""" + + self.on_device_assert(self.proxy) + self.assertRaises( + DevFailed, self.proxy.start_observation, "{}") + + def test_start_observation(self): + """Test starting an observation""" + + self.on_device_assert(self.proxy) + + self.proxy.start_observation(self.VALID_JSON) + + self.assertTrue(self.proxy.is_any_observation_running()) + self.assertTrue(self.proxy.is_observation_running(12345)) + + self.proxy.stop_observation(12345) + + def test_start_observation_multiple(self): + """Test starting multiple observations""" + + second_observation_json = json.loads(self.VALID_JSON) + second_observation_json['observation_id'] = 54321 + + self.on_device_assert(self.proxy) + + self.proxy.start_observation(self.VALID_JSON) + self.proxy.start_observation(json.dumps(second_observation_json)) + + self.assertTrue(self.proxy.is_any_observation_running()) + self.assertTrue(self.proxy.is_observation_running(12345)) + self.assertTrue(self.proxy.is_observation_running(54321)) + + self.proxy.stop_observation(12345) + self.proxy.stop_observation(54321) + + def test_stop_observation_invalid_id(self): + """Test stop_observation exceptions for invalid ids""" + + self.on_device_assert(self.proxy) + + self.assertRaises(DevFailed, self.proxy.stop_observation, -1) + + def test_stop_observation_invalid_running(self): + """Test stop_observation exceptions for not running""" + + self.on_device_assert(self.proxy) + + self.assertRaises(DevFailed, self.proxy.stop_observation, 2) + + def test_is_any_observation_running_after_stop_all_observations(self): + """Test whether is_any_observation_running conforms when we start & stop an observation""" + + self.on_device_assert(self.proxy) + + self.proxy.start_observation(self.VALID_JSON) + self.proxy.stop_all_observations() + + # Test false + self.assertFalse(self.proxy.is_any_observation_running()) + + def test_start_stop_observation(self): + """Test starting and stopping an observation""" + + self.on_device_assert(self.proxy) + + # uses ID 12345 + self.proxy.start_observation(self.VALID_JSON) + self.proxy.stop_observation(12345) + + # Test false + self.assertFalse(self.proxy.is_observation_running(12345)) + + def test_start_multi_stop_all_observation(self): + """Test starting and stopping multiple observations""" + + second_observation_json = json.loads(self.VALID_JSON) + second_observation_json['observation_id'] = 54321 + + self.on_device_assert(self.proxy) + + # uses ID 12345 + self.proxy.start_observation(self.VALID_JSON) + self.proxy.start_observation(json.dumps(second_observation_json)) + self.proxy.stop_all_observations() + + # Test false + self.assertFalse(self.proxy.is_observation_running(12345)) + self.assertFalse(self.proxy.is_observation_running(54321)) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py index 7944ac7434b94ad2268f642a711470ac2190640f..3e32310e130e219b75f4c9525ae87a5143f92eee 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_temperature_manager.py @@ -21,9 +21,9 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase): def setUp(self): """Intentionally recreate the device object in each test""" self.recv_proxy = self.setup_recv_proxy() + self.sdp_proxy = self.setup_sdp_proxy() super().setUp("STAT/TemperatureManager/1") - def tearDown(self): self.recv_proxy.stop_poll_attribute("HBAT_LED_on_RW") @@ -37,11 +37,36 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase): self.assertTrue(recv_proxy.is_attribute_polled(f"HBAT_LED_on_RW")) return recv_proxy + def setup_sdp_proxy(self): + # setup SDP, on which this device depends + sdp_proxy = TestDeviceProxy("STAT/SDP/1") + sdp_proxy.off() + sdp_proxy.warm_boot() + sdp_proxy.set_defaults() + return sdp_proxy + def test_alarm(self): + # Exclude other devices which raise a TimeoutError, since they wait for the attribute *_translator_busy_R to become False + # (set instead to True in this test environment) + self.proxy.put_property({"Shutdown_Device_List": ["STAT/SDP/1"]}) + devices = [DeviceProxy("STAT/SDP/1")] + self.proxy.off() self.proxy.initialise() self.proxy.on() + self.setup_recv_proxy() + self.setup_sdp_proxy() + + # make sure none of the devices are in the OFF or FAULT state. Any other state is fine + for dev in devices: + if dev.state() == DevState.OFF: + dev.warm_boot() + elif dev.state() == DevState.FAULT: + dev.off() + dev.warm_boot() + self.assertEqual(self.proxy.get_property('Shutdown_Device_List')['Shutdown_Device_List'][0], "STAT/SDP/1") + # Here we trigger our own change event by just using an RW attribute self.recv_proxy.HBAT_LED_on_RW = [[False] * 32] * 96 time.sleep(2) @@ -53,32 +78,7 @@ class TestDeviceTemperatureManager(AbstractTestBases.TestDeviceBase): # the TEMP_MANAGER_is_alarming_R should now be True, since it should have detected the temperature alarm. self.assertTrue(self.proxy.is_alarming_R) - - def test_shutdown(self): - self.proxy.off() - self.proxy.initialise() - self.proxy.on() - - devices = [DeviceProxy("STAT/SDP/1"), DeviceProxy("STAT/UNB2/1"), DeviceProxy("STAT/RECV/1"), - DeviceProxy("STAT/APSCT/1"), DeviceProxy("STAT/APSPU/1"), DeviceProxy("STAT/PSOC/1")] - - # make sure none of the devices are in the OFF state. Any other state is fine - for dev in devices: - if dev.state() == DevState.OFF: - dev.initialise() - - # toggle the attribute to make sure we get a change event to True - self.recv_proxy.HBAT_LED_on_RW = [[False] * 32] * 96 - self.recv_proxy.HBAT_LED_on_RW = [[True] * 32] * 96 - - # sleeping here to make sure we've dealt with the above events - time.sleep(2) - - # make sure all the devices are in the OFF state + + # make sure all the hardware devices are in the DISABLE state for dev in devices: - self.assertEqual(DevState.OFF, dev.state()) - - - - - + self.assertEqual(DevState.DISABLE, dev.state()) 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 32dfa8e322100fa16a86c1ab7d8610f617e315ed..991e03543e70c8b308319e45766f797e530a71c6 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py @@ -13,6 +13,7 @@ import datetime import json from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy +from tangostationcontrol.devices.antennafield import AntennaQuality, AntennaUse from .base import AbstractTestBases @@ -45,11 +46,13 @@ class TestDeviceTileBeam(AbstractTestBases.TestDeviceBase): # setup AntennaField antennafield_proxy = TestDeviceProxy("STAT/AntennaField/1") control_mapping = [[1,i] for i in range(self.NR_TILES)] + antenna_qualities = numpy.array([AntennaQuality.OK] * 96) + antenna_use = numpy.array([AntennaUse.AUTO] * 96) antennafield_proxy.put_property({"RECV_devices": ["STAT/RECV/1"], - "HBAT_Power_to_RECV_mapping": numpy.array(control_mapping).flatten()}) + "HBAT_Control_to_RECV_mapping": numpy.array(control_mapping).flatten(), + 'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use}) antennafield_proxy.off() - antennafield_proxy.warm_boot() - antennafield_proxy.set_defaults() + antennafield_proxy.boot() # check if AntennaField really exposes the expected number of tiles self.assertEqual(self.NR_TILES, antennafield_proxy.nr_tiles_R) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/statistics_writer/test_statistics_writer_sst.py b/tangostationcontrol/tangostationcontrol/integration_test/default/statistics_writer/test_statistics_writer_sst.py index 448fb0cabb3d367831b6603cd239b50a246a2bd4..f7f9c913c6ca6ba49a486a2afb4066be082ec7ca 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/statistics_writer/test_statistics_writer_sst.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/statistics_writer/test_statistics_writer_sst.py @@ -68,10 +68,10 @@ class TestStatisticsWriterSST(BaseIntegrationTestCase): stat = stat_parser.get_statistic('2021-09-20T12:17:40.000+00:00') # same as stat_parser.statistics[0] self.assertIsNotNone(stat) self.assertEqual(121, stat.data_id_signal_input_index) - # Test RECV attributes - self.assertListEqual(stat.rcu_attenuator_dB.tolist(), [[0] * 3] * 32) - self.assertListEqual(stat.rcu_band_select.tolist(), [[0] * 3] * 32) - self.assertListEqual(stat.rcu_dth_on.tolist(), [[False] * 3] * 32) + # RECV attributes are not present since the stats-writer is not connecting to any host + self.assertEqual(stat.rcu_attenuator_dB.tolist(), None) + self.assertEqual(stat.rcu_band_select.tolist(), None) + self.assertEqual(stat.rcu_dth_on.tolist(), None) def test_no_tango_SST_statistics(self): diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py b/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py index a0cb2e7ae876735554fc4fa53314c4cb7fdd5ac2..01cfc73b052e71131213810ed1ef6e29bda0a67b 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/toolkit/test_archiver.py @@ -145,7 +145,7 @@ class TestArchiver(BaseIntegrationTestCase): polling_period=1000 archive_event_period=5000 - attr_fullname = 'stat/recv/1/ant_mask_rw' # boolean 3x32 + attr_fullname = 'stat/recv/1/hbat_pwr_on_rw' # boolean 96x32 self.archiver.add_attribute_to_archiver(attr_fullname, polling_period, archive_event_period) time.sleep(3) # Test if the attribute has been correctly added to event subscriber @@ -158,7 +158,7 @@ class TestArchiver(BaseIntegrationTestCase): self.assertTrue(len(records)>0) item = records[-1] # last table record self.assertEqual('stat/recv/1',item.device) # column device - self.assertEqual('ant_mask_rw',item.name) # column attribute + self.assertEqual('hbat_pwr_on_rw',item.name) # column attribute self.assertEqual(datetime,type(item.data_time)) # column datetime self.assertEqual(int,type(item.x)) # column index x self.assertEqual(int,type(item.y)) # column index y diff --git a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py index 806dd6303e12fdc5dedf017bd67b8d04ae573ef2..49add415ca7905446488d56416c5437b994acf9e 100644 --- a/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py +++ b/tangostationcontrol/tangostationcontrol/statistics_writer/statistics_writer.py @@ -139,11 +139,11 @@ def main(): logger.debug("Setting loglevel to DEBUG") # sets the Tango connection in order to retrieve attribute values - if tango_disabled: + if tango_disabled or not host: logger.warning("Tango connection is DISABLED") device = None else: - device = DeviceProxy(args.device) if mode=='SST' else None + device = DeviceProxy(f"tango://{host}:10000/{args.device}".lower()) if mode=='SST' else None # creates the TCP receiver that is given to the writer receiver = _create_receiver(filename, host, port) diff --git a/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py b/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py index f2c537aafea96a8c28388bff40637bd8ca5eea73..a3b5779650217572dfeddb73eff324657acead2b 100644 --- a/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py +++ b/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py @@ -159,12 +159,12 @@ class TestDelays(base.TestCase): # verify parallellisation along direction axis for i, single_dir in enumerate(directions): single_dir_result = d.convert_bulk([single_dir], positions) - numpy.testing.assert_equal(single_dir_result[:, 0], bulk_result[:, i]) + numpy.testing.assert_almost_equal(single_dir_result[:, 0], bulk_result[:, i], 4) # verify parallellisation along position axis for i, single_pos in enumerate(positions): single_pos_result = d.convert_bulk(directions, [single_pos]) - numpy.testing.assert_equal(single_pos_result[0, :], bulk_result[i, :]) + numpy.testing.assert_almost_equal(single_pos_result[0, :], bulk_result[i, :], 4) def test_convert_bulk_speed(self): d = delay_calculator([0, 0, 0]) diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py index fd45244f870ccf0da97f378891638a17447bb28b..cfc90c25c7b06e61d7e0dca9f67cc0a46daac281 100644 --- a/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_attr_wrapper.py @@ -36,7 +36,7 @@ def dev_init(device): device.set_state(DevState.INIT) device.test_client = test_client(device.Fault) for i in device.attr_list(): - asyncio.run(i.async_set_comm_client(device.test_client)) + asyncio.run(i.async_set_comm_client(device, device.test_client)) device.test_client.start() @@ -216,85 +216,85 @@ class TestAttributeTypes(base.TestCase): dev_init(self) class str_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="str_image_R", datatype=str, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="str_image_RW", datatype=str, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="str_image_R", datatype=str, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="str_image_RW", datatype=str, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class bool_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="bool_image_R", datatype=bool, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="bool_image_RW", datatype=bool, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="bool_image_R", datatype=bool, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="bool_image_RW", datatype=bool, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class float32_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="float32_image_R", datatype=numpy.float32, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="float32_image_RW", datatype=numpy.float32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="float32_image_R", datatype=numpy.float32, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="float32_image_RW", datatype=numpy.float32, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class float64_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="float64_image_R", datatype=numpy.float64, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="float64_image_RW", datatype=numpy.float64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="float64_image_R", datatype=numpy.float64, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="float64_image_RW", datatype=numpy.float64, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class double_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="double_image_R", datatype=numpy.double, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="double_image_RW", datatype=numpy.double, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="double_image_R", datatype=numpy.double, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="double_image_RW", datatype=numpy.double, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class uint8_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="uint8_image_R", datatype=numpy.uint8, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="uint8_image_RW", datatype=numpy.uint8, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="uint8_image_R", datatype=numpy.uint8, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="uint8_image_RW", datatype=numpy.uint8, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class uint16_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="uint16_image_R", datatype=numpy.uint16, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="uint16_image_RW", datatype=numpy.uint16, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="uint16_image_R", datatype=numpy.uint16, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="uint16_image_RW", datatype=numpy.uint16, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class uint32_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="uint32_image_R", datatype=numpy.uint32, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="uint32_image_RW", datatype=numpy.uint32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="uint32_image_R", datatype=numpy.uint32, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="uint32_image_RW", datatype=numpy.uint32, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class uint64_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="uint64_image_R", datatype=numpy.uint64, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="uint64_image_RW", datatype=numpy.uint64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="uint64_image_R", datatype=numpy.uint64, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="uint64_image_RW", datatype=numpy.uint64, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class int16_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="int16_image_R", datatype=numpy.int16, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="int16_image_RW", datatype=numpy.int16, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="int16_image_R", datatype=numpy.int16, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="int16_image_RW", datatype=numpy.int16, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class int32_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="int32_image_R", datatype=numpy.int32, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="int32_image_RW", datatype=numpy.int32, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="int32_image_R", datatype=numpy.int32, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="int32_image_RW", datatype=numpy.int32, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) class int64_image_device(lofar_device): - image_R = attribute_wrapper(comms_annotation="int64_image_R", datatype=numpy.int64, dims=(2,3)) - image_RW = attribute_wrapper(comms_annotation="int64_image_RW", datatype=numpy.int64, access=AttrWriteType.READ_WRITE, dims=(2,3)) + image_R = attribute_wrapper(comms_annotation="int64_image_R", datatype=numpy.int64, dims=(3,2)) + image_RW = attribute_wrapper(comms_annotation="int64_image_RW", datatype=numpy.int64, access=AttrWriteType.READ_WRITE, dims=(3,2)) def configure_for_initialise(self): dev_init(self) diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py index 577bab69e469fb26af2252790b22f7f92d2c0ade..245e03eff42a5592fa3a36673a2d1e01405e680f 100644 --- a/tangostationcontrol/tangostationcontrol/test/clients/test_client.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_client.py @@ -65,7 +65,7 @@ class test_client(CommClient): else: dims = (attribute.dim_x,) - dtype = attribute.numpy_type + dtype = attribute.datatype return dims, dtype diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py index 0882411fed94e1d3621a17ef90be5b41e31b0fef..055a01430bb31617967bbacafe97ec3ede81a809 100644 --- a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client.py @@ -94,7 +94,7 @@ class TestOPCua(base.AsyncTestCase): for i in ATTR_TEST_TYPES: class mock_attr: def __init__(self, dtype, x, y): - self.numpy_type = dtype + self.datatype = dtype self.dim_x = x self.dim_y = y diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py index 17f866871bd682b3f289364c16a55e5ee2010a7c..1513f605ec7ee937fe9cc51764488fe0fde4f44b 100644 --- a/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_statistics_client_thread.py @@ -10,7 +10,7 @@ import logging from unittest import mock -from tangostationcontrol.clients.statistics_client_thread import \ +from tangostationcontrol.clients.statistics.client_thread import \ StatisticsClientThread from tangostationcontrol.test import base diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py index a6c53862b36604daabefb4de145d18997b752c1c..60703eb8eec7a42965528ba334676028da5cf100 100644 --- a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py +++ b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_logging.py @@ -9,6 +9,7 @@ from unittest import mock import logging +import unittest from tango.server import Device from tango import device_server @@ -68,7 +69,7 @@ class TestLofarLogging(base.TestCase): self.assertIn("tango_device", self.memory_handler.records[0].__dict__) self.assertIn("software_version", self.memory_handler.records[0].__dict__) - + @unittest.skip("Logs are not sent to Tango device currently, to reduce logspam") def test_configure_logging_uses_tango_device(self): """ Test whether log records get annotated with the active Tango device after using configure_logger(), and whether logs get forwarded to it. """ diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py index 94256ba5ce4bbba274f45d41ca6098acdd434381..96eb83300bec3352b1c0572653f38bf96a2e77d9 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py @@ -12,7 +12,7 @@ import numpy from tango.test_context import DeviceTestContext from tangostationcontrol.devices import antennafield -from tangostationcontrol.devices.antennafield import HBATToRecvMapper +from tangostationcontrol.devices.antennafield import HBATToRecvMapper, AntennaQuality, AntennaUse from tangostationcontrol.test import base from tangostationcontrol.test.devices import device_base @@ -30,7 +30,7 @@ class TestHBATToRecvMapper(base.TestCase): def test_ant_read_mask_r_no_mapping(self): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 3) - receiver_values = [[[False] * 3] * 32, [[False] * 3] * 32, [[False] * 3] * 32] + receiver_values = [[False] * 96, [False] * 96, [False] * 96] expected = [False] * 48 actual = mapper.map_read("ANT_mask_RW", receiver_values) numpy.testing.assert_equal(expected, actual) @@ -38,7 +38,7 @@ class TestHBATToRecvMapper(base.TestCase): def test_ant_read_mask_r_hba_0_and_1_on_rcu_1_and_0_of_recv_1(self): mapper = HBATToRecvMapper(self.CONTROL_HBA_0_AND_1_ON_RCU_1_AND_0_OF_RECV_1, self.POWER_NOT_CONNECTED, 3) - receiver_values = [[[False, True, False]] + [[False, False, False]] * 31, [[False] * 3] * 32, [[False] * 3] * 32] + receiver_values = [[False, True, False] + [False, False, False] * 31, [False] * 96, [False] * 96] expected = [True, False] + [False] * 46 actual = mapper.map_read("ANT_mask_RW", receiver_values) @@ -46,7 +46,7 @@ class TestHBATToRecvMapper(base.TestCase): def test_rcu_band_select_no_mapping(self): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 3) - receiver_values = [[[0] * 3] * 32, [[0] * 3] * 32, [[0] * 3] * 32] + receiver_values = [[0] * 96, [0] * 96, [0] * 96] expected = [0] * 48 actual = mapper.map_read("RCU_band_select_RW", receiver_values) numpy.testing.assert_equal(expected, actual) @@ -193,7 +193,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 1) set_values = [False] * 48 - expected = [[[False] * 3] * 32] + expected = [[False] * 96] actual = mapper.map_write("ANT_mask_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -201,7 +201,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 2) set_values = [False] * 48 - expected = [[[False] * 3] * 32] * 2 + expected = [[False] * 96] * 2 actual = mapper.map_write("ANT_mask_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -209,7 +209,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_HBA_0_AND_1_ON_RCU_1_AND_0_OF_RECV_1, self.POWER_NOT_CONNECTED, 1) set_values = [True, False] + [False] * 46 - expected = [[[False, True, False]] + [[False] * 3] * 31] + expected = [[False, True, False] + [False] * 93] actual = mapper.map_write("ANT_mask_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -217,7 +217,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 1) set_values = [0] * 48 - expected = [[[0] * 3] * 32] + expected = [[0] * 96] actual = mapper.map_write("RCU_band_select_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -225,7 +225,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 2) set_values = [0] * 48 - expected = [[[0] * 3] * 32] * 2 + expected = [[0] * 96] * 2 actual = mapper.map_write("RCU_band_select_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -233,7 +233,7 @@ class TestHBATToRecvMapper(base.TestCase): mapper = HBATToRecvMapper(self.CONTROL_HBA_0_AND_1_ON_RCU_1_AND_0_OF_RECV_1, self.POWER_NOT_CONNECTED, 1) set_values = [1, 0] + [0] * 46 - expected = [[[0, 1, 0]] + [[0] * 3] * 31] + expected = [[0, 1, 0] + [0] * 93] actual = mapper.map_write("RCU_band_select_RW", set_values) numpy.testing.assert_equal(expected, actual) @@ -340,6 +340,14 @@ class TestAntennafieldDevice(device_base.DeviceTestCase): AT_PROPERTIES = {'OPC_Server_Name': 'example.com', 'OPC_Server_Port': 4840, 'OPC_Time_Out': 5.0, 'Antenna_Field_Reference_ITRF' : [3.0, 3.0, 3.0], 'Antenna_Field_Reference_ETRS' : [7.0, 7.0, 7.0]} + # A mapping where HBATs are all not mapped to power RCUs + POWER_NOT_CONNECTED = [[-1, -1]] * 48 + # A mapping where HBATs are all not mapped to control RCUs + CONTROL_NOT_CONNECTED = [[-1, -1]] * 48 + # A mapping where first two HBATs are mapped on the first Receiver. + # The first HBAT control line on RCU 1 and the second HBAT control line on RCU 0. + CONTROL_HBA_0_AND_1_ON_RCU_1_AND_0_OF_RECV_1 = [[1, 1], [1, 0]] + [[-1, -1]] * 46 + def setUp(self): # DeviceTestCase setUp patches lofar_device DeviceProxy super(TestAntennafieldDevice, self).setUp() @@ -353,4 +361,31 @@ class TestAntennafieldDevice(device_base.DeviceTestCase): at_properties_v2 = {'OPC_Server_Name': 'example.com', 'OPC_Server_Port': 4840, 'OPC_Time_Out': 5.0, 'Antenna_Field_Reference_ETRS' : [7.0, 7.0, 7.0]} with DeviceTestContext(antennafield.AntennaField, properties=at_properties_v2, process=True) as proxy: self.assertNotEqual(3.0, proxy.Antenna_Field_Reference_ITRF_R[0]) # value = 6.948998835785814 - + + def test_read_Antenna_Quality(self): + """ Verify if Antenna_Quality_R is correctly retrieved """ + antenna_qualities = numpy.array([AntennaQuality.OK] * 96) + with DeviceTestContext(antennafield.AntennaField, properties=self.AT_PROPERTIES, process=True) as proxy: + numpy.testing.assert_equal(antenna_qualities, proxy.Antenna_Quality_R) + + def test_read_Antenna_Use(self): + """ Verify if Antenna_Use_R is correctly retrieved """ + antenna_use = numpy.array([AntennaUse.AUTO] * 96) + with DeviceTestContext(antennafield.AntennaField, properties=self.AT_PROPERTIES, process=True) as proxy: + numpy.testing.assert_equal(antenna_use, proxy.Antenna_Use_R) + + def test_read_Antenna_Usage_Mask(self): + """ Verify if Antenna_Usage_Mask_R is correctly retrieved """ + antenna_qualities = numpy.array([AntennaQuality.OK] * 96) + antenna_use = numpy.array([AntennaUse.ON] + [AntennaUse.AUTO] * 95) + antenna_properties = {'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use} + with DeviceTestContext(antennafield.AntennaField, properties={**self.AT_PROPERTIES, **antenna_properties}, process=True) as proxy: + numpy.testing.assert_equal(numpy.array([True] * 96), proxy.Antenna_Usage_Mask_R) + + def test_read_Antenna_Usage_Mask_only_one_functioning_antenna(self): + """ Verify if Antenna_Usage_Mask_R (only first antenna is OK) is correctly retrieved """ + antenna_qualities = numpy.array([AntennaQuality.OK] + [AntennaQuality.BROKEN] * 95) + antenna_use = numpy.array([AntennaUse.ON] + [AntennaUse.AUTO] * 95) + antenna_properties = {'Antenna_Quality': antenna_qualities, 'Antenna_Use': antenna_use} + with DeviceTestContext(antennafield.AntennaField, properties={**self.AT_PROPERTIES, **antenna_properties}, process=True) as proxy: + numpy.testing.assert_equal(numpy.array([True] + [False] * 95), proxy.Antenna_Usage_Mask_R) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py index d97cf7b9ebc6625cf9568fb2db24cab4edd51bc7..38d599f9f780cccd2d506a588ae8a2596f5f5ff8 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py @@ -9,6 +9,7 @@ from tango.test_context import DeviceTestContext from tango.server import attribute +from tango import DevState, DevFailed from tangostationcontrol.devices import lofar_device @@ -45,4 +46,23 @@ class TestLofarDevice(device_base.DeviceTestCase): proxy.initialise() self.assertEqual(42.0, proxy.read_attribute_A) self.assertListEqual([42.0, 43.0], proxy.read_attribute_B_array.tolist()) + + def test_disable_state(self): + with DeviceTestContext(lofar_device.lofar_device, process=True, timeout=10) as proxy: + proxy.initialise() + self.assertEqual(DevState.STANDBY, proxy.state()) + proxy.on() + self.assertEqual(DevState.ON, proxy.state()) + proxy.disable_hardware() + self.assertEqual(DevState.DISABLE, proxy.state()) + + def test_disable_state_transitions(self): + with DeviceTestContext(lofar_device.lofar_device, process=True, timeout=10) as proxy: + proxy.off() + with self.assertRaises(DevFailed): + proxy.disable_hardware() + proxy.warm_boot() + proxy.Fault() + with self.assertRaises(DevFailed): + proxy.disable_hardware() diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py index 93420364a8e8d28f2cea790a9be177f590a9ffea..1d5e2af773252e2e28ad789e6a9ccccb75468a91 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_observation_control_device.py @@ -7,18 +7,6 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -import mock - -from tango.test_context import DeviceTestContext -from tango import DevState -from tango import DevFailed - -import json -from datetime import datetime -from datetime import timedelta - -from tangostationcontrol.devices import observation_control - from tangostationcontrol.test import base from tangostationcontrol.test.devices import test_observation_base @@ -28,183 +16,4 @@ class TestObservationControlDevice(base.TestCase, test_observation_base.TestObse def setUp(self): super(TestObservationControlDevice, self).setUp() - def on_device_assert(self, proxy): - """Transition the device to ON and assert intermediate states""" - - proxy.Off() - self.assertEqual(DevState.OFF, proxy.state()) - proxy.Initialise() - self.assertEqual(DevState.STANDBY, proxy.state()) - proxy.On() - self.assertEqual(DevState.ON, proxy.state()) - - def mock_dynamic_devices(self): - observation_proxy = mock.patch.object( - observation_control, 'DeviceProxy') - self.m_observation_control = observation_proxy.start() - self.addCleanup(observation_proxy.stop) - - observation_dynamic_device = mock.patch.object( - observation_control.ObservationControl, 'create_dynamic_device', - autospec=True) - self.m_observation_dynamic_device = observation_dynamic_device.start() - self.addCleanup(observation_dynamic_device.stop) - - def test_device_on(self): - """Transition the ObservationControl device to ON state""" - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - def test_no_observation_running(self): - """Assert no current observations on fresh boot""" - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertFalse(proxy.is_any_observation_running()) - self.assertFalse(proxy.is_observation_running(12345)) - self.assertFalse(proxy.is_observation_running(54321)) - - def test_check_and_convert_parameters_invalid_id(self): - """Test invalid parameter detection""" - self.mock_dynamic_devices() - - parameters = json.loads(self.VALID_JSON) - parameters['observation_id'] = -1 - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertRaises( - DevFailed, proxy.start_observation, json.dumps(parameters)) - - def test_check_and_convert_parameters_invalid_time(self): - """Test invalid parameter detection""" - self.mock_dynamic_devices() - - parameters = json.loads(self.VALID_JSON) - parameters['stop_time'] = (datetime.now() - timedelta(seconds=1)).isoformat() - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertRaises( - DevFailed, proxy.start_observation, json.dumps(parameters)) - - def test_check_and_convert_parameters_invalid_empty(self): - """Test empty parameter detection""" - self.mock_dynamic_devices() - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertRaises( - DevFailed, proxy.start_observation, "{}") - - @mock.patch.object( - observation_control.ObservationControl, 'delete_dynamic_device') - def test_start_observation(self, m_delete_device): - """Test starting an observation""" - self.mock_dynamic_devices() - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - proxy.start_observation(self.VALID_JSON) - - self.assertTrue(proxy.is_any_observation_running()) - self.assertTrue(proxy.is_observation_running(12345)) - - @mock.patch.object( - observation_control.ObservationControl, 'delete_dynamic_device') - def test_start_observation_multiple(self, m_delete_device): - """Test starting multiple observations""" - self.mock_dynamic_devices() - - second_observation_json = json.loads(self.VALID_JSON) - second_observation_json['observation_id'] = 54321 - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - proxy.start_observation(self.VALID_JSON) - proxy.start_observation(json.dumps(second_observation_json)) - - self.assertTrue(proxy.is_any_observation_running()) - self.assertTrue(proxy.is_observation_running(12345)) - self.assertTrue(proxy.is_observation_running(54321)) - - def test_stop_observation_invalid_id(self): - """Test stop_observation exceptions for invalid ids""" - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertRaises(DevFailed, proxy.stop_observation, -1) - - def test_stop_observation_invalid_running(self): - """Test stop_observation exceptions for not running""" - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - self.assertRaises(DevFailed, proxy.stop_observation, 2) - - def test_is_any_observation_running_after_stop_all_observations(self): - """Test whether is_any_observation_running conforms when we start & stop an observation""" - self.mock_dynamic_devices() - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - proxy.start_observation(self.VALID_JSON) - proxy.stop_all_observations() - - # Test false - self.assertFalse(proxy.is_any_observation_running()) - - def test_start_stop_observation(self): - """Test starting and stopping an observation""" - self.mock_dynamic_devices() - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - # uses ID 12345 - proxy.start_observation(self.VALID_JSON) - proxy.stop_observation(12345) - - # Test false - self.assertFalse(proxy.is_observation_running(12345)) - - def test_start_multi_stop_all_observation(self): - """Test starting and stopping multiple observations""" - self.mock_dynamic_devices() - - second_observation_json = json.loads(self.VALID_JSON) - second_observation_json['observation_id'] = 54321 - - with DeviceTestContext(observation_control.ObservationControl, - process=True) as proxy: - self.on_device_assert(proxy) - - # uses ID 12345 - proxy.start_observation(self.VALID_JSON) - proxy.start_observation(json.dumps(second_observation_json)) - proxy.stop_all_observations() - - # Test false - self.assertFalse(proxy.is_observation_running(12345)) - self.assertFalse(proxy.is_observation_running(54321)) + # Moved to Integration Test diff --git a/tangostationcontrol/tox.ini b/tangostationcontrol/tox.ini index 62b18988c5b4289ae1c12b3283d7e1b4b52fd8f7..74e93462b8e97325c12e81b53f372b34961a05e1 100644 --- a/tangostationcontrol/tox.ini +++ b/tangostationcontrol/tox.ini @@ -46,9 +46,9 @@ commands = {envpython} -m coverage erase {envpython} -m stestr run {posargs} {envpython} -m coverage combine - {envpython} -m coverage html -d cover + {envpython} -m coverage html --omit='*test*' -d cover {envpython} -m coverage xml -o coverage.xml - {envpython} -m coverage report + {envpython} -m coverage report --omit='*test*' ; TODO(Corne): Integrate Hacking to customize pep8 rules [testenv:pep8]