diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8bbc48ec8e58a29303e516874f4d44c174e20cae..ffa80d4f794dba8d2d4ae923911bce61e977fd7d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -117,13 +117,6 @@ docker_build_image_all: - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh loki latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh logstash latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh jupyter-lab latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh apsct-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh ccd-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh apspu-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh recvh-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh recvl-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh sdptr-sim latest - - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh unb2-sim latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh grafana latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh prometheus latest - bash $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh tango-prometheus-exporter latest @@ -206,83 +199,6 @@ docker_build_image_jupyter: 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 jupyter-lab $tag -docker_build_image_apsct_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/aspct-sim.yml - - docker-compose/pypcc-sim-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 apsct-sim $tag -docker_build_image_ccd_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/ccd-sim.yml - - docker-compose/pypcc-sim-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 ccd-sim $tag -docker_build_image_apspu_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/apspu-sim.yml - - docker-compose/pypcc-sim-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 apspu-sim $tag -docker_build_image_recvh_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/recvh-sim.yml - - docker-compose/pypcc-sim-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 recvh-sim $tag -docker_build_image_recvl_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/recvl-sim.yml - - docker-compose/pypcc-sim-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 recvl-sim $tag -docker_build_image_sdptr_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/sdptr-sim.yml - - docker-compose/sdptr-sim/* - 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 sdptr-sim $tag -docker_build_image_unb2_sim: - extends: .base_docker_images_except - only: - refs: - - merge_requests - changes: - - docker-compose/unb2-sim.yml - - docker-compose/pypcc-sim-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 unb2-sim $tag newline_at_eof: stage: linting before_script: diff --git a/docker-compose/Makefile b/docker-compose/Makefile index 51a74085d4e85c0f039d4ba1c6be6ddb9831b01e..92240df541107275120d87cfff9f40fa2c03110a 100644 --- a/docker-compose/Makefile +++ b/docker-compose/Makefile @@ -166,7 +166,7 @@ DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) \ .DEFAULT_GOAL := help pull: ## pull the images from the Docker hub - $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) pull + $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) pull --ignore-pull-failures base: context ## Build base lofar device image $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) build --progress=plain lofar-device-base @@ -271,5 +271,9 @@ clean: down ## clear all TANGO database entries, and all containers docker volume rm $(BASEDIR)_tangodb $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) rm -f +nuke: down + - docker rmi -f $(shell $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) config --images) + - docker volume rm $(shell $(DOCKER_COMPOSE_ARGS) $(DOCKER_COMPOSE) $(COMPOSE_FILE_ARGS) config --format json | jq '.volumes | .[] | .name' -r) + help: ## show this help. @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/docker-compose/README.md b/docker-compose/README.md index 4b57a0b08e65b6bfc99701e38fff9db9c386f9fa..05882d6ad8bdd4aeafd4554cc07dfbad7bd47f62 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -8,12 +8,12 @@ contains developer expectations that they should uphold to. ## Index 1. [Station services]() - 1. [Types of container services and specific strategies](#types-of-containers-and-specific-strategies) + 1. [Types of container services and specific strategies](#types-of-containers-and-specific-strategies) 2. [HDB++ image updates](#hdb-image-updates) 3. [Gitlab CI/CD](#gitlab-cicd) - 1. [Image tagging and change detection](#image-tagging-and-change-detection) - 1. [Setup and maintenance](#setup-and-maintenance) - 2. [Gitlab CI phases](#gitlab-ci-phases) + 1. [Image tagging and change detection](#image-tagging-and-change-detection) + 1. [Setup and maintenance](#setup-and-maintenance) + 2. [Gitlab CI phases](#gitlab-ci-phases) ## Station Services @@ -23,35 +23,34 @@ are used in production. - [Devices \[external\]](https://lofar20-station-control.readthedocs.io/en/latest/devices/overview.html) - Simulators - - [sdptr \[external\]](https://git.astron.nl/lofar2.0/sdptr) - - [sdptr-sim](sdptr-sim.yml) - - [pypcc \[external\]](https://git.astron.nl/lofar2.0/pypcc) - - [pypcc-sim-base](pypcc-sim-base/Dockerfile) - - [apsct-sim](apsct-sim.yml) - - [apspu-sim](apspu-sim.yml) - - [ccd-sim](ccd-sim.yml) - - [recvh-sim](recvh-sim.yml) - - [recvl-sim](recvl-sim.yml) - - [unb2-sim](unb2-sim.yml) + - [sdptr \[external\]](https://git.astron.nl/lofar2.0/sdptr) + - [sdptr-sim](sdptr-sim.yml) + - [pypcc \[external\]](https://git.astron.nl/lofar2.0/pypcc) + - [apsct-sim](apsct-sim.yml) + - [apspu-sim](apspu-sim.yml) + - [ccd-sim](ccd-sim.yml) + - [recvh-sim](recvh-sim.yml) + - [recvl-sim](recvl-sim.yml) + - [unb2-sim](unb2-sim.yml) - Base images - - [ci-runner](ci-runner/Dockerfile) - - [lofar-device-base](lofar-device-base.yml) + - [ci-runner](ci-runner/Dockerfile) + - [lofar-device-base](lofar-device-base.yml) - Services - - databases - - dsconfig - - prometheus - - webservers / user interfaces - - jupyterlab - - [grafana](grafana/README.md) - - [http-json-schemas](http-json-schemas/README.md) - - logging / monitoring - - prometheus-node-exporter - - [tango-prometheus-exporter](tango-prometheus-exporter/README.md) - - logstash (grafana-logstash-output-loki) - - loki - - tango - - itango - - tango-rest + - databases + - dsconfig + - prometheus + - webservers / user interfaces + - jupyterlab + - [grafana](grafana/README.md) + - [http-json-schemas](http-json-schemas/README.md) + - logging / monitoring + - prometheus-node-exporter + - [tango-prometheus-exporter](tango-prometheus-exporter/README.md) + - logstash (grafana-logstash-output-loki) + - loki + - tango + - itango + - tango-rest ### Types of containers and specific strategies diff --git a/docker-compose/apsct-sim.yml b/docker-compose/apsct-sim.yml index d6ef5b9a579976c0952020030a829bef637f087f..e1c1fdf722aaf1e724f4a6b1ec57b51f47e4ddf3 100644 --- a/docker-compose/apsct-sim.yml +++ b/docker-compose/apsct-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: apsct-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: apsct-sim container_name: apsct-sim logging: diff --git a/docker-compose/apspu-sim.yml b/docker-compose/apspu-sim.yml index ebfddbaa8bb2963441fe93f239256bc8bb98c88e..be0e38fd8fbdb42cd25ae9fee0d1efb672117dcb 100644 --- a/docker-compose/apspu-sim.yml +++ b/docker-compose/apspu-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: apspu-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: apspu-sim container_name: apspu-sim logging: diff --git a/docker-compose/ccd-sim.yml b/docker-compose/ccd-sim.yml index 1580b222ec4be3f86de7d3df057efcb3a01e6ce6..89a711ea871516bf83686b5ffdfbf6f9c1473a54 100644 --- a/docker-compose/ccd-sim.yml +++ b/docker-compose/ccd-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: ccd-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: ccd-sim container_name: ccd-sim logging: diff --git a/docker-compose/jupyter-lab/Dockerfile b/docker-compose/jupyter-lab/Dockerfile index 32cc522bd553d3d839487fdab9afe2ea32240c2d..1ac48fd3656247bedcc573242e7c140eb4c2b05a 100644 --- a/docker-compose/jupyter-lab/Dockerfile +++ b/docker-compose/jupyter-lab/Dockerfile @@ -61,7 +61,7 @@ RUN sudo chmod +x /usr/bin/tini # Needed to perform certain migration actions during startup RUN sudo mkdir -p /home/tango/.jupyter/lab/workspaces -RUN sudo chmod 0777 /home/tango/.jupyter +RUN sudo chmod -R 0777 /home/tango/.jupyter USER ${CONTAINER_EXECUTION_UID} # pyppeteer-install installs in the homedir, so run it as the user that will execute the notebook diff --git a/docker-compose/pypcc-sim-base/Dockerfile b/docker-compose/pypcc-sim-base/Dockerfile deleted file mode 100644 index be2b00841cde80cedbe867323d0203d90c36a879..0000000000000000000000000000000000000000 --- a/docker-compose/pypcc-sim-base/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -ARG LOCAL_DOCKER_REGISTRY_HOST -ARG LOCAL_DOCKER_REGISTRY_LOFAR - -FROM ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest - -CMD ["hwtr", "--simulator", "--port","4843"] diff --git a/docker-compose/recvh-sim.yml b/docker-compose/recvh-sim.yml index d6d3dec7e365389ce676081a5b8c7867c116a8e9..3cbd437f083ebe32308f57b2e0e22a57d7814da3 100644 --- a/docker-compose/recvh-sim.yml +++ b/docker-compose/recvh-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: recvh-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: recvh-sim container_name: recvh-sim logging: diff --git a/docker-compose/recvl-sim.yml b/docker-compose/recvl-sim.yml index ee95f45583df79a0cc48090552147eb366aedb03..d257992bc4b4e37b026804dcec19b298c44e6f29 100644 --- a/docker-compose/recvl-sim.yml +++ b/docker-compose/recvl-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: recvl-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: recvl-sim container_name: recvl-sim logging: diff --git a/docker-compose/sdptr-sim.yml b/docker-compose/sdptr-sim.yml index 68c91fff0fd39f06c96ca6dbb4e100e8ac44bd89..740f37613000f822050ca728a15f21ba00b46450 100644 --- a/docker-compose/sdptr-sim.yml +++ b/docker-compose/sdptr-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: sdptr-sim: - build: - context: sdptr-sim - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/sdptr:latest hostname: sdptr-sim container_name: sdptr-sim logging: @@ -22,6 +18,8 @@ services: options: max-size: "100m" max-file: "10" + + command: /usr/local/bin/sdptr --ip_prefix=127.0. --nodaemon networks: - control restart: unless-stopped diff --git a/docker-compose/sdptr-sim/Dockerfile b/docker-compose/sdptr-sim/Dockerfile deleted file mode 100644 index 678b79ecef5d9425f2993fb26a2163b7c50036f7..0000000000000000000000000000000000000000 --- a/docker-compose/sdptr-sim/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -ARG LOCAL_DOCKER_REGISTRY_HOST -ARG LOCAL_DOCKER_REGISTRY_LOFAR - -FROM ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/sdptr:latest - -WORKDIR /sdptr/src -CMD ["sdptr", "--ip_prefix=127.0.", "--nodaemon"] diff --git a/docker-compose/unb2-sim.yml b/docker-compose/unb2-sim.yml index 89abb8ce69d70a0aac09f9ba8d30c30a1aa9ec93..1a99e5fddeef499fe6ae8901ef2811c3f6b7189e 100644 --- a/docker-compose/unb2-sim.yml +++ b/docker-compose/unb2-sim.yml @@ -10,11 +10,7 @@ version: '2.1' services: unb2-sim: - build: - context: pypcc-sim-base - args: - - LOCAL_DOCKER_REGISTRY_HOST=${LOCAL_DOCKER_REGISTRY_HOST} - - LOCAL_DOCKER_REGISTRY_LOFAR=${LOCAL_DOCKER_REGISTRY_LOFAR} + image: ${LOCAL_DOCKER_REGISTRY_HOST}/${LOCAL_DOCKER_REGISTRY_LOFAR}/pypcc:latest hostname: unb2-sim container_name: unb2-sim logging: diff --git a/sbin/container_logs.sh b/sbin/container_logs.sh new file mode 100755 index 0000000000000000000000000000000000000000..16eee53ea73217be43e47fc163df785514b9227a --- /dev/null +++ b/sbin/container_logs.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +trap "echo Exited!; exit;" SIGINT SIGTERM +while : +do + docker logs "$1" -f + sleep 1 +done diff --git a/sbin/tag_and_push_docker_image.sh b/sbin/tag_and_push_docker_image.sh index d852c4386295464c2309e08cee78e1e1be4a216d..9ee815ee16653a2ee64b565afe5f3476d85d8f51 100755 --- a/sbin/tag_and_push_docker_image.sh +++ b/sbin/tag_and_push_docker_image.sh @@ -70,12 +70,6 @@ LOCAL_IMAGES=( "logstash logstash y" "lofar-device-base lofar-device-base y" - "apsct-sim docker-compose_apsct-sim y" "apspu-sim docker-compose_apspu-sim y" - "ccd-sim docker-compose_ccd-sim y" - "recvh-sim docker-compose_recvh-sim y" "recvl-sim docker-compose_recvl-sim y" - "sdptr-sim docker-compose_sdptr-sim y" - "unb2-sim docker-compose_unb2-sim y" - "itango docker-compose_itango y" "grafana grafana n" "prometheus prometheus n" diff --git a/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy.py b/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy.py index a6c72933a1a41804e7fbc655f8b5d350d2cf30be..e3e8723f9d12de3ac4af0a124628830c7d075b95 100644 --- a/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy.py +++ b/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy.py @@ -1,22 +1,26 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 """Abstract Hierarchy for PyTango devices""" +import fnmatch +import logging from abc import ABC from enum import Enum from typing import Callable from typing import Dict from typing import List -from typing import Union from typing import Optional +from typing import Union -import fnmatch - -from tango import DevState, DeviceProxy +import tango +from tango import DevState +from tango import DeviceProxy from tangostationcontrol.common.proxy import create_device_proxy +logger = logging.getLogger() + class HierarchyMatchingFilter(Enum): """Select to filter by exact match, substring or regex""" @@ -302,6 +306,53 @@ class AbstractHierarchy(ABC): child_filter, self.children(depth=-1), self._get_filter(filter_type) ) + def branch_child( + self, + child_filter: child_filter_input_type, + filter_type: HierarchyMatchingFilter, + ) -> Optional[DeviceProxy]: + child = self.child(child_filter, filter_type) + + if child: + return child + + db = tango.Database() + children = db.get_device_property(self.parents()[0], self._child_property_name)[ + self._child_property_name + ] + return AbstractHierarchy( + self._child_property_name, children, self.parents()[1:], self._proxies + ).branch_child(child_filter, filter_type) + + def branch_children_names( + self, + child_filter: child_filter_input_type, + filter_type: HierarchyMatchingFilter, + ) -> List[str]: + """Retrieve Device children names matching child_filter substring located in + the same branch of the hierarchy + + :param child_filter: Substring of the device to retrieve device names list + for + :param filter_type: Type of filter such as exact, substring or regex + :return A list of device names matching the child filter substring + """ + if isinstance(child_filter, Enum): + child_filter = child_filter.value + + children = self.children_names(child_filter, filter_type) + + if len(children) > 0: + return children + + db = tango.Database() + children = db.get_device_property(self.parents()[0], self._child_property_name)[ + self._child_property_name + ] + return AbstractHierarchy( + self._child_property_name, children, self.parents()[1:], self._proxies + ).branch_children_names(child_filter, filter_type) + def _match_parent(self, parent_name: str): """Try to find an exact match for one of our parents diff --git a/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy_device.py b/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy_device.py index 586611fb2440f8e4dcd3dcf75ad53c9842d43c3f..fca7b3e95c2dea1a4b769ea3b44e10194e8a186f 100644 --- a/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/interfaces/hierarchy_device.py @@ -1,16 +1,16 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 """Abstract Hierarchy Device for PyTango devices""" +import logging from typing import Dict from typing import List from typing import Optional -import logging from tango import Database -from tango import DeviceProxy from tango import DevState +from tango import DeviceProxy from tangostationcontrol.devices.interfaces.hierarchy import AbstractHierarchy from tangostationcontrol.devices.interfaces.hierarchy import HierarchyMatchingFilter @@ -29,7 +29,7 @@ class AbstractHierarchyDevice: """ def __init__(self): - self._hierarchy = None + self._hierarchy: Optional[AbstractHierarchy] = None @staticmethod def _find_parents(device_name: str, child_property: str): @@ -108,7 +108,21 @@ class AbstractHierarchyDevice: ) -> Optional[DeviceProxy]: return self._hierarchy.child(child_filter_str, matching_filter) - def parent(self): + def branch_child( + self, + child_filter_str: str, + matching_filter: HierarchyMatchingFilter = HierarchyMatchingFilter.Find, + ) -> Optional[DeviceProxy]: + return self._hierarchy.branch_child(child_filter_str, matching_filter) + + def branch_children_names( + self, + child_filter_str: str, + matching_filter: HierarchyMatchingFilter = HierarchyMatchingFilter.Find, + ) -> List[str]: + return self._hierarchy.branch_children_names(child_filter_str, matching_filter) + + def parent(self) -> Optional[str]: _parents = self.parents() if len(_parents) > 1 or len(_parents) == 0: return None diff --git a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py index 3743acabb9c0bfcafe820e3c9c1a279082f3805c..dd78812fad20393f69512a1a2608a54b31409797 100644 --- a/tangostationcontrol/tangostationcontrol/devices/tilebeam.py +++ b/tangostationcontrol/tangostationcontrol/devices/tilebeam.py @@ -1,5 +1,5 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 """ TileBeam Device Server for LOFAR2.0 @@ -12,15 +12,15 @@ import numpy # Additional import from tangostationcontrol.beam.delays import Delays -from tangostationcontrol.common.constants import N_xyz, N_elements, N_pol +from tangostationcontrol.common.constants import N_xyz, N_elements, N_pol, MAX_ANTENNA from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import ( device_logging_to_python, log_exceptions, ) -from tangostationcontrol.common.proxy import create_device_proxy -from tangostationcontrol.devices.interfaces.beam_device import BeamDevice from tangostationcontrol.devices.device_decorators import TimeIt +from tangostationcontrol.devices.interfaces.beam_device import BeamDevice +from tangostationcontrol.devices.types import DeviceTypes logger = logging.getLogger() @@ -60,29 +60,26 @@ class TileBeam(BeamDevice): def configure_for_initialise(self): parent = self.control.parent() - # TODO(L2SS-1364): Ensure tilebeam does not need direct access to antennafield - self.antennafield_proxy = create_device_proxy(parent) - # We maintain the same number of tiles as the AntennaField self._nr_tiles = self.control.read_parent_attribute(parent, "nr_antennas_R") super().configure_for_initialise(self._nr_tiles) # Retrieve positions from AntennaField device - Antenna_Reference_itrf = self.control.read_parent_attribute( + antenna_reference_itrf = self.control.read_parent_attribute( parent, "Antenna_Reference_itrf_R" ) - HBAT_antenna_itrf_offsets = self.control.read_parent_attribute( + hbat_antenna_itrf_offsets = self.control.read_parent_attribute( parent, "HBAT_antenna_itrf_offsets_R" ).reshape(self._nr_tiles, N_elements, N_xyz) # a delay calculator for each tile self.HBAT_delay_calculators = [ - Delays(reference_itrf) for reference_itrf in Antenna_Reference_itrf + Delays(reference_itrf) for reference_itrf in antenna_reference_itrf ] # absolute positions of each antenna element self.HBAT_antenna_positions = [ - Antenna_Reference_itrf[tile] + HBAT_antenna_itrf_offsets[tile] + antenna_reference_itrf[tile] + hbat_antenna_itrf_offsets[tile] for tile in range(self._nr_tiles) ] @@ -124,9 +121,42 @@ class TileBeam(BeamDevice): delays = self._delays(pointing_direction, timestamp) # Convert delays into beam weights - delays = delays.flatten() - # TODO(L2SS-1364): Ensure tilebeam does not need to execute antennafield command - bf_delay_steps = self.antennafield_proxy.calculate_HBAT_bf_delay_steps(delays) + delays = delays.flatten().reshape(self._nr_tiles, N_elements) + + result_values = numpy.zeros( + (self._nr_tiles, N_elements * N_pol), dtype=numpy.int64 + ) + control_mapping = numpy.reshape( + self.control.read_parent_attribute( + self.control.parent(), "Control_to_RECV_mapping_R" + ), + (-1, N_pol), + ) + + recv_proxies = [] + for recv in self.control.branch_children_names(DeviceTypes.RECV): + recv_proxies.append(self.control.branch_child(recv)) + + for recv_idx, recv_proxy in enumerate(recv_proxies): + # collect all delays for this recv_proxy + recv_result_indices = numpy.where(control_mapping[:, 0] == (recv_idx + 1)) + recv_delays = delays[recv_result_indices] + + if not recv_result_indices: + # no RCUs are actually used from this recv_proxy + continue + + # convert them into delay steps + flatten_delay_steps = numpy.array( + recv_proxy.calculate_HBAT_bf_delay_steps(recv_delays.flatten()), + dtype=numpy.int64, + ) + delay_steps = numpy.reshape(flatten_delay_steps, (-1, N_elements * N_pol)) + + # write back into same positions we collected them from + result_values[recv_result_indices] = delay_steps + + bf_delay_steps = result_values.flatten() return bf_delay_steps @@ -139,12 +169,42 @@ class TileBeam(BeamDevice): ): parent = self.control.parent() - # Write weights to RECV through the AntennaToRecvMapper - # TODO(L2SS-1364): Ensure tilebeam does not need to write antennafield attribute - self.antennafield_proxy.HBAT_bf_delay_steps_RW = bf_delay_steps.reshape( - self._nr_tiles, N_elements * N_pol + control_mapping = numpy.reshape( + self.control.read_parent_attribute(parent, "Control_to_RECV_mapping_R"), + (-1, N_pol), ) + recv_proxies = [] + for recv in self.control.branch_children_names(DeviceTypes.RECV): + recv_proxies.append(self.control.branch_child(recv)) + + mapped_values = [] + + for _ in range(len(recv_proxies)): + mapped_values.append(numpy.full((MAX_ANTENNA, N_elements * N_pol), None)) + + mapped_values = numpy.array(mapped_values) + + bf_delay_steps2 = bf_delay_steps.reshape(self._nr_tiles, N_elements * N_pol) + + for idx, mapping in enumerate(control_mapping): + recv = mapping[0] + rcu = mapping[1] + if recv > 0: + mapped_values[recv - 1, rcu] = bf_delay_steps2[idx] + + mapped_values = mapped_values.reshape( + (len(recv_proxies), MAX_ANTENNA, N_elements * N_pol) + ) + + for idx, recv_proxy in enumerate(recv_proxies): + self.atomic_read_modify_write_attribute( + mapped_values[idx], + recv_proxy, + "HBAT_bf_delay_steps_RW", + cast_type=numpy.int64, + ) + # Record where we now point to, now that we've updated the weights. # Only the entries within the mask have been updated mask = self.control.read_parent_attribute(parent, "ANT_mask_RW")