diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 18b0dd91fe27ab8b63ec625e8121553689513255..64e26f5b2b5659db05a6cd975e3d2ac30be5ef64 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: git.astron.nl:5000/lofar2.0/tango/tango-itango:9.3.7 +image: git.astron.nl:5000/lofar2.0/tango/lofar-device-base:latest variables: GIT_SUBMODULE_STRATEGY: recursive PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" @@ -42,6 +42,11 @@ stages: - . bootstrap/etc/lofar20rc.sh || true ## Allow docker image script to execute # - chmod u+x $CI_PROJECT_DIR/sbin/tag_and_push_docker_image.sh +# This suffers from only refs changes not working as expected: +# https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55012 +# Therefore we have to add `only: refs: - merge_requests` to all jobs that are +# only supposed to run on merge requests with file changes. However, +# two pipelines will spawn instead of one of which one tagged with 'detached`. .base_docker_images_except: extends: .base_docker_images except: @@ -62,6 +67,8 @@ docker_store_images_master_tag: docker_store_images_changes: extends: .base_docker_store_images only: + refs: + - merge_requests changes: - docker-compose/.env except: @@ -101,6 +108,8 @@ docker_build_image_all: docker_build_image_elk: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/elk.yml - docker-compose/elk/* @@ -112,6 +121,8 @@ docker_build_image_elk: docker_build_image_lofar_device_base: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/lofar-device-base.yml - docker-compose/lofar-device-base/* @@ -121,27 +132,30 @@ docker_build_image_lofar_device_base: docker_build_image_prometheus: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/prometheus.yml - docker-compose/prometheus/* - except: - refs: - - tags - - master 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 prometheus $tag docker_build_image_itango: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/itango.yml + - docker-compose/itango/* 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 itango $tag docker_build_image_grafana: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/grafana.yml - docker-compose/grafana/* @@ -151,6 +165,8 @@ docker_build_image_grafana: docker_build_image_jupyter: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/jupyter.yml - docker-compose/jupyter/* @@ -160,6 +176,8 @@ docker_build_image_jupyter: 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/* @@ -169,6 +187,8 @@ docker_build_image_apsct_sim: 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/* @@ -178,6 +198,8 @@ docker_build_image_apspu_sim: docker_build_image_recv_sim: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/recv-sim.yml - docker-compose/pypcc-sim-base/* @@ -187,6 +209,8 @@ docker_build_image_recv_sim: docker_build_image_sdptr_sim: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/sdptr-sim.yml - docker-compose/sdptr-sim/* @@ -196,6 +220,8 @@ docker_build_image_sdptr_sim: 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/* @@ -205,6 +231,8 @@ docker_build_image_unb2_sim: docker_build_image_device_apsct: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-aspct.yml - docker-compose/lofar-device-base/* @@ -214,6 +242,8 @@ docker_build_image_device_apsct: docker_build_image_device_apspu: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-apspu.yml - docker-compose/lofar-device-base/* @@ -223,6 +253,8 @@ docker_build_image_device_apspu: docker_build_image_device_boot: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-boot.yml - docker-compose/lofar-device-base/* @@ -232,6 +264,8 @@ docker_build_image_device_boot: docker_build_image_device_docker: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-docker.yml - docker-compose/lofar-device-base/* @@ -241,6 +275,8 @@ docker_build_image_device_docker: docker_build_image_device_ovservation_control: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-observation_control.yml - docker-compose/lofar-device-base/* @@ -250,6 +286,8 @@ docker_build_image_device_ovservation_control: docker_build_image_device_recv: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-recv.yml - docker-compose/lofar-device-base/* @@ -259,6 +297,8 @@ docker_build_image_device_recv: docker_build_image_device_sdp: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-sdp.yml - docker-compose/lofar-device-base/* @@ -268,6 +308,8 @@ docker_build_image_device_sdp: docker_build_image_device_sst: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-sst.yml - docker-compose/lofar-device-base/* @@ -277,6 +319,8 @@ docker_build_image_device_sst: docker_build_image_device_unb2: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-unb2.yml - docker-compose/lofar-device-base/* @@ -286,6 +330,8 @@ docker_build_image_device_unb2: docker_build_image_device_xst: extends: .base_docker_images_except only: + refs: + - merge_requests changes: - docker-compose/device-xst.yml - docker-compose/lofar-device-base/* diff --git a/docker-compose/itango/Dockerfile b/docker-compose/itango/Dockerfile index 044ae985c4d1d45c26a6c00f6c3a455b93187cff..448afacfe687f6b75b163c159a779ff14ec07ee3 100644 --- a/docker-compose/itango/Dockerfile +++ b/docker-compose/itango/Dockerfile @@ -4,7 +4,8 @@ FROM ${SOURCE_IMAGE} RUN sudo apt-get -y update && \ sudo apt-get -y upgrade && \ sudo apt-get -y install apt-file apt-transport-https apt-utils aptitude && \ - sudo aptitude -y install htop iftop iproute2 mc most net-tools tcpdump telnet tmux traceroute vim xterm git && \ + sudo apt-get -y install htop iftop iproute2 mc most net-tools tcpdump && \ + sudo apt-get -y install telnet tmux traceroute vim xterm git && \ sudo aptitude clean && \ sudo aptitude autoclean diff --git a/tangostationcontrol/tangostationcontrol/devices/abstract_device.py b/tangostationcontrol/tangostationcontrol/devices/abstract_device.py deleted file mode 100644 index c3c6aea23d0af39f80dd733efb4c911847d93635..0000000000000000000000000000000000000000 --- a/tangostationcontrol/tangostationcontrol/devices/abstract_device.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of the XXX project -# -# -# -# Distributed under the terms of the APACHE license. -# See LICENSE.txt for more info. - -"""Abstract Device Meta for LOFAR2.0 - -""" - -import logging - -from tango.server import DeviceMeta - -logger = logging.getLogger() - - -# TODO(Corne): Fix combining metaclasses by iterating over their variables and -# methods. https://support.astron.nl/jira/browse/L2SS-551 -# class AbstractDeviceMetas(DeviceMeta, ABCMeta): -class AbstractDeviceMetas(DeviceMeta): - """Collects meta classes to allow lofar_device to be both a Device and an ABC. """ - - def __new__(mcs, name, bases, namespace, **kwargs): - cls = DeviceMeta.__new__(mcs, name, bases, namespace, **kwargs) - # temp_cls = ABCMeta.__new__(mcs, name, bases, namespace) - # setattr(cls, '__abstractmethods__', temp_cls.__abstractmethods__) - # setattr(cls, '_abc_impl', temp_cls._abc_impl) - return cls diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index a44a557a3fbb875d7f623ce05ed039576cdc69f7..3330d3a3b2be83035d9915ccb5b421e7912a995b 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -8,18 +8,17 @@ """ import numpy +import datetime +from tango.server import attribute, command, device_property +from tango import AttrWriteType, DebugIt, DevState, DeviceProxy, DevVarStringArray, DevVarDoubleArray -from tango.server import attribute -from tango.server import command -from tango import AttrWriteType -from tango import DevState -from tango import DebugIt - +# Additional import from tangostationcontrol.common.entrypoint import entry -from tangostationcontrol.devices.device_decorators import only_in_states from tangostationcontrol.devices.lofar_device import lofar_device from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions from tangostationcontrol.common.measures import get_measures_directory, use_measures_directory, download_measures, restart_python, get_available_measures_directories +from tangostationcontrol.beam.delays import delay_calculator +from tangostationcontrol.devices.device_decorators import * import logging logger = logging.getLogger() @@ -30,32 +29,35 @@ __all__ = ["Beam", "main"] @device_logging_to_python() class Beam(lofar_device): - _hbat_pointing_direction = numpy.zeros(96) - _hbat_pointing_epoch = numpy.zeros(96) - - @property - def hbat_pointing_direction(self): - return tuple(self._hbat_pointing_direction) - - @property - def hbat_pointing_epoch(self): - return tuple(self._hbat_pointing_epoch) + _hbat_pointing_direction = numpy.zeros((96,3), dtype=numpy.str) + _hbat_pointing_timestamp = numpy.zeros(96, dtype=numpy.double) # ----------------- # Device Properties # ----------------- + reference_itrf = device_property( + dtype='DevVarFloatArray', + mandatory=False, + default_value = numpy.tile(numpy.array([3826577.066, 461022.948, 5064892.786]),(96,1)) # CS002LBA, in ITRF2005 timestamp 2012.5 + ) + + antenna_itrf = device_property( + dtype='DevVarFloatArray', + mandatory=False, + default_value = numpy.tile(numpy.array([3826923.546, 460915.441, 5064643.489]),(96,16,1)) # CS001LBA, in ITRF2005 timestamp 2012.5 + ) # ---------- # Attributes # ---------- HBAT_pointing_direction_R = attribute(access=AttrWriteType.READ, - dtype=(numpy.double,), max_dim_x=96, - fget=lambda self: self.hbat_pointing_direction) + dtype=((numpy.str,),), max_dim_x=3, max_dim_y=96, + fget=lambda self: self._hbat_pointing_direction) - HBAT_pointing_epoch_R = attribute(access=AttrWriteType.READ, + HBAT_pointing_timestamp_R = attribute(access=AttrWriteType.READ, dtype=(numpy.double,), max_dim_x=96, - fget=lambda self: self.hbat_pointing_epoch) + fget=lambda self: self._hbat_pointing_timestamp) # Directory where the casacore measures that we use, reside. We configure ~/.casarc to # use the symlink /opt/IERS/current, which we switch to the actual set of files to use. @@ -67,32 +69,72 @@ class Beam(lofar_device): # -------- # overloaded functions # -------- + @log_exceptions() + def configure_for_initialise(self): + super().configure_for_initialise() + + # Set a reference of RECV device + self.recv_proxy = DeviceProxy("STAT/RECV/1") # -------- - # Commands + # internal functions # -------- - @DebugIt() - @command(dtype_in=(numpy.double,), dtype_out=int) - @only_in_states([DevState.STANDBY, DevState.ON]) - def set_direction_pointings(self, new_points: numpy.array): - if new_points.size != 96: - return -1 - - self._hbat_pointing_direction = new_points - - return 0 + def _HBAT_delays(self, pointing_direction: numpy.array, timestamp: datetime.datetime = datetime.datetime.now()): + """ + Calculate the delays (in seconds) based on the pointing list and the timestamp + TBD: antenna and reference positions will be retrieved from RECV and not stored as BEAM device properties + """ + + delays = numpy.zeros((96,16), dtype=numpy.float64) + + for tile in range(96): + # initialise delay calculator + d = delay_calculator(self.reference_itrf[tile]) + d.set_measure_time(timestamp) + + # calculate the delays based on the set reference position, the set time and now the set direction and antenna positions + delays[tile] = d.convert(pointing_direction[tile], self.antenna_itrf[tile]) + + return delays + + @staticmethod + def _calculate_HBAT_bf_delays(delays: numpy.ndarray): + """ + Helper function that converts a signal path delay (in seconds) to an analog beam weight, + which is a value per tile per dipole per polarisation. + """ + # Duplicate delay values per polarisation + polarised_delays = numpy.tile(delays, 2) # output dims -> 96x32 + + # Divide by 0.5ns and round + HBAT_bf_delays = numpy.array(polarised_delays / 0.5e-09, dtype=numpy.int64) + + return HBAT_bf_delays + + def _HBAT_set_pointing(self, pointing_direction: numpy.array, timestamp: datetime.datetime = datetime.datetime.now()): + """ + Uploads beam weights based on a given pointing direction 2D array (96 tiles x 3 parameters) + """ + # Retrieve delays from casacore + delays = self._HBAT_delays(pointing_direction, timestamp) + # Convert delays into beam weights + HBAT_bf_delays = self._calculate_HBAT_bf_delays(delays) + + # Write weights to RECV + self.recv_proxy.HBAT_BF_delays_RW = HBAT_bf_delays + + # Record where we now point to, now that we've updated the weights. + # Only the entries within the mask have been updated + mask = self.recv_proxy.Ant_mask_RW.flatten() + for rcu in range(96): + if mask[rcu]: + self._hbat_pointing_direction[rcu] = pointing_direction[rcu] + self._hbat_pointing_timestamp[rcu] = timestamp.timestamp() - @DebugIt() - @command(dtype_in=(numpy.double,), dtype_out=int) - @only_in_states([DevState.STANDBY, DevState.ON]) - def set_direction_epochs(self, new_points: numpy.array): - if new_points.size != 96: - return -1 - - self._hbat_pointing_epoch = new_points - - return 0 + # -------- + # Commands + # -------- @command(dtype_out=str, doc_out="Name of newly installed measures directory") @DebugIt() @@ -124,6 +166,32 @@ class Beam(lofar_device): # the new tables logger.warning("Restarting device to activate new measures tables") restart_python() + + @command(dtype_in=DevVarStringArray, dtype_out=DevVarDoubleArray) + @DebugIt() + @log_exceptions() + @only_in_states([DevState.ON]) + def HBAT_delays(self, pointing_direction: numpy.array, timestamp: datetime.datetime = datetime.datetime.now()): + """ + Calculate the delays (in seconds) based on the pointing list and the timestamp + TBD: antenna and reference positions will be retrieved from RECV and not stored as BEAM device properties + """ + pointing_direction = numpy.array(pointing_direction).reshape(96,3) + + delays = self._HBAT_delays(pointing_direction, timestamp) + + return delays.flatten() + + @command(dtype_in=DevVarStringArray) + @DebugIt() + @only_in_states([DevState.ON]) + def HBAT_set_pointing(self, pointing_direction: list, timestamp: datetime.datetime = datetime.datetime.now()): + """ + Uploads beam weights based on a given pointing direction 2D array (96 tiles x 3 parameters) + """ + pointing_direction = numpy.array(pointing_direction).reshape(96,3) + + self._HBAT_set_pointing(pointing_direction, timestamp) # ---------- # Run server diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py index 690f65104b1ada278b7c09447df47de1817941ce..6fed370c4e714813b1a7ce6768cc93209cff1c5e 100644 --- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py @@ -11,10 +11,8 @@ """ -from abc import abstractmethod - # PyTango imports -from tango.server import Device, command, attribute +from tango.server import attribute, command, Device, DeviceMeta from tango import AttrWriteType, DevState, DebugIt, Attribute, DeviceProxy import time import math @@ -23,7 +21,6 @@ import math from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper from tangostationcontrol.common.lofar_logging import log_exceptions from tangostationcontrol.common.lofar_version import get_version -from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error @@ -32,7 +29,8 @@ __all__ = ["lofar_device"] import logging logger = logging.getLogger() -class lofar_device(Device, metaclass=AbstractDeviceMetas): + +class lofar_device(Device, metaclass=DeviceMeta): """ **Properties:** @@ -217,14 +215,12 @@ class lofar_device(Device, metaclass=AbstractDeviceMetas): def configure_for_fault(self): pass - @abstractmethod def configure_for_off(self): pass def configure_for_on(self): pass - @abstractmethod def configure_for_initialise(self): pass diff --git a/tangostationcontrol/tangostationcontrol/devices/opcua_device.py b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py index 6da1fdf21d635e4f32cf848cb4c8c81287b896d6..eb2e508c35cf6923fb9d121f729465a4eca621e3 100644 --- a/tangostationcontrol/tangostationcontrol/devices/opcua_device.py +++ b/tangostationcontrol/tangostationcontrol/devices/opcua_device.py @@ -17,14 +17,15 @@ import numpy import asyncio # Additional import -from tangostationcontrol.common.lofar_logging import log_exceptions +from tangostationcontrol.common.lofar_logging import log_exceptions from tangostationcontrol.clients.opcua_client import OPCUAConnection from tangostationcontrol.devices.lofar_device import lofar_device import logging logger = logging.getLogger() -__all__ = ["opcua_device", "main"] +__all__ = ["opcua_device"] + class opcua_device(lofar_device): """ diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py index ae8b663dc61b4eafa17a73acaa4efc548c3d4d95..21286acd44b5a83a8ac3fb904f55b2024bbf03fe 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/statistics.py @@ -11,8 +11,6 @@ """ -from abc import abstractmethod - # PyTango imports from tango.server import device_property @@ -32,15 +30,13 @@ import numpy __all__ = ["Statistics"] -# TODO(Corne): Make Statistics use ABCMeta again when L2SS-551 is fixed -# https://support.astron.nl/jira/browse/L2SS-551 + class Statistics(opcua_device): # In derived classes, set this to a subclass of StatisticsCollector @property - @abstractmethod def STATISTICS_COLLECTOR_CLASS(self): - pass + raise NotImplementedError # ----------------- # Device Properties diff --git a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py index adef57e3144e297b273ff5b33e77c9fde513ee82..c0b6f7a98894b7c7d660828231935b63c5b0f5da 100644 --- a/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py +++ b/tangostationcontrol/tangostationcontrol/devices/sdp/xst.py @@ -28,6 +28,7 @@ import numpy __all__ = ["XST", "main"] + class XST(Statistics): STATISTICS_COLLECTOR_CLASS = XSTCollector diff --git a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_beam.py b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_beam.py index b6be958cfde3b9f236947e9b1b76195c844bad40..8e1f2091f41504358619d793aa98ba52761f5e03 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_beam.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/devices/test_device_beam.py @@ -7,9 +7,36 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. +import numpy +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy + from .base import AbstractTestBases class TestDeviceBeam(AbstractTestBases.TestDeviceBase): def setUp(self): super().setUp("STAT/Beam/1") + + def test_write_HBAT_delays(self): + """ Test whether the delay values are correctly saved into the relative RECV attribute""" + + self.proxy.initialise() + self.proxy.on() + + # setup RECV as well + recv_proxy = TestDeviceProxy("STAT/RECV/1") + recv_proxy.off() + recv_proxy.initialise() + recv_proxy.on() + + # Verify attribute is present (all zeros if never used before) + HBAT_delays_r1 = numpy.array(recv_proxy.read_attribute('HBAT_BF_delays_RW').value) + self.assertIsNotNone(HBAT_delays_r1) + + # Verify writing operation does not lead to errors + self.proxy.HBAT_set_pointing(numpy.array([["J2000","0deg","0deg"]] * 96).flatten()) # write values to RECV + HBAT_delays_r2 = numpy.array(recv_proxy.read_attribute('HBAT_BF_delays_RW').value) + self.assertIsNotNone(HBAT_delays_r2) + + # Verify delays changed (to be discussed) + #self.assertFalse((HBAT_delays_r1==HBAT_delays_r2).all()) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py deleted file mode 100644 index 742fa9e405d112444239ae30284e51b8452416e8..0000000000000000000000000000000000000000 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_abstract_device.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- 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. - -import abc -from unittest import mock - -from tango import server -from tango.server import attribute - -from tangostationcontrol.devices.abstract_device import AbstractDeviceMetas - -from tangostationcontrol.test import base - - -class TestAbstractDevice(base.TestCase): - - class AbstractExample(object, metaclass=abc.ABCMeta): - """A pure abc.ABCMeta metaclass with an abstract method - - This is an abstract class that inherits object with the abc.ABCMeta as - metaclass - """ - - @abc.abstractmethod - def example_method(self): - raise NotImplementedError - - class TestHardwareDevice(server.Device, metaclass=AbstractDeviceMetas): - """This is your overarching abstract class with a combined metaclass - - Device is an object with DeviceMeta as metaclass - We use HardwareDeviceMetas as metaclass - - Our metaclass contract is now fulfilled. - """ - - @attribute(dtype=float) - def call_example_method(self): - return self.example_method() - - @abc.abstractmethod - def example_method(self): - raise NotImplementedError - - class ConcreteHardwareDevice(TestHardwareDevice): - - def example_method(self): - return 12 - - def setUp(self): - super(TestAbstractDevice, self).setUp() - - # def test_instance_tango(self): - # - # try: - # with DeviceTestContext(self.TestHardwareDevice, process=True) as proxy: - # # Calling this method raises the NotImplementedError exception - # proxy.call_example_method() - # except Exception as e: - # self.assertIsInstance(e, RuntimeError) - # - # with DeviceTestContext(self.ConcreteHardwareDevice, process=True) as proxy: - # self.assertEqual(12, proxy.call_example_method) - - @mock.patch.object(server, 'get_worker') - @mock.patch.object(server, 'LatestDeviceImpl') - def test_instance_error(self, m_worker, m_implement): - # Creating this object should raise a type error but it does not - # combining metaclasses in this way does not have the desired result. - # This is a known limitation of this approach - m_device = self.TestHardwareDevice(mock.Mock(), mock.Mock()) - - # Raising the NotImplementedError works as expected, however. - self.assertRaises(NotImplementedError, m_device.example_method) - - # Creating this object of a class that has a pure metaclass does raise - # the expected error. - self.assertRaises(TypeError, self.AbstractExample) - - @mock.patch.object(server, 'get_worker') - @mock.patch.object(server, 'LatestDeviceImpl') - def test_isinstance(self, m_worker, m_implement): - m_device = self.TestHardwareDevice(mock.Mock(), mock.Mock()) - - self.assertFalse(isinstance(m_device, AbstractDeviceMetas)) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py index 107f95293f587e632f018be68a5cf07764bc49b7..06a6d858114b30a1dc1f1f6ba7314aadf1a2c5c3 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_beam_device.py @@ -7,13 +7,12 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -import numpy - from tango import DevState from tango.test_context import DeviceTestContext -from tangostationcontrol.devices import beam +from tangostationcontrol.devices import beam, lofar_device +import numpy import mock from tangostationcontrol.test import base @@ -24,96 +23,52 @@ class TestBeamDevice(base.TestCase): def setUp(self): super(TestBeamDevice, self).setUp() - # lofar_device init_device will launch a DeviceProxy not captured by - # the TestDeviceContext making it fail. - - # Patch init_device and force match spec - init_patcher = mock.patch.object( - beam.Beam, 'init_device', spec=beam.Beam.init_device) - self.m_init = init_patcher.start() - self.addCleanup(init_patcher.stop) - + # Patch DeviceProxy to allow making the proxies during initialisation + # that we otherwise avoid using + for device in [beam, lofar_device]: + proxy_patcher = mock.patch.object( + device, 'DeviceProxy') + proxy_patcher.start() + self.addCleanup(proxy_patcher.stop) + def test_get_pointing_directions(self): """Verify can read pointings attribute and length matches without err""" with DeviceTestContext(beam.Beam, process=True) as proxy: self.assertEqual(96, len(proxy.read_attribute( "HBAT_pointing_direction_R").value)) - def test_get_pointing_epochs(self): - """Verify can read epochs attribute and length matches without err""" + def test_get_pointing_timestamps(self): + """Verify can read timestamps attribute and length matches without err""" with DeviceTestContext(beam.Beam, process=True) as proxy: self.assertEqual(96, len(proxy.read_attribute( - "HBAT_pointing_epoch_R").value)) - - def test_set_pointing_direction(self): - """Verify can set pointings attribute without error""" - - with DeviceTestContext(beam.Beam, process=True) as proxy: - proxy.init() - proxy.Initialise() - self.assertEqual(DevState.STANDBY, proxy.state()) - self.assertEqual(0, proxy.set_direction_pointings(numpy.zeros(96))) - - def test_set_pointing_epochs(self): - """Verify can set epochs attribute without error""" + "HBAT_pointing_timestamp_R").value)) + def test_HBAT_delays_dims(self): + """Verify HBAT delays are retrieved with correct dimensions""" with DeviceTestContext(beam.Beam, process=True) as proxy: proxy.init() proxy.Initialise() self.assertEqual(DevState.STANDBY, proxy.state()) - self.assertEqual(0, proxy.set_direction_epochs(numpy.zeros(96))) - - def pointing(self, attribute: str, lambd): - data = numpy.arange(0, 96) - + proxy.set_defaults() + proxy.on() + self.assertEqual(DevState.ON, proxy.state()) + + # verify HBAT_delays method returns the correct dimensions + HBAT_delays = proxy.HBAT_delays(numpy.array([["J2000","0deg","0deg"]] * 96).flatten()) + self.assertEqual((96*16,), HBAT_delays.shape) + + def test_HBAT_delays_calculations(self): + """Verify the calculations from delays to weights""" with DeviceTestContext(beam.Beam, process=True) as proxy: proxy.init() proxy.Initialise() self.assertEqual(DevState.STANDBY, proxy.state()) - - # Evaluate default all zeros are present using numpy array compare - compare_obj = numpy.zeros(96) == proxy.read_attribute( - attribute).value - self.assertTrue(compare_obj.all()) - - # Set direction pointings to range of incrementing values - self.assertEqual(0, lambd(proxy, data)) - - # Verify attribute has been updated with correct data - compare_obj = data == proxy.read_attribute(attribute).value - self.assertTrue(compare_obj.all()) - - def test_direction_pointing(self): - """Set and Get test with actual values for pointing attribute""" - - self.pointing("HBAT_pointing_direction_R", lambda x, y: - x.set_direction_pointings(y)) - - def test_direction_epochs(self): - """Set and Get test with actual values for pointing attribute""" - - self.pointing("HBAT_pointing_epoch_R", lambda x, y: - x.set_direction_epochs(y)) - - def test_pointing_invalid(self): - """Test that set pointings command refuses invalid lengths""" - - with DeviceTestContext(beam.Beam, process=True) as proxy: - proxy.init() - proxy.Initialise() - self.assertEqual(DevState.STANDBY, proxy.state()) - - # should return error due to invalid length - self.assertEqual(-1, proxy.set_direction_pointings(numpy.zeros(55))) - - def test_epoch_invalid(self): - """Test that set epochs command refuses invalid lengths""" - - with DeviceTestContext(beam.Beam, process=True) as proxy: - proxy.init() - proxy.Initialise() - self.assertEqual(DevState.STANDBY, proxy.state()) - - # should return error due to invalid length - self.assertEqual(-1, proxy.set_direction_epochs(numpy.zeros(55))) + proxy.set_defaults() + proxy.on() + self.assertEqual(DevState.ON, proxy.state()) + + # verify if values are actually transformed + HBAT_delays = proxy.HBAT_delays(numpy.array([["J2000","0deg","0deg"]] * 96).flatten()) + HBAT_bf_delays = beam.Beam._calculate_HBAT_bf_delays(HBAT_delays) + self.assertNotEqual(HBAT_delays, HBAT_bf_delays) diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_device.py new file mode 100644 index 0000000000000000000000000000000000000000..29c2462fe95047bc6364acd0d4c6a03c945801f2 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_statistics_device.py @@ -0,0 +1,53 @@ +# -*- 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. + +import mock + +from tango import server +from tango.test_context import DeviceTestContext + +from tangostationcontrol.test import base + + +class TestStatisticsDevice(base.TestCase): + + def setUp(self): + super(TestStatisticsDevice, self).setUp() + + def test_python_bug(self): + """Python is bugged and super() calls in ctypes fail + + This is the same type of issues that prevents combining metaclasses + DeviceMeta and ABCMeta + + https://github.com/waveform80/picamera/issues/355 + https://bugs.python.org/issue29270 + """ + import ctypes as ct + + try: + class TestSubclass(ct.c_uint32): + def __repr__(self): + return super().__repr__() + except Exception as e: + self.assertIsInstance(e, TypeError) + + @mock.patch.object(server, 'get_worker') + def test_instance_statistics(self, m_worker): + """Test that we can import and create a statistics device + + The import of Statistics fails when both ABCMeta and DeviceMeta are + combined. Leave this test to prevent regressions when reattempting our + combined metaclass. + """ + + from tangostationcontrol.devices.sdp.statistics import Statistics + + with DeviceTestContext(Statistics, process=True) as proxy: + self.assertTrue(proxy.ping() > 0) diff --git a/tangostationcontrol/tox.ini b/tangostationcontrol/tox.ini index fc0876dd08ba604c5d112e541ef14f5ea04c96c4..fc688b430a9cdaa3805f5340e22826003e553080 100644 --- a/tangostationcontrol/tox.ini +++ b/tangostationcontrol/tox.ini @@ -7,6 +7,7 @@ skipsdist = True usedevelop = True sitepackages = True install_command = pip3 install {opts} {packages} +passenv = HOME setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning