From 30ce13620bda44295da5c38806020c8652f7367d Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Wed, 4 Sep 2024 13:17:38 +0000 Subject: [PATCH] Resolve L2SS-1955 & L2SS-1902 "Remove psoc" --- .editorconfig | 3 + .gitlab-ci.yml | 10 +- CDB/stations/DTS.json | 1 - CDB/stations/common.json | 48 +- CDB/stations/cs.json | 1 - CDB/stations/rs.json | 1 - CDB/stations/simulators_ConfigDb.json | 13 - CDB/stations/testenv_cs001.json | 13 - CDB/stations/testenv_rs307.json | 875 ++++++++++++++++++ README.md | 12 +- .../startup/02-devices.py | 1 - infra/dev/all.hcl | 4 + infra/dev/services.hcl | 4 + infra/dev/services/variables.hcl | 4 + infra/dev/tango.hcl | 4 + infra/dev/tango/tango.hcl | 18 +- infra/dev/tango/variables.hcl | 4 + infra/env/common.yaml | 1 - infra/jobs/station/Makefile | 7 +- sbin/prepare_dev_env.sh | 1 + sbin/run_integration_test.sh | 32 +- .../docs/source/devices/overview.rst | 8 - .../docs/source/devices/psoc.rst | 6 - tangostationcontrol/docs/source/index.rst | 1 - .../integration_test/common/__init__.py | 0 .../common/base_device_classes/__init__.py | 0 .../power_hierarchy_tests.py | 378 ++++++++ .../common/device_beamlet_tests.py | 136 +++ .../common/device_bst_tests.py | 27 + .../common/device_digitalbeam_tests.py | 252 +++++ .../common/device_hba_tests.py | 410 ++++++++ .../device_observation_control_tests.py | 308 ++++++ .../common/device_sdp_tests.py | 18 + .../common/device_sdpfirmware_tests.py | 28 + .../common/device_sst_tests.py | 73 ++ .../common/device_tilebeam_tests.py | 280 ++++++ .../common/device_xst_tests.py | 18 + .../configDB/LOFAR_ConfigDb.json | 25 - .../configDB/simulators_ConfigDb.json | 13 - .../devices/antennafield/test_device_hba.py | 396 +------- .../test_power_hierarchy.py | 357 +------ .../default/devices/test_device_beamlet.py | 125 +-- .../default/devices/test_device_bst.py | 19 +- .../devices/test_device_digitalbeam.py | 243 +---- .../default/devices/test_device_metadata.py | 1 - .../test_device_observation_control.py | 337 +------ .../devices/test_device_observation_field.py | 4 +- .../default/devices/test_device_psoc.py | 11 - .../default/devices/test_device_sdp.py | 28 +- .../devices/test_device_sdpfirmware.py | 32 +- .../default/devices/test_device_sst.py | 65 +- .../default/devices/test_device_tilebeam.py | 264 +----- .../default/devices/test_device_unb2.py | 2 +- .../default/devices/test_device_xst.py | 20 +- .../devices/test_observation_client.py | 6 +- .../remote_station/__init__.py | 0 .../remote_station/antennafield/__init__.py | 2 + .../antennafield/test_device_hba.py | 20 + .../base_device_classes/__init__.py | 0 .../test_power_hierarchy.py | 21 + .../remote_station/test_device_beamlet.py | 11 + .../remote_station/test_device_bst.py | 13 + .../remote_station/test_device_digitalbeam.py | 23 + .../test_device_observation_control.py | 37 + .../remote_station/test_device_sdp.py | 16 + .../remote_station/test_device_sdpfirmware.py | 15 + .../remote_station/test_device_sst.py | 13 + .../remote_station/test_device_tilebeam.py | 40 + .../remote_station/test_device_xst.py | 13 + .../clients/snmp/attribute_classes.py | 80 -- .../tangostationcontrol/common/constants.py | 3 + .../tangostationcontrol/devices/__init__.py | 2 - .../base_device_classes/power_hierarchy.py | 6 - .../tangostationcontrol/devices/psoc.py | 180 ---- .../tangostationcontrol/devices/types.py | 1 - .../test/dummy_observation_settings.py | 12 +- .../test/clients/test_snmp_client.py | 37 - .../test_hierarchy_device.py | 10 +- .../test/devices/test_psoc_device.py | 30 - .../test/observation/test_observation.py | 22 +- .../test_observation_controller.py | 8 +- .../observation/test_observation_field.py | 16 +- 82 files changed, 3318 insertions(+), 2261 deletions(-) create mode 100644 CDB/stations/testenv_rs307.json delete mode 100644 tangostationcontrol/docs/source/devices/psoc.rst create mode 100644 tangostationcontrol/integration_test/common/__init__.py create mode 100644 tangostationcontrol/integration_test/common/base_device_classes/__init__.py create mode 100644 tangostationcontrol/integration_test/common/base_device_classes/power_hierarchy_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_beamlet_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_bst_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_digitalbeam_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_hba_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_observation_control_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_sdp_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_sdpfirmware_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_sst_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_tilebeam_tests.py create mode 100644 tangostationcontrol/integration_test/common/device_xst_tests.py delete mode 100644 tangostationcontrol/integration_test/default/devices/test_device_psoc.py create mode 100644 tangostationcontrol/integration_test/remote_station/__init__.py create mode 100644 tangostationcontrol/integration_test/remote_station/antennafield/__init__.py create mode 100644 tangostationcontrol/integration_test/remote_station/antennafield/test_device_hba.py create mode 100644 tangostationcontrol/integration_test/remote_station/base_device_classes/__init__.py create mode 100644 tangostationcontrol/integration_test/remote_station/base_device_classes/test_power_hierarchy.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_beamlet.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_bst.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_digitalbeam.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_observation_control.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_sdp.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_sdpfirmware.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_sst.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_tilebeam.py create mode 100644 tangostationcontrol/integration_test/remote_station/test_device_xst.py delete mode 100644 tangostationcontrol/tangostationcontrol/devices/psoc.py delete mode 100644 tangostationcontrol/test/devices/test_psoc_device.py diff --git a/.editorconfig b/.editorconfig index df0525fcb..6c2122894 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,6 +17,9 @@ ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false +[Makefile] +indent_style = tab + [{*.bash,*.sh,*.zsh}] indent_size = 2 tab_width = 2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5e397f0af..457fd4669 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -305,11 +305,17 @@ unit_test: - log/ - .jumppad/logs/ -integration_test_docker: +integration_test_core: extends: .test_docker script: # Do not remove 'bash' or statement will be ignored by primitive docker shell - - bash -e $CI_PROJECT_DIR/sbin/run_integration_test.sh --no-build --save-logs --module="tango" + - bash -e $CI_PROJECT_DIR/sbin/run_integration_test.sh --no-build --save-logs --module="tango" --station=cs + +integration_test_remote: + extends: .test_docker + script: + # Do not remove 'bash' or statement will be ignored by primitive docker shell + - bash -e $CI_PROJECT_DIR/sbin/run_integration_test.sh --no-build --save-logs --module="tango" --station=rs service_test_docker: extends: .test_docker diff --git a/CDB/stations/DTS.json b/CDB/stations/DTS.json index cc1753666..ccaa2b931 100644 --- a/CDB/stations/DTS.json +++ b/CDB/stations/DTS.json @@ -18,7 +18,6 @@ "Control_Children": [ "STAT/EC/1", "STAT/CCD/1", - "STAT/PSOC/1", "STAT/PCON/1", "STAT/Configuration/1", "STAT/Calibration/1", diff --git a/CDB/stations/common.json b/CDB/stations/common.json index 4096102dc..5f9ad7a71 100644 --- a/CDB/stations/common.json +++ b/CDB/stations/common.json @@ -11,11 +11,6 @@ "Power_Available_In_State": ["HIBERNATE"] } }, - "PSOC": { - "properties": { - "Power_Available_In_State": ["HIBERNATE"] - } - }, "PCON": { "properties": { "Power_Available_In_State": ["HIBERNATE"] @@ -118,7 +113,7 @@ "properties": { "Power_Children": [ "STAT/PCON/1", - "STAT/PSOC/1" + "STAT/CCD/1" ], "OPC_Server_Name": [ "10.87.2.126" @@ -193,47 +188,6 @@ } } }, - "PSOC": { - "STAT": { - "PSOC": { - "STAT/PSOC/1": { - "properties": { - "Power_Children": [ - "STAT/CCD/1" - ], - "SNMP_host": [ - "10.99.250.80" - ], - "SNMP_community": [ - "public" - ], - "SNMP_mib_dir": [ - "devices/mibs/PowerNet-MIB.mib" - ], - "SNMP_timeout": [ - "10.0" - ], - "SNMP_version": [ - "1" - ], - "SNMP_use_simulators": [ - "False" - ], - "PSOC_sockets": [ - "socket_1", - "socket_2", - "socket_3", - "socket_4", - "socket_5", - "socket_6", - "socket_7", - "socket_8" - ] - } - } - } - } - }, "PCON": { "STAT": { "PCON": { diff --git a/CDB/stations/cs.json b/CDB/stations/cs.json index 536f242eb..7b0f161f2 100644 --- a/CDB/stations/cs.json +++ b/CDB/stations/cs.json @@ -18,7 +18,6 @@ "Control_Children": [ "STAT/EC/1", "STAT/CCD/1", - "STAT/PSOC/1", "STAT/PCON/1", "STAT/Configuration/1", "STAT/Calibration/1", diff --git a/CDB/stations/rs.json b/CDB/stations/rs.json index a7f52c16e..50ee06212 100644 --- a/CDB/stations/rs.json +++ b/CDB/stations/rs.json @@ -17,7 +17,6 @@ "Control_Children": [ "STAT/EC/1", "STAT/CCD/1", - "STAT/PSOC/1", "STAT/PCON/1", "STAT/Configuration/1", "STAT/Calibration/1", diff --git a/CDB/stations/simulators_ConfigDb.json b/CDB/stations/simulators_ConfigDb.json index 910139622..38c0a04c6 100644 --- a/CDB/stations/simulators_ConfigDb.json +++ b/CDB/stations/simulators_ConfigDb.json @@ -171,19 +171,6 @@ } } }, - "PSOC": { - "STAT": { - "PSOC": { - "STAT/PSOC/1": { - "properties": { - "SNMP_use_simulators": [ - "True" - ] - } - } - } - } - }, "RECVH": { "STAT": { "RECVH": { diff --git a/CDB/stations/testenv_cs001.json b/CDB/stations/testenv_cs001.json index c670652b6..46909da40 100644 --- a/CDB/stations/testenv_cs001.json +++ b/CDB/stations/testenv_cs001.json @@ -365,19 +365,6 @@ } } }, - "PSOC": { - "STAT": { - "PSOC": { - "STAT/PSOC/1": { - "properties": { - "SNMP_use_simulators": [ - "True" - ] - } - } - } - } - }, "RECVH": { "STAT": { "RECVH": { diff --git a/CDB/stations/testenv_rs307.json b/CDB/stations/testenv_rs307.json new file mode 100644 index 000000000..ede3458be --- /dev/null +++ b/CDB/stations/testenv_rs307.json @@ -0,0 +1,875 @@ +{ + "servers": { + "APSCT": { + "STAT": { + "APSCT": { + "STAT/APSCT/L0": { + "properties": { + "OPC_Server_Name": [ + "apsct-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "APSCT_On_Off_timeout": [ + "1" + ] + } + }, + "STAT/APSCT/L1": { + "properties": { + "OPC_Server_Name": [ + "apsct-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "APSCT_On_Off_timeout": [ + "1" + ] + } + }, + "STAT/APSCT/H0": { + "properties": { + "OPC_Server_Name": [ + "apsct-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "APSCT_On_Off_timeout": [ + "1" + ] + } + } + } + } + }, + "CCD": { + "STAT": { + "CCD": { + "STAT/CCD/1": { + "properties": { + "OPC_Server_Name": [ + "ccd-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "CCD_On_Off_timeout": [ + "1" + ] + } + } + } + } + }, + "EC": { + "STAT": { + "EC": { + "STAT/EC/1": { + "properties": { + "OPC_Server_Name": [ + "ec-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "OPC_Node_Path_Prefix": [ + "3:ServerInterfaces", + "4:Environmental_Control" + ], + "OPC_namespace": [ + "http://Environmental_Control" + ] + } + } + } + } + }, + "APSPU": { + "STAT": { + "APSPU": { + "STAT/APSPU/L0": { + "properties": { + "OPC_Server_Name": [ + "apspu-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + }, + "STAT/APSPU/L1": { + "properties": { + "OPC_Server_Name": [ + "apspu-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + }, + "STAT/APSPU/H0": { + "properties": { + "OPC_Server_Name": [ + "apspu-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "Beamlet": { + "STAT": { + "Beamlet": { + "STAT/Beamlet/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + }, + "STAT/Beamlet/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_beamlet_output_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_beamlet_output_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "DigitalBeam": { + "STAT": { + "DigitalBeam": { + "STAT/DigitalBeam/LBA": { + "properties": { + "Beam_tracking_interval": [ + "1.0" + ], + "Beam_tracking_preparation_period": [ + "0.5" + ] + } + }, + "STAT/DigitalBeam/HBA": { + "properties": { + "Beam_tracking_interval": [ + "1.0" + ], + "Beam_tracking_preparation_period": [ + "0.5" + ] + } + } + } + } + }, + "TemperatureManager": { + "STAT": { + "TemperatureManager": { + "STAT/TemperatureManager/1": { + "properties": { + "Alarm_Error_List": [ + "APSCT, APSCT_TEMP_error_R", + "APSPU, APSPU_TEMP_error_R", + "UNB2, UNB2_TEMP_error_R", + "RECVH, RECV_TEMP_error_R", + "RECVL, RECV_TEMP_error_R" + ], + "Shutdown_Device_List": [ + "STAT/SDPFirmware/LBA", + "STAT/SDPFirmware/HBA", + "STAT/SDP/LBA", + "STAT/SDP/HBA", + "STAT/UNB2/L0", + "STAT/UNB2/L1", + "STAT/UNB2/H0", + "STAT/RECVH/H0", + "STAT/RECVL/L0", + "STAT/RECVL/L1", + "STAT/APSCT/L0", + "STAT/APSCT/L1", + "STAT/APSCT/H0", + "STAT/CCD/1", + "STAT/APSPU/L0", + "STAT/APSPU/L1", + "STAT/APSPU/H0" + ] + } + } + } + } + }, + "PCON": { + "STAT": { + "PCON": { + "STAT/PCON/1": { + "properties": { + "SNMP_use_simulators": [ + "True" + ] + } + } + } + } + }, + "PSOC": { + "STAT": { + "PSOC": { + "STAT/PSOC/1": { + "properties": { + "SNMP_use_simulators": [ + "True" + ] + } + } + } + } + }, + "RECVH": { + "STAT": { + "RECVH": { + "STAT/RECVH/H0": { + "properties": { + "OPC_Server_Name": [ + "recvh-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "RCU_On_Off_timeout": [ + "1" + ], + "RCU_DTH_On_Off_timeout": [ + "1" + ] + } + } + } + } + }, + "RECVL": { + "STAT": { + "RECVL": { + "STAT/RECVL/L0": { + "properties": { + "OPC_Server_Name": [ + "recvl-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "RCU_On_Off_timeout": [ + "1" + ], + "RCU_DTH_On_Off_timeout": [ + "1" + ] + } + }, + "STAT/RECVL/L1": { + "properties": { + "OPC_Server_Name": [ + "recvl-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "RCU_On_Off_timeout": [ + "1" + ], + "RCU_DTH_On_Off_timeout": [ + "1" + ] + } + } + } + } + }, + "SDPFirmware": { + "STAT": { + "SDPFirmware": { + "STAT/SDPFirmware/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "Firmware_Boot_timeout": [ + "1.0" + ] + } + }, + "STAT/SDPFirmware/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "Firmware_Boot_timeout": [ + "1.0" + ] + } + } + } + } + }, + "SDP": { + "STAT": { + "SDP": { + "STAT/SDP/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + }, + "STAT/SDP/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ] + } + } + } + } + }, + "BST": { + "STAT": { + "BST": { + "STAT/BST/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + }, + "STAT/BST/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_bst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_bst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "SST": { + "STAT": { + "SST": { + "STAT/SST/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + }, + "STAT/SST/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_sst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_sst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "XST": { + "STAT": { + "XST": { + "STAT/XST/LBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + }, + "STAT/XST/HBA": { + "properties": { + "OPC_Server_Name": [ + "sdptr-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "FPGA_xst_offload_hdr_eth_destination_mac_RW_default": [ + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB", + "01:23:45:67:89:AB" + ], + "FPGA_xst_offload_hdr_ip_destination_address_RW_default": [ + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1", + "127.0.0.1" + ] + } + } + } + } + }, + "UNB2": { + "STAT": { + "UNB2": { + "STAT/UNB2/L0": { + "properties": { + "OPC_Server_Name": [ + "unb2-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "UNB2_On_Off_timeout": [ + "1" + ] + } + }, + "STAT/UNB2/L1": { + "properties": { + "OPC_Server_Name": [ + "unb2-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "UNB2_On_Off_timeout": [ + "1" + ] + } + }, + "STAT/UNB2/H0": { + "properties": { + "OPC_Server_Name": [ + "unb2-sim.service.consul" + ], + "OPC_Server_Port": [ + "4840" + ], + "OPC_Time_Out": [ + "5.0" + ], + "UNB2_On_Off_timeout": [ + "1" + ] + } + } + } + } + }, + "TileBeam": { + "STAT": { + "TileBeam": { + "STAT/TileBeam/HBA": { + "properties": { + "Tracking_enabled_RW_default": [ "True" ], + "Beam_tracking_interval": [ + "10.0" + ] + } + } + } + } + }, + "StationManager": { + "STAT": { + "StationManager": { + "STAT/StationManager/1": { + "properties": { + "Suppress_State_Transition_Failures": [ + "True" + ] + } + } + } + } + } + } +} diff --git a/README.md b/README.md index fedae2f2b..9e77facda 100644 --- a/README.md +++ b/README.md @@ -114,17 +114,6 @@ If you think the network is lingering as an error you can use ### Inspect services -Now we can start all containers, and make sure everything is up: - -```sh -make start -make status -``` - -If not, you can inspect why with `docker logs <container>`. The containers will -automatically be restarted on reboot or failure. Stop them explicitly to bring -them down (`make stop <container>`). - Most notably, you will have web interfaces available at: - http://localhost:8888 (Jupyter Lab) @@ -164,6 +153,7 @@ Next change the version in the following places: * 0.42.2 Add protection control device shutting down station during over temperature Use the station manager `protection_lock_RW` to see if the station is locked against further damage. + Add integration tests for remote stations * 0.42.1 Added lock around commands in AsyncDevices to prevent concurrent execution * 0.42.0 Change CS032 port mappings to prevent beamlet overlap * 0.41.1 Reduce log size for value changes and metadata publications drastically diff --git a/docker/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-devices.py b/docker/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-devices.py index 5de897e4b..42831e92a 100644 --- a/docker/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-devices.py +++ b/docker/jupyter-lab/ipython-profiles/stationcontrol-jupyter/startup/02-devices.py @@ -80,7 +80,6 @@ stationmanager = OptionalDeviceProxy("STAT/StationManager/1") ccd = OptionalDeviceProxy("STAT/CCD/1") ec = OptionalDeviceProxy("STAT/EC/1") pcon = OptionalDeviceProxy("STAT/PCON/1") -psoc = OptionalDeviceProxy("STAT/PSOC/1") protectioncontrol = OptionalDeviceProxy("STAT/ProtectionControl/1") configuration = OptionalDeviceProxy("STAT/Configuration/1") calibration = OptionalDeviceProxy("STAT/Calibration/1") diff --git a/infra/dev/all.hcl b/infra/dev/all.hcl index 0d0a81ea6..67f8717f3 100644 --- a/infra/dev/all.hcl +++ b/infra/dev/all.hcl @@ -12,6 +12,10 @@ variable "debug_host" { default = "" } +variable "station_type" { + default = "cs" +} + module "nomad" { source = "./nomad" variables = { diff --git a/infra/dev/services.hcl b/infra/dev/services.hcl index 3ee8d999f..16049f539 100644 --- a/infra/dev/services.hcl +++ b/infra/dev/services.hcl @@ -5,6 +5,10 @@ variable "image_tag" { default = "latest" } +variable "station_type" { + default = "cs" +} + module "nomad" { source = "./nomad" variables = { diff --git a/infra/dev/services/variables.hcl b/infra/dev/services/variables.hcl index ebc49b1ac..6dbadd784 100644 --- a/infra/dev/services/variables.hcl +++ b/infra/dev/services/variables.hcl @@ -2,6 +2,10 @@ variable "nomad_cluster" { default = "" } +variable "station_type" { + default = "cs" +} + variable "lofar20_dir" { default = "" } diff --git a/infra/dev/tango.hcl b/infra/dev/tango.hcl index 8c77d9aad..4cddf0006 100644 --- a/infra/dev/tango.hcl +++ b/infra/dev/tango.hcl @@ -12,6 +12,10 @@ variable "debug_host" { default = "" } +variable "station_type" { + default = "cs" +} + module "nomad" { source = "./nomad" variables = { diff --git a/infra/dev/tango/tango.hcl b/infra/dev/tango/tango.hcl index 6e8aada4e..976ee8090 100644 --- a/infra/dev/tango/tango.hcl +++ b/infra/dev/tango/tango.hcl @@ -87,6 +87,7 @@ resource "exec" "dsconfig" { DOCKER_HOST=docker_host() TANGO_HOST="tango.service.consul:10000" TAG=variable.image_tag + STATION_TYPE=variable.station_type } working_directory = "${variable.lofar20_dir}" timeout = "300s" @@ -108,10 +109,19 @@ resource "exec" "dsconfig" { bash sbin/dsconfig.sh --update CDB/stations/l1.json bash sbin/dsconfig.sh --update CDB/stations/lba.json bash sbin/dsconfig.sh --update CDB/stations/h0.json - bash sbin/dsconfig.sh --update CDB/stations/hba_core.json - bash sbin/dsconfig.sh --update CDB/stations/cs.json - bash sbin/dsconfig.sh --update CDB/stations/cs001.json - bash sbin/dsconfig.sh --update CDB/stations/testenv_cs001.json + if [ "$STATION_TYPE" = "rs" ]; then + echo "Configuring dsconfig for remote station" + bash sbin/dsconfig.sh --update CDB/stations/hba_remote.json + bash sbin/dsconfig.sh --update CDB/stations/rs.json + bash sbin/dsconfig.sh --update CDB/stations/rs307.json + bash sbin/dsconfig.sh --update CDB/stations/testenv_rs307.json + else + echo "Configuring dsconfig for core station" + bash sbin/dsconfig.sh --update CDB/stations/hba_core.json + bash sbin/dsconfig.sh --update CDB/stations/cs.json + bash sbin/dsconfig.sh --update CDB/stations/cs001.json + bash sbin/dsconfig.sh --update CDB/stations/testenv_cs001.json + fi EOF } diff --git a/infra/dev/tango/variables.hcl b/infra/dev/tango/variables.hcl index f396640ec..cacc9d706 100644 --- a/infra/dev/tango/variables.hcl +++ b/infra/dev/tango/variables.hcl @@ -2,6 +2,10 @@ variable "nomad_cluster" { default = "" } +variable "station_type" { + default = "cs" +} + variable "image_tag" { default = "latest" } diff --git a/infra/env/common.yaml b/infra/env/common.yaml index b45c930f8..93fe34c00 100644 --- a/infra/env/common.yaml +++ b/infra/env/common.yaml @@ -73,7 +73,6 @@ devices: - ObservationControl - Metadata - PCON - - PSOC - RECVH - RECVL - SDP diff --git a/infra/jobs/station/Makefile b/infra/jobs/station/Makefile index 9520f70ab..d564746ac 100644 --- a/infra/jobs/station/Makefile +++ b/infra/jobs/station/Makefile @@ -1,10 +1,15 @@ TAG ?= latest STATION ?= dev +STATION_TYPE ?= cs DIR_OUT ?= . DIR_SRC += . SRC_JOBS += $(wildcard $(addsuffix /*.levant.nomad, $(DIR_SRC))) JOBS := $(patsubst %.levant.nomad,%.nomad, $(SRC_JOBS)) -ENV ?= ../../env/common.yaml ../../env/cs.yaml +ifeq ($(STATION_TYPE), cs) + ENV ?= ../../env/common.yaml ../../env/cs.yaml +else + ENV ?= ../../env/common.yaml ../../env/rs.yaml +endif .PHONY: render diff --git a/sbin/prepare_dev_env.sh b/sbin/prepare_dev_env.sh index 3fa5149d3..1889f1926 100755 --- a/sbin/prepare_dev_env.sh +++ b/sbin/prepare_dev_env.sh @@ -37,6 +37,7 @@ if ! [ -x "$(command -v levant)" ]; then chmod +x ./.bin/levant fi +echo "Generating jobs for station configuration: $STATION_TYPE" make -C infra/jobs/station DIR_OUT="$( realpath "infra/dev/jobs/station")" render docker_volume="dev_nomad_station" diff --git a/sbin/run_integration_test.sh b/sbin/run_integration_test.sh index 67a57bc73..140aa91f3 100755 --- a/sbin/run_integration_test.sh +++ b/sbin/run_integration_test.sh @@ -36,12 +36,15 @@ function usage { echo "" echo "./$(basename "$0") --module=<tango|services|all> Only start given subset of the infrastructure, defaults to all" + echo "" + echo "./$(basename "$0") --station=<cs|rs> + Configure testing for core or remote station" } # list of arguments expected in the input -optstring_long="help,no-build,skip-tests,preserve,save-logs,interactive,module::" +optstring_long="help,no-build,skip-tests,preserve,save-logs,interactive,module::,station::" optstring="h" options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@") @@ -49,6 +52,7 @@ options=$(getopt -l ${optstring_long} -o ${optstring} -- "$@") eval set -- "$options" module="all" +station="cs" while true; do case ${1} in @@ -83,6 +87,10 @@ while true; do shift module="$1" ;; + --station) + shift + station="$1" + ;; --) shift break;; @@ -90,6 +98,8 @@ while true; do shift done +export STATION_TYPE=$station + if [ "${module}" == "services" ]; then echo "module=services, enabling skip-tests and preservation" export no_tests=1 @@ -286,6 +296,7 @@ jumppad_options=( # these don't seem to propagate --var="lofar20_dir=$LOFAR20_DIR" --var="image_tag=$TAG" --var="debug_host=$LOCAL_IP" + --var="station_type=$STATION_TYPE" ) echo "Start module: $module" @@ -316,7 +327,7 @@ echo "Using tango host $TANGO_HOST" # Devices list is used to explitly word split when supplied to commands, must # disable shellcheck SC2086 for each case. -DEVICES=(device-stationmanager device-aps device-apsct device-ccd device-ec device-apspu device-sdpfirmware device-sdp device-recvh device-recvl device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-pcon device-afh device-afl device-protectioncontrol device-observationcontrol device-configuration device-calibration device-metadata) +DEVICES=(device-stationmanager device-aps device-apsct device-ccd device-ec device-apspu device-sdpfirmware device-sdp device-recvh device-recvl device-bst device-sst device-unb2 device-xst device-beamlet device-digitalbeam device-tilebeam device-psoc device-afh device-afl device-protectioncontrol device-observationcontrol device-configuration device-calibration device-metadata) # Wait for devices to restart @@ -328,10 +339,13 @@ fi # Start the integration test -integration_test default - -integration_test tilebeam_performance "device-sdpfirmware device-sdp device-recvh device-recvl device-tilebeam device-afh device-afl" "${LOFAR20_DIR}/CDB/integrations/tilebeam_cluster_ConfigDb.json" - -integration_test digitalbeam_performance "device-sdpfirmware device-sdp device-recvh device-recvl device-digitalbeam device-beamlet device-afh device-afl" "${LOFAR20_DIR}/CDB/integrations/digitalbeam_cluster_ConfigDb.json" - -integration_test configuration "device-configuration" +if [ "$STATION_TYPE" = "cs" ]; then + echo "Running integration tests for core station" + integration_test default + integration_test tilebeam_performance "device-sdpfirmware device-sdp device-recvh device-recvl device-tilebeam device-afh device-afl" "${LOFAR20_DIR}/CDB/integrations/tilebeam_cluster_ConfigDb.json" + integration_test digitalbeam_performance "device-sdpfirmware device-sdp device-recvh device-recvl device-digitalbeam device-beamlet device-afh device-afl" "${LOFAR20_DIR}/CDB/integrations/digitalbeam_cluster_ConfigDb.json" + integration_test configuration "device-configuration" +else + echo "Running integration tests for remote station" + integration_test remote_station +fi diff --git a/tangostationcontrol/docs/source/devices/overview.rst b/tangostationcontrol/docs/source/devices/overview.rst index a1bbe0d9b..c5c2bfdda 100644 --- a/tangostationcontrol/docs/source/devices/overview.rst +++ b/tangostationcontrol/docs/source/devices/overview.rst @@ -33,8 +33,6 @@ This package implements the *Station Control (SC)* part of a LOFAR2.0 station, t APSCT; APSPU; UNB2; - - PSOC; } subgraph clusterSDP { @@ -70,11 +68,6 @@ This package implements the *Station Control (SC)* part of a LOFAR2.0 station, t APSPUTR; } APSPU -> APSPUTR; - - subgraph clusterPSOC { - label = "PSOC"; - } - PSOC -> clusterPSOC; } A brief description of each of these devices: @@ -94,7 +87,6 @@ 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: diff --git a/tangostationcontrol/docs/source/devices/psoc.rst b/tangostationcontrol/docs/source/devices/psoc.rst deleted file mode 100644 index c3265005f..000000000 --- a/tangostationcontrol/docs/source/devices/psoc.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _psoc: - -PSOC --------------------- - -The ``psoc == DeviceProxy("STAT/PSOC/1")`` device controls the Power Distribution Unit (PSOC). diff --git a/tangostationcontrol/docs/source/index.rst b/tangostationcontrol/docs/source/index.rst index 872a3b4ca..44bb396b5 100644 --- a/tangostationcontrol/docs/source/index.rst +++ b/tangostationcontrol/docs/source/index.rst @@ -29,7 +29,6 @@ Even without having access to any LOFAR2.0 hardware, you can install the full st devices/bst-sst-xst devices/station-manager devices/docker - devices/psoc devices/ccd devices/ec devices/configuration diff --git a/tangostationcontrol/integration_test/common/__init__.py b/tangostationcontrol/integration_test/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tangostationcontrol/integration_test/common/base_device_classes/__init__.py b/tangostationcontrol/integration_test/common/base_device_classes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tangostationcontrol/integration_test/common/base_device_classes/power_hierarchy_tests.py b/tangostationcontrol/integration_test/common/base_device_classes/power_hierarchy_tests.py new file mode 100644 index 000000000..aded09f1f --- /dev/null +++ b/tangostationcontrol/integration_test/common/base_device_classes/power_hierarchy_tests.py @@ -0,0 +1,378 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +""" +Power Hierarchy module integration test +""" +import logging + +from tango import DevState, DeviceProxy + +from integration_test import base +from integration_test.device_proxy import TestDeviceProxy +from lofar_station_client.common import CaseInsensitiveString +from tangostationcontrol.common.constants import N_rcu, N_rcu_inp +from tangostationcontrol.devices.base_device_classes.hierarchy_device import ( + NotFoundException, + HierarchyMatchingFilter, +) +from tangostationcontrol.devices.base_device_classes.power_hierarchy import ( + PowerHierarchyControlDevice, +) + +logger = logging.getLogger() + + +class DevicePowerHierarchyControlTests(base.IntegrationTestCase): + """Integration Test class for PowerHierarchyDevice methods""" + + __test__ = False + + pwr_attr_name = "hardware_powered_R" + + stationmanager_name = "STAT/StationManager/1" + ec_name = "STAT/EC/1" + aps_l0_name = "STAT/APS/L0" + aps_l1_name = "STAT/APS/L1" + aps_h0_name = "STAT/APS/H0" + apsct_name = "STAT/APSCT/H0" + apspu_h0_name = "STAT/APSPU/H0" + apspu_l0_name = "STAT/APSPU/L0" + apspu_l1_name = "STAT/APSPU/L1" + ccd_name = "STAT/CCD/1" + pcon_name = "STAT/PCON/1" + sdp_name = "STAT/SDP/HBA0" + unb2_h0_name = "STAT/UNB2/H0" + unb2_l0_name = "STAT/UNB2/L0" + recvh_name = "STAT/RECVH/H0" + recvl_name = "STAT/RECVL/L0" + + def setUp( + self, + station_name: str = "CS001", + antennafield_name: str = None, + sdp_name: str = None, + sdpfirmware_name: str = None, + ): + super().setUp() + + if antennafield_name is None or sdp_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + self.station_name = station_name + + self.antennafield_name = antennafield_name + self.sdp_name = sdp_name + self.sdpfirmware_name = sdpfirmware_name + + self.setup_all_devices() + + def _unacceptable_exceptions(self): + """Return the set of exceptions raised by the last state transition + in the StationManager, that we do not accept. + + We must accept exceptions since we do not emulate interaction with + actual hardware. In this function, we make sure to just ignore those + which we know will be raised even in a sunny-day scenario.""" + + result = [] + + for ex_str in self.stationmanager_proxy.last_requested_transition_exceptions_R: + # Skip "vetted" exceptions that involve switching power + # on actual hardware, which obviously won't power on in + # our simulators. + if "Failed to execute command_inout on device" in ex_str: + if "command power_hardware_on" in ex_str: + continue + if "command power_hardware_off" in ex_str: + continue + + # Anything left is not acceptable + result.append(ex_str) + + return result + + def setup_stationmanager_proxy(self): + """Initialise StationManager device""" + stationmanager_proxy = TestDeviceProxy(self.stationmanager_name) + # extend timeout for running commands, as state transitions can take a long time + stationmanager_proxy.set_timeout_millis(60000) + + stationmanager_proxy.off() + stationmanager_proxy.initialise() + stationmanager_proxy.on() + self.assertEqual(stationmanager_proxy.state(), DevState.ON) + return stationmanager_proxy + + def setup_proxy_off(self, device_name: str): + """Initialise proxy and turn off device""" + proxy = TestDeviceProxy(device_name) + proxy.off() + return proxy + + def setup_all_devices(self): + """Initialise all Tango devices needed for state transitions""" + self.stationmanager_proxy = self.setup_stationmanager_proxy() + + self.ec_proxy = self.setup_proxy_off(self.ec_name) + self.aps_l0_proxy = self.setup_proxy_off(self.aps_l0_name) + self.pcon_proxy = self.setup_proxy_off(self.pcon_name) + self.ccd_proxy = self.setup_proxy_off(self.ccd_name) + self.apspu_h0_proxy = self.setup_proxy_off(self.apspu_h0_name) + self.apspu_l0_proxy = self.setup_proxy_off(self.apspu_l0_name) + self.apsct_proxy = self.setup_proxy_off(self.apsct_name) + self.unb2_h0_proxy = self.setup_proxy_off(self.unb2_h0_name) + self.unb2_l0_proxy = self.setup_proxy_off(self.unb2_l0_name) + self.recvh_proxy = self.setup_proxy_off(self.recvh_name) + self.recvl_proxy = self.setup_proxy_off(self.recvl_name) + self.sdpfirmware_proxy = self.setup_proxy_off(self.sdpfirmware_name) + self.sdp_proxy = self.setup_proxy_off(self.sdp_name) + self.antennafield_proxy = self.setup_proxy_off(self.antennafield_name) + + def test_power_sequence_definition(self): + """ + Test whether Power Sequence is correctly retrieved from the HierarchyDevice + """ + self.setup_stationmanager_proxy() + self.setup_all_devices() + + stationmanager_ph = PowerHierarchyControlDevice() + stationmanager_ph.init(self.stationmanager_name) + children_hierarchy = stationmanager_ph.children(depth=2) + + # Check if EC is child of StationManager + ec_name = self.ec_name.casefold() + self.assertTrue(ec_name in children_hierarchy) + self.assertTrue(isinstance(children_hierarchy[ec_name]["proxy"], DeviceProxy)) + + # Check if CCD is child of EC + self.assertTrue( + self.ccd_name.casefold() in children_hierarchy[ec_name]["children"].keys() + ) + + # Check if EC retrieves correctly its parent state (StationManager -> ON) + ec_ph = PowerHierarchyControlDevice() + ec_ph.init(self.ec_name) + self.assertEqual(ec_ph.parent_state(), DevState.ON) + # Check if child reads correctly a parent attribute + self.assertEqual( + ec_ph.read_parent_attribute("Station_Name_R"), + self.station_name, + ) + + def test_off_to_hibernate(self): + """Test Tango devices are correctly triggered in the OFF to HIBERNATE transition""" + self.assertEqual(self.pcon_proxy.state(), DevState.OFF) + self.assertEqual(self.ccd_proxy.state(), DevState.OFF) + # Switch from OFF to HIBERNATE + self.stationmanager_proxy.station_hibernate() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") + self.assertEqual( + self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" + ) + self.assertEqual(self.pcon_proxy.state(), DevState.ON) + self.assertEqual(self.ccd_proxy.state(), DevState.ON) + + logger.info( + "Exceptions suppressed in test_off_to_hibernate: %s", + self.stationmanager_proxy.last_requested_transition_exceptions_R, + ) + + self.assertListEqual([], self._unacceptable_exceptions()) + + def test_hibernate_to_standby(self): + """ + Test whether Tango devices are correctly triggered in the HIBERNATE to STANDBY transition + """ + # Switch from OFF to HIBERNATE + self.stationmanager_proxy.station_hibernate() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") + self.assertEqual( + self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" + ) + self.assertEqual(self.apspu_h0_proxy.state(), DevState.OFF) + self.assertEqual(self.apspu_l0_proxy.state(), DevState.OFF) + self.assertEqual(self.apsct_proxy.state(), DevState.OFF) + self.assertEqual(self.unb2_h0_proxy.state(), DevState.OFF) + self.assertEqual(self.unb2_l0_proxy.state(), DevState.OFF) + self.assertEqual(self.recvh_proxy.state(), DevState.OFF) + self.assertEqual(self.recvl_proxy.state(), DevState.OFF) + self.assertEqual(self.sdpfirmware_proxy.state(), DevState.OFF) + # Switch from HIBERNATE to STANDBY + self.stationmanager_proxy.station_standby() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "STANDBY") + self.assertEqual( + self.stationmanager_proxy.requested_station_state_R.name, "STANDBY" + ) + self.assertEqual(self.apspu_h0_proxy.state(), DevState.ON) + self.assertEqual(self.apspu_l0_proxy.state(), DevState.ON) + self.assertEqual(self.apsct_proxy.state(), DevState.ON) + self.assertEqual(self.unb2_h0_proxy.state(), DevState.ON) + self.assertEqual(self.unb2_l0_proxy.state(), DevState.ON) + self.assertEqual(self.recvh_proxy.state(), DevState.ON) + self.assertEqual(self.recvl_proxy.state(), DevState.ON) + self.assertEqual(self.sdpfirmware_proxy.state(), DevState.ON) + # Check if SDP Firmware is booted with factory image + firmware_images = self.sdpfirmware_proxy.FPGA_boot_image_RW.tolist() + self.assertListEqual( + [0] * len(firmware_images), + firmware_images, + ) + + logger.info( + "Exceptions suppressed: %s", + self.stationmanager_proxy.last_requested_transition_exceptions_R, + ) + + self.assertListEqual([], self._unacceptable_exceptions()) + + def test_standby_to_on(self): + """ + Test whether Tango devices are correctly triggered in the STANDBY to ON transition + """ + # Switch from OFF to HIBERNATE + self.stationmanager_proxy.station_hibernate() + # Switch from HIBERNATE to STANDBY + self.stationmanager_proxy.station_standby() + # Switch from STANDBY to ON + self.assertEqual(self.sdp_proxy.state(), DevState.OFF) + self.assertEqual(self.antennafield_proxy.state(), DevState.OFF) + self.stationmanager_proxy.station_on() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "ON") + self.assertEqual(self.stationmanager_proxy.requested_station_state_R.name, "ON") + self.assertEqual(self.sdp_proxy.state(), DevState.ON) + self.assertEqual(self.antennafield_proxy.state(), DevState.ON) + + # Test if DTH and DAB are disabled + self.assertListEqual( + self.recvh_proxy.RCU_DTH_on_R.tolist(), + [[False] * N_rcu_inp] * N_rcu, + ) + self.assertListEqual( + self.antennafield_proxy.RCU_DAB_filter_on_RW.tolist(), + [False] * self.antennafield_proxy.nr_antennas_R, + ) + + logger.info( + "Exceptions suppressed: %s", + self.stationmanager_proxy.last_requested_transition_exceptions_R, + ) + + self.assertListEqual([], self._unacceptable_exceptions()) + + def test_on_to_standby(self): + """ + Test whether Tango devices are correctly triggered in the ON to STANDBY transition + """ + # Switch from OFF to HIBERNATE + self.stationmanager_proxy.station_hibernate() + # Switch from HIBERNATE to STANDBY + self.stationmanager_proxy.station_standby() + # Switch from STANDBY to ON + self.stationmanager_proxy.station_on() + # Reverse to STANDBY + self.stationmanager_proxy.station_standby() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "STANDBY") + self.assertEqual( + self.stationmanager_proxy.requested_station_state_R.name, "STANDBY" + ) + self.assertEqual(self.sdp_proxy.state(), DevState.OFF) + self.assertEqual(self.antennafield_proxy.state(), DevState.OFF) + + logger.info( + "Exceptions suppressed: %s", + self.stationmanager_proxy.last_requested_transition_exceptions_R, + ) + + self.assertListEqual([], self._unacceptable_exceptions()) + + def test_standby_to_hibernate(self): + """ + Test whether Tango devices are correctly triggered in the STANDBY to HIBERNATE transition + """ + # Switch from OFF to HIBERNATE + self.stationmanager_proxy.station_hibernate() + # Switch from HIBERNATE to STANDBY + self.stationmanager_proxy.station_standby() + # Reverse to HIBERNATE + self.stationmanager_proxy.station_hibernate() + self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") + self.assertEqual( + self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" + ) + self.assertEqual(self.apspu_h0_proxy.state(), DevState.OFF) + self.assertEqual(self.apspu_l0_proxy.state(), DevState.OFF) + self.assertEqual(self.apsct_proxy.state(), DevState.OFF) + self.assertEqual(self.unb2_h0_proxy.state(), DevState.OFF) + self.assertEqual(self.unb2_l0_proxy.state(), DevState.OFF) + self.assertEqual(self.recvh_proxy.state(), DevState.OFF) + self.assertEqual(self.recvl_proxy.state(), DevState.OFF) + self.assertEqual(self.sdpfirmware_proxy.state(), DevState.OFF) + + logger.info( + "Exceptions suppressed: %s", + self.stationmanager_proxy.last_requested_transition_exceptions_R, + ) + + self.assertListEqual([], self._unacceptable_exceptions()) + + def test_branch_child_deep_tree(self): + """ + Test whether Tango devices are correctly retrieved with branch_child function + """ + self.setup_all_devices() + # Create a Hierarchy Device from Device STAT/RECVH/H0 + recvh_ph = PowerHierarchyControlDevice() + recvh_ph.init(self.recvh_name) + self.assertEqual(recvh_ph.parent(), "stat/apspu/h0") + # Branch child method must not return its direct parent + self.assertRaises( + NotFoundException, + recvh_ph.branch_child, + "*/apspu/h*", + HierarchyMatchingFilter.REGEX, + ) + # Test if returns another device with the right query + self.assertEqual( + recvh_ph.branch_child("*/apspu/*", HierarchyMatchingFilter.REGEX).name(), + CaseInsensitiveString(self.apspu_l0_name), + ) + # Test if returns a device in the tree with depth > 1 + self.assertEqual( + recvh_ph.branch_child("*/aps/l*", HierarchyMatchingFilter.REGEX).name(), + CaseInsensitiveString(self.aps_l0_name), + ) + + def test_branch_children_names_deep_tree(self): + """ + Test whether Tango devices are correctly retrieved with + branch_children_names function + """ + self.setup_all_devices() + # Create a Hierarchy Device from Device STAT/RECVH/H0 + recvh_ph = PowerHierarchyControlDevice() + recvh_ph.init(self.recvh_name) + self.assertEqual(recvh_ph.parent(), "stat/apspu/h0") + self.assertEqual(recvh_ph.children(), {}) + # Branch_children_names method must not return its direct parent + self.assertListEqual( + recvh_ph.branch_children_names("*/apspu/h*", HierarchyMatchingFilter.REGEX), + [], + ) + # Test if returns another device with the right query + self.assertListEqual( + recvh_ph.branch_children_names("*/apspu/*", HierarchyMatchingFilter.REGEX), + [ + CaseInsensitiveString(self.apspu_l0_name), + CaseInsensitiveString(self.apspu_l1_name), + ], + ) + # Test if returns a device in the tree with depth > 1 + self.assertListEqual( + recvh_ph.branch_children_names("*/aps/*", HierarchyMatchingFilter.REGEX), + [ + CaseInsensitiveString(self.aps_l0_name), + CaseInsensitiveString(self.aps_l1_name), + CaseInsensitiveString(self.aps_h0_name), + ], + ) diff --git a/tangostationcontrol/integration_test/common/device_beamlet_tests.py b/tangostationcontrol/integration_test/common/device_beamlet_tests.py new file mode 100644 index 000000000..e4f05f623 --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_beamlet_tests.py @@ -0,0 +1,136 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import time +from ctypes import c_short + +import numpy +import numpy.testing + +from integration_test.default.devices.base import TestDeviceBase + +from tango import DevState +from tangostationcontrol.common.constants import ( + N_beamlets_ctrl, + S_pn, + CLK_200_MHZ, + CLK_160_MHZ, + N_pn, + A_pn, +) + + +class BeamletDeviceTests(TestDeviceBase): + """Integration test class for device Beamlet""" + + __test__ = False + + def setUp( + self, + beamlet_name: str = None, + sdp_name: str = None, + sdpfirmware_name: str = None, + ): + """Intentionally recreate the device object in each test""" + + if beamlet_name is None or sdp_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(beamlet_name) + + self.sdp_proxy = self.setup_proxy(sdp_name, defaults=True) + self.sdp_proxy.nyquist_zone_RW = [[2] * S_pn] * N_pn + + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name, defaults=True) + self.sdpfirmware_proxy.clock_RW = CLK_200_MHZ + + def test_pointing_to_zenith(self): + self.proxy.initialise() + self.proxy.on() + + # The subband frequency of HBA subband 0 is 200 MHz, + # so its period is 5 ns. A delay of 2.5e-9 seconds is thus half a period, + # and result in a 180 degree phase offset. + delays = numpy.array([[[2.5e-9] * N_pn] * A_pn] * N_beamlets_ctrl) + + calculated_bf_weights = self.proxy.calculate_bf_weights(delays.flatten()) + + # With a unit weight of 2**14, we thus expect beamformer weights of -2**14 + 0j, + # which is 49152 when read as an uint32. + self.assertEqual(-(2**14), c_short(49152).value) # check our calculations + expected_bf_weights = numpy.array( + [49152] * N_pn * A_pn * N_beamlets_ctrl, dtype=numpy.uint32 + ) + + numpy.testing.assert_almost_equal(expected_bf_weights, calculated_bf_weights) + + def test_subband_select_change(self): + # Change subband + self.proxy.off() + self.proxy.initialise() + self.assertEqual(DevState.STANDBY, self.proxy.state()) + self.proxy.subband_select_RW = [10] * N_beamlets_ctrl + self.proxy.on() + self.assertEqual(DevState.ON, self.proxy.state()) + + # The subband frequency of HBA subband 10 is 201953125 Hz + # so its period is 4.95 ns ca, half period is 2.4758e-9 + delays = numpy.array([[[2.4758e-9] * N_pn] * A_pn] * N_beamlets_ctrl) + calculated_bf_weights_subband_10 = self.proxy.calculate_bf_weights( + delays.flatten() + ) + + self.assertEqual(-(2**14), c_short(49152).value) # check our calculations + expected_bf_weights_10 = numpy.array( + [49152] * N_pn * A_pn * N_beamlets_ctrl, dtype=numpy.uint32 + ) + numpy.testing.assert_almost_equal( + expected_bf_weights_10, calculated_bf_weights_subband_10 + ) + + def test_sdp_clock_change(self): + sdpfirmware_proxy = self.sdpfirmware_proxy + self.proxy.initialise() + self.proxy.subband_select_RW = numpy.array( + list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)), + dtype=numpy.uint32, + ) + self.proxy.on() + + # any non-zero delay should result in different weights for different clocks + delays = numpy.array([[[2.5e-9] * N_pn] * A_pn] * N_beamlets_ctrl) + + sdpfirmware_proxy.clock_RW = CLK_200_MHZ + time.sleep(3) # wait for beamlet device to process change event + calculated_bf_weights_200 = self.proxy.calculate_bf_weights(delays.flatten()) + + sdpfirmware_proxy.clock_RW = CLK_160_MHZ + time.sleep(3) # wait for beamlet device to process change event + calculated_bf_weights_160 = self.proxy.calculate_bf_weights(delays.flatten()) + + sdpfirmware_proxy.clock_RW = CLK_200_MHZ + time.sleep(3) # wait for beamlet device to process change event + calculated_bf_weights_200_v2 = self.proxy.calculate_bf_weights(delays.flatten()) + + # outcome should be changed back and forth across clock changes + self.assertTrue((calculated_bf_weights_200 != calculated_bf_weights_160).all()) + self.assertTrue( + (calculated_bf_weights_200 == calculated_bf_weights_200_v2).all() + ) + + # change subbands + self.proxy.off() + self.proxy.initialise() + self.proxy.subband_select_RW = [317] * N_beamlets_ctrl + self.proxy.on() + calculated_bf_weights_200_v3 = self.proxy.calculate_bf_weights(delays.flatten()) + self.assertTrue( + (calculated_bf_weights_200_v2 != calculated_bf_weights_200_v3).all() + ) + + sdpfirmware_proxy.clock_RW = CLK_160_MHZ + time.sleep(1) # wait for beamlet device to process change event + calculated_bf_weights_160_v2 = self.proxy.calculate_bf_weights(delays.flatten()) + self.assertTrue( + (calculated_bf_weights_160 != calculated_bf_weights_160_v2).all() + ) diff --git a/tangostationcontrol/integration_test/common/device_bst_tests.py b/tangostationcontrol/integration_test/common/device_bst_tests.py new file mode 100644 index 000000000..ac2b01e1f --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_bst_tests.py @@ -0,0 +1,27 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.default.devices.base import TestDeviceBase + + +class BSTDeviceTests(TestDeviceBase): + __test__ = False + + def setUp( + self, bst_name: str = None, sdpfirmware_name: str = None, sdp_name: str = None + ): + if bst_name is None or sdp_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + self.sdpfirmware_name = sdpfirmware_name + self.sdp_name = sdp_name + + """Intentionally recreate the device object in each test""" + super().setUp(bst_name) + + def test_device_read_all_attributes(self): + # We need to connect to SDP first to read some of our attributes + self.setup_proxy(self.sdpfirmware_name, defaults=True) + self.setup_proxy(self.sdp_name, defaults=True) + + super().test_device_read_all_attributes() diff --git a/tangostationcontrol/integration_test/common/device_digitalbeam_tests.py b/tangostationcontrol/integration_test/common/device_digitalbeam_tests.py new file mode 100644 index 000000000..4ec817c05 --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_digitalbeam_tests.py @@ -0,0 +1,252 @@ +# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import logging +import time + +import numpy +import timeout_decorator + +from integration_test.device_proxy import TestDeviceProxy +from integration_test.default.devices.base import TestDeviceBase +from tangostationcontrol.common.constants import ( + MAX_ANTENNA, + N_beamlets_ctrl, + N_pn, + A_pn, + CLK_200_MHZ, + CLK_160_MHZ, + CS001_TILES, +) +from tangostationcontrol.devices.base_device_classes.antennafield_device import ( + AntennaStatus, + AntennaUse, +) + +logger = logging.getLogger() + +# tests will break in weird ways if our constants change beyond these constraints +assert CS001_TILES >= 2 +assert MAX_ANTENNA >= 2 + + +class DigitalBeamDeviceTests(TestDeviceBase): + __test__ = False + + antenna_status_ok = numpy.array([AntennaStatus.OK] * MAX_ANTENNA) + antenna_status_only_second = numpy.array( + [AntennaStatus.BROKEN] + + [AntennaStatus.OK] + + [AntennaStatus.BROKEN] * (MAX_ANTENNA - 2) + ) + antenna_use_ok = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA) + + def setUp( + self, + tile_number: int = CS001_TILES, + digitalbeam_name: str = None, + antennafield_name: str = None, + beamlet_name: str = None, + sdp_name: str = None, + sdpfirmware_name: str = None, + recv_name: str = None, + ): + """Intentionally recreate the device object in each test""" + + if ( + digitalbeam_name is None + or antennafield_name is None + or beamlet_name is None + or sdp_name is None + or sdpfirmware_name is None + or recv_name is None + ): + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(digitalbeam_name) + + self.tile_number = tile_number + + self.recv_proxy = self.setup_proxy(recv_name, defaults=True) + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name, defaults=True) + self.sdp_proxy = self.setup_proxy(sdp_name) + self.beamlet_proxy = self.initialise_beamlet_proxy(beamlet_name) + + control_mapping = [[1, i] for i in range(tile_number)] + sdp_mapping = [[i // 6, i % 6] for i in range(tile_number)] + self.antennafield_proxy = self.setup_proxy( + antennafield_name, + cb=lambda x: { + x.put_property( + { + "Control_to_RECV_mapping": numpy.array( + control_mapping + ).flatten(), + "Antenna_to_SDP_Mapping": numpy.array(sdp_mapping).flatten(), + "Antenna_Status": self.antenna_status_ok, + "Antenna_Use": self.antenna_use_ok, + "Antenna_Cables": ["50m", "80m"] * (tile_number // 2), + "Antenna_Sets": ["FIRST", "ALL"], + "Antenna_Set_Masks": [ + "1" + ("0" * (tile_number - 1)), + "1" * tile_number, + ], + "Antenna_Type": "HBA", + } + ) + }, + ) + + self.addCleanup(TestDeviceProxy.test_device_turn_off, beamlet_name) + + def initialise_beamlet_proxy(self, name: str = None): + if not name and self.beamlet_proxy: + beamlet_proxy = self.beamlet_proxy + else: + beamlet_proxy = TestDeviceProxy(name) + + beamlet_proxy.off() + beamlet_proxy.initialise() + return beamlet_proxy + + def test_pointing_to_zenith_clock_change(self): + self.beamlet_proxy.on() + + # Set first (default) clock configuration + self.sdpfirmware_proxy.clock_RW = CLK_200_MHZ + time.sleep(1) + + self.proxy.initialise() + self.proxy.Tracking_enabled_RW = False + self.proxy.on() + + # Point to Zenith + self.proxy.set_pointing( + numpy.array( + [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R + ).flatten() + ) + + # beam weights should now be non-zero, we don't actually check their values for correctness + FPGA_bf_weights_pp_clock200 = self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() + self.assertNotEqual(0, sum(FPGA_bf_weights_pp_clock200)) + + self.beamlet_proxy = self.initialise_beamlet_proxy() + self.beamlet_proxy.on() + + # Change clock configuration + self.sdpfirmware_proxy.clock_RW = CLK_160_MHZ + time.sleep(1) + + FPGA_bf_weights_pp_clock160 = self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() + # Assert some values are different + self.assertNotEqual( + sum(FPGA_bf_weights_pp_clock160), sum(FPGA_bf_weights_pp_clock200) + ) + + def test_pointing_to_zenith_subband_change(self): + self.beamlet_proxy.subband_select_RW = numpy.array( + list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)), + dtype=numpy.uint32, + ) + self.beamlet_proxy.on() + + self.proxy.initialise() + self.proxy.Tracking_enabled_RW = False + self.proxy.on() + + # Point to Zenith + self.proxy.set_pointing( + numpy.array( + [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R + ).flatten() + ) + # Store values with first subband configuration + FPGA_bf_weights_pp_subband_v1 = ( + self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() + ) + + # Restart beamlet proxy + self.beamlet_proxy = self.initialise_beamlet_proxy() + self.beamlet_proxy.subband_select_RW = [317] * N_beamlets_ctrl + self.beamlet_proxy.on() + + # Store values with second subband configuration + FPGA_bf_weights_pp_subband_v2 = ( + self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() + ) + # Assert some values are different + self.assertNotEqual( + sum(FPGA_bf_weights_pp_subband_v1), sum(FPGA_bf_weights_pp_subband_v2) + ) + + def test_set_pointing_masked_enable(self): + """Verify that only selected inputs are written""" + + self.proxy.initialise() + self.proxy.Tracking_enabled_RW = False + self.proxy.on() + + # Enable first input + self.proxy.Antenna_Set_RW = "FIRST" + + # fill weights with values the beamformer will never use (|x| > 1) + impossible_values = numpy.array([[2] * N_beamlets_ctrl * A_pn] * N_pn) + self.beamlet_proxy.FPGA_bf_weights_pp_RW = impossible_values + + self.proxy.set_pointing( + numpy.array( + [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R + ).flatten() + ) + + # Verify all impossible values are replaced with other values for all inputs + # which should be non-zero for the first antenna, and zero for the rest + FPGA_bf_weights_pp_RW = self.beamlet_proxy.FPGA_bf_weights_pp_RW.reshape( + (N_pn * A_pn, -1) + ) + + # first antenna should have values from the beamformer, so not 0 and not 2 + self.assertTrue( + numpy.all(numpy.not_equal(0, FPGA_bf_weights_pp_RW[0, :])), + f"{FPGA_bf_weights_pp_RW}", + ) + self.assertTrue( + numpy.all(numpy.not_equal(2, FPGA_bf_weights_pp_RW[0, :])), + f"{FPGA_bf_weights_pp_RW}", + ) + + # rest of the antennas should have been given a weight of 0, as they + # were not in the usage mask. + self.assertTrue( + numpy.all(numpy.equal(0, FPGA_bf_weights_pp_RW[1 : self.tile_number, :])), + f"{FPGA_bf_weights_pp_RW}", + ) + + @timeout_decorator.timeout(15) + def test_beam_tracking_90_percent_interval(self): + """Verify that the beam tracking operates within 95% of interval""" + + self.proxy.initialise() + self.proxy.Tracking_enabled_RW = True + self.proxy.on() + + interval = float( + self.proxy.get_property("Beam_tracking_interval")["Beam_tracking_interval"][ + 0 + ] + ) + + # Allow beam tracking time to settle + time.sleep(interval * 3) + + # We have to poll at regular interval due to not working subscribe + # events + for _ in range(0, 5): + error = self.proxy.Pointing_error_R[0] + self.assertTrue( + -interval * 0.10 < error < interval * 0.10, + f"Error: {error} larger than {interval * 0.10}", + ) + logger.info("BeamTracking error: %s", error) + time.sleep(interval) diff --git a/tangostationcontrol/integration_test/common/device_hba_tests.py b/tangostationcontrol/integration_test/common/device_hba_tests.py new file mode 100644 index 000000000..6a77499c2 --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_hba_tests.py @@ -0,0 +1,410 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import logging +import time +import numpy + +from integration_test.default.devices.base import TestDeviceBase +from tango import DevState + +from tangostationcontrol.common.constants import ( + N_elements, + MAX_ANTENNA, + N_pol, + N_rcu, + N_rcu_inp, + CS001_TILES, + CLK_160_MHZ, + N_pn, + S_pn, +) +from tangostationcontrol.common.frequency_bands import bands +from tangostationcontrol.devices.base_device_classes.antennafield_device import ( + AntennaStatus, + AntennaUse, +) + +# tests will break in weird ways if our constants change beyond these constraints +assert CS001_TILES >= 2 +assert N_pn * S_pn >= 4 +assert N_rcu_inp * N_rcu >= 2 + +logger = logging.getLogger() + + +class HBADeviceTests(TestDeviceBase): + """Integration base test class for device HBA""" + + __test__ = False + + def setUp( + self, + tiles: int = CS001_TILES, + afh_name: str = None, + sdpfirmware_name: str = None, + sdp_name: str = None, + ): + if afh_name is None or sdp_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + self.tiles = tiles + + self.stationmanager_proxy = self.setup_proxy("STAT/StationManager/1") + + # Setup will dump current properties and restore them for us + super().setUp(afh_name) + + # Typical tests emulate 'CS001_TILES' number of antennas in + # the AntennaField. 'MAX_ANTENNA' is the number of inputs + # offered by a backing RECV device. Each antenna has + # N_pol (2) inputs. + self.proxy.put_property( + { + "Power_to_RECV_mapping": numpy.array( + [[1, x * 2 + 0] for x in range(tiles)] + ).flatten(), + "Control_to_RECV_mapping": numpy.array( + [[1, x * 2 + 1] for x in range(tiles)] + ).flatten(), + "Frequency_Band_RW_default": numpy.array( + [["HBA_110_190", "HBA_110_190"]] * tiles + ).flatten(), + } + ) + self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True) + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name) + self.sdp_proxy = self.setup_proxy(sdp_name) + + # configure the frequencies, which allows access + # to the calibration attributes and commands + self.sdpfirmware_proxy.clock_RW = CLK_160_MHZ + self.recv_proxy.RCU_band_select_RW = [[1] * N_rcu_inp] * N_rcu + + def test_ANT_mask_RW_configured_after_Antenna_Usage_Mask(self): + """Verify if ANT_mask_RW values are correctly configured from Antenna_Usage_Mask values""" + + antennafield_proxy = self.proxy + numpy.testing.assert_equal( + numpy.array([[True] * N_rcu_inp] * N_rcu), self.recv_proxy.ANT_mask_RW + ) + + antenna_status = numpy.array([AntennaStatus.OK] * self.tiles) + antenna_use = numpy.array( + [AntennaUse.ON] + [AntennaUse.AUTO] * (self.tiles - 1) + ) + antenna_properties = { + "Antenna_Status": antenna_status, + "Antenna_Use": antenna_use, + } + mapping_properties = { + "Power_to_RECV_mapping": [-1, -1] * self.tiles, + # Two inputs of recv device connected, only defined for 48 inputs + # each pair is one input + "Control_to_RECV_mapping": [1, 0, 1, 1] + [-1, -1] * (self.tiles - 2), + } + antennafield_proxy.off() + antennafield_proxy.put_property(antenna_properties) + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + antennafield_proxy.power_hardware_on() + + # Verify all antennas are indicated to work + numpy.testing.assert_equal( + numpy.array([True] * self.tiles), antennafield_proxy.Antenna_Usage_Mask_R + ) + + # Verify only connected inputs + Antenna_Usage_Mask_R are true + # As well as dimensions of ANT_mask_RW must match control mapping + numpy.testing.assert_equal( + numpy.array([True] * 2 + [False] * (self.tiles - 2)), + antennafield_proxy.ANT_mask_RW, + ) + + # Verify recv proxy values unaffected as default for ANT_mask_RW is true + numpy.testing.assert_equal( + numpy.array([True] * 2 + [True] * (MAX_ANTENNA - 2)), + self.recv_proxy.ANT_mask_RW.flatten(), + ) + + def test_ANT_mask_RW_configured_after_Antenna_Usage_Mask_only_one_functioning_antenna( + self, + ): + """Verify if ANT_mask_RW values are correctly configured from + Antenna_Usage_Mask values (only second antenna is OK)""" + + antennafield_proxy = self.proxy + + # Broken antennas except second + antenna_status = numpy.array( + [AntennaStatus.BROKEN] + + [AntennaStatus.OK] + + [AntennaStatus.BROKEN] * (self.tiles - 2) + ) + antenna_use = numpy.array([AntennaUse.AUTO] * self.tiles) + antenna_properties = { + "Antenna_Status": antenna_status, + "Antenna_Use": antenna_use, + } + + # Configure control mapping to control all 96 inputs of recv device + mapping_properties = { + "Power_to_RECV_mapping": [-1, -1] * self.tiles, + "Control_to_RECV_mapping": + # [1, 0, 1, 1, 1, 2, 1, x ... 1, 95] + numpy.array([[1, x] for x in range(self.tiles)]).flatten(), + } + + # Cycle device and set properties + antennafield_proxy.off() + antennafield_proxy.put_property(antenna_properties) + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + antennafield_proxy.power_hardware_on() + + # Antenna_Usage_Mask_R should be false except one + numpy.testing.assert_equal( + numpy.array([False] + [True] + [False] * (self.tiles - 2)), + antennafield_proxy.Antenna_Usage_Mask_R, + ) + # device.power_hardware_on() writes Antenna_Usage_Mask_R to ANT_mask_RW + numpy.testing.assert_equal( + numpy.array([False] + [True] + [False] * (self.tiles - 2)), + antennafield_proxy.ANT_mask_RW, + ) + # ANT_mask_RW on antennafield writes to configured recv devices for all + # mapped inputs. Unmapped values at the end remain at True. + numpy.testing.assert_equal( + numpy.array( + [False] + + [True] + + [False] * (self.tiles - 2) + + [True] * (MAX_ANTENNA - self.tiles) + ), + self.recv_proxy.ANT_mask_RW.flatten(), + ) + + def test_antennafield_set_mapped_attribute_ignore_all(self): + """Verify RECV device attribute unaffected by antennafield if not mapped""" + + # Connect recvh/1 device but no control inputs + mapping_properties = { + "Power_to_RECV_mapping": [-1, -1] * self.tiles, + "Control_to_RECV_mapping": [-1, -1] * self.tiles, + } + + # Cycle device an put properties + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + + # Set HBAT_PWR_on_RW to false on recv device and read results + self.recv_proxy.write_attribute( + "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA + ) + current_values = self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value + + # write true through antennafield + antennafield_proxy.write_attribute( + "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * self.tiles + ) + # Test that original recv values for HBAT_PWR_on_RW match current + numpy.testing.assert_equal( + current_values, self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value + ) + + # Verify device did not enter FAULT state + self.assertEqual(DevState.ON, antennafield_proxy.state()) + + def test_antennafield_set_mapped_attribute(self): + """Verify RECV device attribute changed by antennafield if mapped inputs""" + + mapping_properties = { + "Power_to_RECV_mapping": [-1, -1] * self.tiles, + # Each pair is one mapping so 2 inputs are connected + "Control_to_RECV_mapping": [1, 0, 1, 1] + [-1, -1] * (self.tiles - 2), + } + + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + + self.recv_proxy.write_attribute( + "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA + ) + + try: + antennafield_proxy.write_attribute( + "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * self.tiles + ) + numpy.testing.assert_equal( + numpy.array( + [[True] * N_elements * N_pol] * 2 + + [[False] * N_elements * N_pol] * (MAX_ANTENNA - 2) + ), + self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value, + ) + finally: + # Always restore recv again + self.recv_proxy.write_attribute( + "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA + ) + + # Verify device did not enter FAULT state + self.assertEqual(DevState.ON, antennafield_proxy.state()) + + def test_antennafield_set_mapped_attribute_all(self): + """Verify RECV device attribute changed by antennafield all inputs mapped""" + + mapping_properties = { + "Power_to_RECV_mapping": [-1, -1] * self.tiles, + "Control_to_RECV_mapping": + # [1, 0, 1, 1, 1, 2, 1, x ... 1, 95] + numpy.array([[1, x] for x in range(self.tiles)]).flatten(), + } + + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + + self.recv_proxy.write_attribute( + "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA + ) + + try: + antennafield_proxy.write_attribute( + "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * self.tiles + ) + + # Mapped values went to True + numpy.testing.assert_equal( + numpy.array([[True] * N_elements * N_pol] * self.tiles), + self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value[: self.tiles], + ) + # Unmapped values went to False + numpy.testing.assert_equal( + numpy.array( + [[False] * N_elements * N_pol] * (MAX_ANTENNA - self.tiles) + ), + self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value[self.tiles :], + ) + finally: + # Always restore recv again + self.recv_proxy.write_attribute( + "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA + ) + + # Verify device did not enter FAULT state + self.assertEqual(DevState.ON, antennafield_proxy.state()) + + def test_antennafield_set_mapped_attribute_small(self): + """Verify small RECV device attribute changed all inputs mapped""" + + mapping_properties = { + "Power_to_RECV_mapping": numpy.array( # X maps on power + [[1, x * 2 + 1] for x in range(self.tiles)] + ).flatten(), + "Control_to_RECV_mapping": numpy.array( # Y maps on control + [[1, x * 2 + 0] for x in range(self.tiles)] + ).flatten(), + } + + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(mapping_properties) + antennafield_proxy.boot() + + self.recv_proxy.write_attribute("RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu) + + try: + antennafield_proxy.write_attribute( + # [X, Y] + "RCU_band_select_RW", + [[1, 2]] * self.tiles, + ) + + # Mapped values were overwritten + numpy.testing.assert_equal( + # [Power, Control] + numpy.array([2, 1] * self.tiles), + self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten()[ + : (self.tiles * 2) + ], + ) + # Unmapped values remain at 0 + numpy.testing.assert_equal( + # [Power, Control] + numpy.array([0] * (MAX_ANTENNA - self.tiles * 2)), + self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten()[ + (self.tiles * 2) : + ], + ) + finally: + # Always restore recv again + self.recv_proxy.write_attribute( + "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu + ) + + # Verify device did not enter FAULT state + self.assertEqual(DevState.ON, antennafield_proxy.state()) + + def test_frequency_band(self): + # Test whether changes in frequency band propagate properly. + VALID_MODI = ["HBA_110_190", "HBA_170_230", "HBA_210_250"] + + properties = { + "Control_to_RECV_mapping": [1, 1] + [-1, -1] * (self.tiles - 1), + "Power_to_RECV_mapping": [1, 0] + [-1, -1] * (self.tiles - 1), + "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + [-1, -1] * (self.tiles - 2), + } + + antennafield_proxy = self.proxy + antennafield_proxy.off() + antennafield_proxy.put_property(properties) + antennafield_proxy.boot() + + for band in [b for b in bands.values() if b.name in VALID_MODI]: + # clear downstream settings + self.recv_proxy.write_attribute( + "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu + ) + self.sdp_proxy.write_attribute("nyquist_zone_RW", [[0] * S_pn] * N_pn) + + antennafield_proxy.write_attribute( + "Frequency_Band_RW", [[band.name, band.name]] * self.tiles + ) + # Wait for Tango attributes updating + time.sleep(1) + + # test whether clock propagated correctly + numpy.testing.assert_equal( + band.clock, self.sdpfirmware_proxy.clock_RW, err_msg=f"{band.name}" + ) + + # test whether nyquist zone propagated correctly (for both polarisations!) + numpy.testing.assert_equal( + numpy.array( + [band.nyquist_zone, band.nyquist_zone] * 2 + [0] * (N_pn * S_pn - 4) + ), + self.sdp_proxy.read_attribute("nyquist_zone_RW").value.flatten(), + err_msg=f"{band.name}", + ) + + # test whether rcu filter propagated correctly + numpy.testing.assert_equal( + numpy.array( + [band.rcu_band, band.rcu_band] + [0] * (N_rcu_inp * N_rcu - 2) + ), + self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten(), + err_msg=f"{band.name}", + ) + + # test whether reading back results in the same band for our inputs + numpy.testing.assert_equal( + numpy.array([band.name, band.name]), + antennafield_proxy.read_attribute("Frequency_Band_RW").value[0], + err_msg=f"{band.name}", + ) diff --git a/tangostationcontrol/integration_test/common/device_observation_control_tests.py b/tangostationcontrol/integration_test/common/device_observation_control_tests.py new file mode 100644 index 000000000..1e7caa9dc --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_observation_control_tests.py @@ -0,0 +1,308 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import json +import logging +import time +from datetime import datetime +from datetime import timedelta + +import numpy +from integration_test.default.devices.base import TestDeviceBase +from tango import DevFailed, DevState + +from tangostationcontrol.common.constants import CS001_TILES +from timeout_decorator import timeout_decorator + +logger = logging.getLogger() + + +class DeviceObservationControlTests(TestDeviceBase): + __test__ = False + + def setUp( + self, + tile_number: int = CS001_TILES, + hba_immediate_json: str = None, + antennafield_name: str = None, + recv_name: str = None, + sdp_name: str = None, + sdpfirmware_name: str = None, + beamlet_name: str = None, + digitalbeam_name: str = None, + tilebeam_name: str = None, + sst_name: str = None, + xst_name: str = None, + bst_name: str = None, + ): + super().setUp("STAT/ObservationControl/1") + + if hba_immediate_json is None: + raise RuntimeError("JSON specification for immediate observation required") + + self.valid_json = hba_immediate_json + self.expected_obs_id = json.loads(self.valid_json)["antenna_fields"][0][ + "observation_id" + ] + self.expected_antenna_field = json.loads(self.valid_json)["antenna_fields"][0][ + "antenna_field" + ] + + if ( + antennafield_name is None + or recv_name is None + or sdp_name is None + or sdpfirmware_name is None + or beamlet_name is None + or digitalbeam_name is None + or tilebeam_name is None + or sst_name is None + or xst_name is None + or bst_name is None + ): + raise RuntimeError("Missing required device names to initialize tests") + + self.recv_proxy = self.setup_proxy(recv_name, defaults=True) + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name) + self.sdp_proxy = self.setup_proxy(sdp_name) + + self.metadata_proxy = self.setup_proxy("STAT/Metadata/1") + + control_mapping = [[1, i] for i in range(tile_number)] + sdp_mapping = [[i // 6, i % 6] for i in range(tile_number)] + self.antennafield_proxy = self.setup_proxy( + antennafield_name, + defaults=True, + restore_properties=True, + cb=lambda x: { + x.put_property( + { + "Antenna_Set": "ALL", + "Power_to_RECV_mapping": numpy.array(control_mapping).flatten(), + "Antenna_to_SDP_Mapping": numpy.array(sdp_mapping).flatten(), + } + ) + }, + ) + + self.beamlet_proxy = self.setup_proxy(beamlet_name, defaults=True) + self.digitalbeam_proxy = self.setup_proxy(digitalbeam_name, defaults=True) + self.tilebeam_proxy = self.setup_proxy(tilebeam_name, defaults=True) + self.sst_proxy = self.setup_proxy(sst_name, defaults=True) + self.xst_proxy = self.setup_proxy(xst_name, defaults=True) + self.bst_proxy = self.setup_proxy(bst_name, defaults=True) + + def on_device_assert(self, proxy): + """Transition the device to ON and assert intermediate states + + This will repeatedly call ``stop_all_observations_now`` in turn calling + ``_destroy_all_observation_field_devices`` cleaning the Database from exported + devices + """ + + 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(self.expected_obs_id)) + self.assertFalse(self.proxy.is_observation_running(54321)) + + def test_add_observation_now(self): + """Test starting an observation now""" + + self.on_device_assert(self.proxy) + + # Integration test JSON has no start time so will start immediately even when + # using `add_observation` + self.proxy.add_observation(self.valid_json) + + self.assertTrue(self.proxy.is_any_observation_running()) + self.assertTrue(self.proxy.is_observation_running(self.expected_obs_id)) + self.assertTrue(self.proxy.is_antenna_field_active(self.expected_antenna_field)) + + self.proxy.stop_observation_now(self.expected_obs_id) + + self.assertFalse(self.proxy.is_any_observation_running()) + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) + self.assertFalse( + self.proxy.is_antenna_field_active(self.expected_antenna_field) + ) + + def test_add_observation_future(self): + """Test starting an observation in the future""" + + self.on_device_assert(self.proxy) + + parameters = json.loads(self.valid_json) + for antenna_field in parameters["antenna_fields"]: + antenna_field["start_time"] = ( + datetime.now() + timedelta(days=1) + ).isoformat() + antenna_field["stop_time"] = ( + datetime.now() + timedelta(days=2) + ).isoformat() + + self.proxy.add_observation(json.dumps(parameters)) + + self.assertIn(self.expected_obs_id, self.proxy.observations_R) + self.assertNotIn(self.expected_obs_id, self.proxy.running_observations_R) + self.assertFalse(self.proxy.is_any_observation_running()) + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) + + self.proxy.stop_observation_now(self.expected_obs_id) + + def test_add_observation_multiple(self): + """Test starting multiple observations""" + + second_observation_json = json.loads(self.valid_json) + second_observation_json["antenna_fields"][0]["observation_id"] = 54321 + + self.on_device_assert(self.proxy) + + self.proxy.add_observation(self.valid_json) + self.proxy.add_observation(json.dumps(second_observation_json)) + + self.assertTrue(self.proxy.is_any_observation_running()) + self.assertTrue(self.proxy.is_observation_running(self.expected_obs_id)) + self.assertTrue(self.proxy.is_observation_running(54321)) + + self.proxy.stop_observation_now(self.expected_obs_id) + self.proxy.stop_observation_now(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_now, -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_now, 2) + + def test_is_any_observation_running_after_stop_all_observations_now(self): + """Test whether is_any_observation_running conforms when we start & stop an observation""" + + self.on_device_assert(self.proxy) + + self.proxy.add_observation(self.valid_json) + self.proxy.stop_all_observations_now() + + # Test false + self.assertFalse(self.proxy.is_any_observation_running()) + + def test_start_stop_observation_now(self): + """Test starting and stopping an observation""" + + self.on_device_assert(self.proxy) + + # uses ID 12345 + self.proxy.add_observation(self.valid_json) + self.proxy.stop_observation_now(self.expected_obs_id) + + # Test false + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) + + 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["antenna_fields"][0]["observation_id"] = 54321 + + self.on_device_assert(self.proxy) + + # uses ID 5 + self.proxy.add_observation(self.valid_json) + self.proxy.add_observation(json.dumps(second_observation_json)) + self.proxy.stop_all_observations_now() + + # Test false + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) + 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) + for station in parameters["antenna_fields"]: + station["observation_id"] = -1 + + self.on_device_assert(self.proxy) + self.assertRaises(DevFailed, self.proxy.add_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.add_observation, "{}") + + def test_check_and_convert_parameters_invalid_antenna_set(self): + """Test invalid antenna set name""" + parameters = json.loads(self.valid_json) + for station in parameters["antenna_fields"]: + station["antenna_set"] = "ZZZ" + self.on_device_assert(self.proxy) + self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters)) + + def test_check_and_convert_parameters_invalid_time(self): + """Test invalid parameter detection""" + + parameters = json.loads(self.valid_json) + for antenna_field in parameters["antenna_fields"]: + antenna_field["stop_time"] = ( + datetime.now() - timedelta(seconds=1) + ).isoformat() + + self.on_device_assert(self.proxy) + self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters)) + + @timeout_decorator.timeout(60) + def test_add_observation_callbacks(self): + """Test adding an observation and checking start / stop callbacks work""" + + self.on_device_assert(self.proxy) + + parameters = json.loads(self.valid_json) + for antenna_field in parameters["antenna_fields"]: + antenna_field["start_time"] = ( + datetime.now() + timedelta(seconds=5) + ).isoformat() + antenna_field["stop_time"] = ( + datetime.now() + timedelta(seconds=20) + ).isoformat() + + self.proxy.add_observation(json.dumps(parameters)) + + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) + + time.sleep(5) + + while not self.proxy.is_observation_running(self.expected_obs_id): + logging.info("Waiting for observation to start...") + time.sleep(0.1) + + self.assertTrue(self.proxy.is_observation_running(self.expected_obs_id)) + self.assertGreater(self.metadata_proxy.messages_published_R, 0) + + time.sleep(20) + + while self.proxy.is_observation_running(self.expected_obs_id): + logging.info("Waiting for observation to stop...") + time.sleep(0.1) + + self.assertFalse(self.proxy.is_observation_running(self.expected_obs_id)) diff --git a/tangostationcontrol/integration_test/common/device_sdp_tests.py b/tangostationcontrol/integration_test/common/device_sdp_tests.py new file mode 100644 index 000000000..7284cd887 --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_sdp_tests.py @@ -0,0 +1,18 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.default.devices.base import TestDeviceBase + + +class SDPDeviceTests(TestDeviceBase): + __test__ = False + + def setUp(self, sdp_name: str = None, sdpfirmware_name: str = None): + """Intentionally recreate the device object in each test""" + + if sdp_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(sdp_name) + + self.setup_proxy(sdpfirmware_name) diff --git a/tangostationcontrol/integration_test/common/device_sdpfirmware_tests.py b/tangostationcontrol/integration_test/common/device_sdpfirmware_tests.py new file mode 100644 index 000000000..84ed6791d --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_sdpfirmware_tests.py @@ -0,0 +1,28 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.default.devices.base import TestDeviceBase + +from tangostationcontrol.common.constants import N_pn +from tangostationcontrol.common.env_decorators import ensure_device_boots + + +class SDPFirmwareDeviceTests(TestDeviceBase): + """Integration test class for device SDPFirmware""" + + __test__ = False + + def setUp(self, sdpfirmware_name: str = None): + """Intentionally recreate the device object in each test""" + + if sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(sdpfirmware_name) + + @ensure_device_boots() + def test_device_sdpfirmware_read_attribute(self): + """Test if we can read an attribute obtained over OPC-UA""" + self.assertListEqual( + [True] * N_pn, list(self.proxy.TR_fpga_communication_error_R) + ) diff --git a/tangostationcontrol/integration_test/common/device_sst_tests.py b/tangostationcontrol/integration_test/common/device_sst_tests.py new file mode 100644 index 000000000..8a42e103c --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_sst_tests.py @@ -0,0 +1,73 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import socket +import sys +import time + +from integration_test.default.devices.base import TestDeviceBase +from tangostationcontrol.common.env_decorators import ensure_device_boots + +from tango import DevState + + +class SSTDeviceTests(TestDeviceBase): + """Integration test class for device SST""" + + __test__ = False + + UDP_PORT = 5011 + TCP_PORT = 5111 + + def setUp(self, sst_name: str = None, sdpfirmware_name: str = None): + """Intentionally recreate the device object in each test""" + + if sst_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(sst_name) + + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name, defaults=True) + + @ensure_device_boots() + def test_device_sst_send_udp(self): + port_property = {"Statistics_Client_TCP_Port": "4998"} + self.proxy.put_property(port_property) + + self.assertEqual(DevState.ON, self.proxy.state()) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s1.connect(("device-sst.service.consul", 5001)) + + # TODO(Corne): Change me into an actual SST packet + s1.send("Hello World!".encode("UTF-8")) + + s1.close() + + @ensure_device_boots() + def test_device_sst_connect_tcp_receive(self): + port_property = {"Statistics_Client_TCP_Port": "5111"} + self.proxy.put_property(port_property) + + time.sleep(2) + + s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s1.connect(("device-sst.service.consul", self.UDP_PORT)) + + s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s2.connect(("device-sst.service.consul", self.TCP_PORT)) + + time.sleep(2) + + # TODO(Corne): Change me into an actual SST packet + m_data = "Hello World!".encode("UTF-8") + s1.send(m_data) + + time.sleep(2) + + data = s2.recv(sys.getsizeof(m_data)) + + s1.close() + s2.close() + + self.assertEqual(m_data, data) diff --git a/tangostationcontrol/integration_test/common/device_tilebeam_tests.py b/tangostationcontrol/integration_test/common/device_tilebeam_tests.py new file mode 100644 index 000000000..bc71c941a --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_tilebeam_tests.py @@ -0,0 +1,280 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +import logging +import datetime +import json +import time + +import numpy +import timeout_decorator + +from integration_test.device_proxy import TestDeviceProxy +from integration_test.default.devices.base import TestDeviceBase + +from tangostationcontrol.common.constants import ( + CS001_TILES, + MAX_ANTENNA, + N_elements, + N_pol, +) +from tangostationcontrol.devices.base_device_classes.antennafield_device import ( + AntennaStatus, + AntennaUse, +) +from tangostationcontrol.common.env_decorators import ( + ensure_device_boots, +) + +logger = logging.getLogger() + + +class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, numpy.ndarray): + return obj.tolist() + return json.JSONEncoder.default(self, obj) + + +class TileBeamDeviceTests(TestDeviceBase): + """Integration test class for device Tilebeam""" + + __test__ = False + + longMessage = True + + # HBA0 / HBA1 + LOFAR_REF_POINTING = [24, 25, 27, 29, 17, 18, 20, 21, 10, 11, 13, 14, 3, 4, 5, 7] + LOFAR_REF_REPEATS = 2 + + def setUp( + self, + tile_number: int = CS001_TILES, + tilebeam_name: str = None, + antennafield_name: str = None, + sdp_name: str = None, + sdpfirmware_name: str = None, + recv_name: str = None, + ): + """Setup Tilebeam""" + + if ( + tilebeam_name is None + or antennafield_name is None + or sdp_name is None + or sdpfirmware_name is None + or recv_name is None + ): + raise RuntimeError("Missing required device names to initialize tests") + + self.pointing_direction = numpy.array( + [["J2000", "0rad", "0rad"]] * tile_number + ).flatten() + + super().setUp(tilebeam_name) + + self.tiles = tile_number + + self.station_manager_proxy = self.setup_proxy("STAT/StationManager/1") + self.recv_proxy = self.setup_proxy(recv_name, defaults=True) + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name, defaults=True) + self.sdp_proxy = self.setup_proxy(sdp_name, defaults=True) + self.antennafield_proxy = self.setup_proxy( + antennafield_name, + cb=self.setup_antennafield_property, + restore_properties=True, + ) + + # check if AntennaField really exposes the expected number of tiles + self.assertEqual(self.tiles, self.antennafield_proxy.nr_antennas_R) + + def setup_antennafield_property(self, proxy: TestDeviceProxy): + """Setup AntennaField""" + control_mapping = [[1, i] for i in range(self.tiles)] + antenna_status = numpy.array([AntennaStatus.OK] * MAX_ANTENNA) + antenna_use = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA) + proxy.put_property( + { + "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(), + "Antenna_Status": antenna_status, + "Antenna_Use": antenna_use, + } + ) + + # check if AntennaField really exposes the expected number of tiles + self.assertEqual(self.tiles, proxy.nr_antennas_R) + + @ensure_device_boots() + def test_delays_dims(self): + """Verify delays are retrieved with correct dimensions""" + delays = self.proxy.delays(self.pointing_direction) + self.assertEqual(self.tiles * N_elements, len(delays)) + + @ensure_device_boots() + def test_set_pointing(self): + """Verify if set pointing procedure is correctly executed""" + # setup BEAM + self.proxy.Tracking_enabled_RW = False + + # Verify attribute is present (all zeros if never used before) + delays_r1 = numpy.array( + self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value + ) + + self.assertIsNotNone(delays_r1) + + time.sleep(3) + + # Verify writing operation does not lead to errors + self.proxy.set_pointing(self.pointing_direction) # write values to RECV + delays_r2 = numpy.array( + self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value + ) + + self.assertIsNotNone(delays_r2) + + # Verify delays changed (to be discussed) + # self.assertFalse((delays_r1==delays_r2).all()) + + @ensure_device_boots() + def test_pointing_to_zenith(self): + self.proxy.Tracking_enabled_RW = False + + # Point to Zenith + self.proxy.set_pointing( + numpy.array([["AZELGEO", "0rad", "1.570796rad"]] * self.tiles).flatten() + ) + + calculated_HBAT_delay_steps = numpy.array( + self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value + ) + + expected_HBAT_delay_steps = numpy.array( + [[15] * N_elements * N_pol] * self.tiles, dtype=numpy.int64 + ) + + # Accept values of 14, 15 and 16 + numpy.testing.assert_allclose( + calculated_HBAT_delay_steps, expected_HBAT_delay_steps, rtol=0.066, atol=1 + ) + + @ensure_device_boots() + def test_pointing_across_horizon(self): + antennafield_proxy = self.antennafield_proxy + + self.proxy.Tracking_enabled_RW = False + + # point at north on the horizon + self.proxy.set_pointing(["AZELGEO", "0rad", "0rad"] * self.tiles) + + # obtain delays of the X polarisation of all the elements of the first tile + north_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[ + 0 + ].reshape(4, 4, 2)[:, :, 0] + + # delays must differ under rotation, or our test will give a false positive + self.assertNotEqual( + north_beam_delay_steps.tolist(), + numpy.rot90(north_beam_delay_steps).tolist(), + ) + # 90, 180 and 270 degrees + for angle in (1.570796, 3.141593, 4.712389): + # point at angle degrees (90=E, 180=S, 270=W) + self.proxy.set_pointing(["AZELGEO", f"{angle}rad", "0rad"] * self.tiles) + + # obtain delays of the X polarisation of all the elements of the first tile + angled_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[ + 0 + ].reshape(4, 4, 2)[:, :, 0] + + expected_delay_steps = numpy.rot90( + north_beam_delay_steps, k=-(int((angle * 180) / numpy.pi) / 90) + ) + + self.assertListEqual( + expected_delay_steps.tolist(), + angled_beam_delay_steps.tolist(), + msg=f"angle={angle}", + ) + + @ensure_device_boots() + def test_delays_same_as_LOFAR_ref_pointing(self): + self.proxy.Tracking_enabled_RW = False + + # Point to LOFAR 1 ref pointing (0.929342, 0.952579, J2000) + pointings = numpy.array( + [["J2000", "0.929342rad", "0.952579rad"]] * self.tiles + ).flatten() + # Need to set the time to '2022-01-18 11:19:35' + timestamp = datetime.datetime(2022, 1, 18, 11, 19, 35).timestamp() + + parameters = {"pointing_direction": pointings, "timestamp": timestamp} + + json_string = json.dumps(parameters, cls=NumpyEncoder) + self.proxy.set_pointing_for_specific_time(json_string) + + calculated_HBAT_delay_steps = numpy.array( + self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value + ) # dims (self.tiles, 32) + + # Check all delay steps are zero with small margin + # [24, 25, 27, 28, 17, 18, 20, 21, 10, 11, 13, 14, 3, 4, 5, 7] These are the real values from LOFAR. + # The [3] = 28 diff is explained that we match the closest delay step and LOFAR 1 wants the one with + # in 0.2ns but if it can't it will do a int(delay / 0.5ns) so we get slightly different results but + # they can be explained. + expected_HBAT_delay_steps = numpy.repeat( + numpy.array( + self.LOFAR_REF_POINTING, + dtype=numpy.int64, + ), + repeats=self.LOFAR_REF_REPEATS, + ) + numpy.testing.assert_equal( + calculated_HBAT_delay_steps[0], expected_HBAT_delay_steps + ) + numpy.testing.assert_equal( + calculated_HBAT_delay_steps[self.tiles - 1], + expected_HBAT_delay_steps, + ) + + @ensure_device_boots() + def test_tilebeam_tracking(self): + # check if we're really tracking + self.assertTrue(self.proxy.Tracking_enabled_R) + + # point somewhere + new_pointings = [("J2000", f"{tile}rad", "0rad") for tile in range(self.tiles)] + self.proxy.Pointing_direction_RW = new_pointings + + # check pointing + self.assertListEqual( + new_pointings[0:2], list(self.proxy.Pointing_direction_R[0:2]) + ) + + @timeout_decorator.timeout(120) + def test_beam_tracking_95_percent_interval(self): + """Verify that the beam tracking operates within 95% of interval""" + + self.proxy.initialise() + self.proxy.Tracking_enabled_RW = True + self.proxy.on() + + interval = float( + self.proxy.get_property("Beam_tracking_interval")["Beam_tracking_interval"][ + 0 + ] + ) + + # Allow beam tracking time to settle + time.sleep(interval * 3) + + # We have to poll at regular interval due to not working subscribe + # events + for _ in range(0, 5): + error = self.proxy.Pointing_error_R[0] + self.assertTrue( + -interval * 0.05 < error < interval * 0.05, + f"Error: {error} larger than {interval * 0.05}", + ) + logger.info("BeamTracking error: %s", error) + time.sleep(interval) diff --git a/tangostationcontrol/integration_test/common/device_xst_tests.py b/tangostationcontrol/integration_test/common/device_xst_tests.py new file mode 100644 index 000000000..5f263fe80 --- /dev/null +++ b/tangostationcontrol/integration_test/common/device_xst_tests.py @@ -0,0 +1,18 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.default.devices.base import TestDeviceBase + + +class XSTDeviceTests(TestDeviceBase): + __test__ = False + + def setUp(self, xst_name: str = None, sdpfirmware_name: str = None): + """Intentionally recreate the device object in each test""" + + if xst_name is None or sdpfirmware_name is None: + raise RuntimeError("Missing required device names to initialize tests") + + super().setUp(xst_name) + + self.sdpfirmware_proxy = self.setup_proxy(sdpfirmware_name, defaults=True) diff --git a/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json b/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json index a204f8347..70927173e 100644 --- a/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json +++ b/tangostationcontrol/integration_test/configuration/configDB/LOFAR_ConfigDb.json @@ -48,31 +48,6 @@ } } }, - "PSOC": { - "STAT": { - "PSOC": { - "STAT/PSOC/1": { - "properties": { - "SNMP_host": ["127.0.0.1"], - "SNMP_community": ["public"], - "SNMP_mib_dir": ["devices/mibs/PowerNet-MIB.mib"], - "SNMP_timeout": ["10.0"], - "SNMP_version": ["1"], - "PSOC_sockets": [ - "socket_1", - "socket_2", - "socket_3", - "socket_4", - "socket_5", - "socket_6", - "socket_7", - "socket_8" - ] - } - } - } - } - }, "PCON": { "STAT": { "PCON": { diff --git a/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json b/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json index 88f571110..bd298cfb0 100644 --- a/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json +++ b/tangostationcontrol/integration_test/configuration/configDB/simulators_ConfigDb.json @@ -342,19 +342,6 @@ } } }, - "PSOC": { - "STAT": { - "PSOC": { - "STAT/PSOC/1": { - "properties": { - "SNMP_use_simulators": [ - "True" - ] - } - } - } - } - }, "RECVH": { "STAT": { "RECVH": { diff --git a/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py b/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py index fa861b624..1478a4131 100644 --- a/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py +++ b/tangostationcontrol/integration_test/default/devices/antennafield/test_device_hba.py @@ -1,396 +1,20 @@ -# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -import time -import numpy +from tangostationcontrol.common.constants import CS001_TILES -from integration_test.default.devices.base import TestDeviceBase -from tango import DevState +from integration_test.common.device_hba_tests import HBADeviceTests -from tangostationcontrol.common.constants import ( - N_elements, - MAX_ANTENNA, - N_pol, - N_rcu, - N_rcu_inp, - CS001_TILES, - CLK_160_MHZ, - N_pn, - S_pn, -) -from tangostationcontrol.common.frequency_bands import bands -from tangostationcontrol.devices.base_device_classes.antennafield_device import ( - AntennaStatus, - AntennaUse, -) -# tests will break in weird ways if our constants change beyond these constraints -assert CS001_TILES >= 2 -assert N_pn * S_pn >= 4 -assert N_rcu_inp * N_rcu >= 2 - - -class TestHBADevice(TestDeviceBase): - """Integration test class for device Antennafield HBA""" +class TestHBADevice(HBADeviceTests): + """Integration base test class for device HBA""" __test__ = True def setUp(self): - self.stationmanager_proxy = self.setup_proxy("STAT/StationManager/1") - - # Setup will dump current properties and restore them for us - super().setUp("STAT/AFH/HBA0") - - # Typical tests emulate 'CS001_TILES' number of antennas in - # the AntennaField. 'MAX_ANTENNA' is the number of inputs - # offered by a backing RECV device. Each antenna has - # N_pol (2) inputs. - self.proxy.put_property( - { - "Power_to_RECV_mapping": numpy.array( - [[1, x * 2 + 0] for x in range(CS001_TILES)] - ).flatten(), - "Control_to_RECV_mapping": numpy.array( - [[1, x * 2 + 1] for x in range(CS001_TILES)] - ).flatten(), - "Frequency_Band_RW_default": numpy.array( - [["HBA_110_190", "HBA_110_190"]] * CS001_TILES - ).flatten(), - } - ) - self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True) - self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0") - self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0") - - # configure the frequencies, which allows access - # to the calibration attributes and commands - self.sdpfirmware_proxy.clock_RW = CLK_160_MHZ - self.recv_proxy.RCU_band_select_RW = [[1] * N_rcu_inp] * N_rcu - - def test_ANT_mask_RW_configured_after_Antenna_Usage_Mask(self): - """Verify if ANT_mask_RW values are correctly configured from Antenna_Usage_Mask values""" - - antennafield_proxy = self.proxy - numpy.testing.assert_equal( - numpy.array([[True] * N_rcu_inp] * N_rcu), self.recv_proxy.ANT_mask_RW - ) - - antenna_status = numpy.array([AntennaStatus.OK] * CS001_TILES) - antenna_use = numpy.array( - [AntennaUse.ON] + [AntennaUse.AUTO] * (CS001_TILES - 1) - ) - antenna_properties = { - "Antenna_Status": antenna_status, - "Antenna_Use": antenna_use, - } - mapping_properties = { - "Power_to_RECV_mapping": [-1, -1] * CS001_TILES, - # Two inputs of recv device connected, only defined for 48 inputs - # each pair is one input - "Control_to_RECV_mapping": [1, 0, 1, 1] + [-1, -1] * (CS001_TILES - 2), - } - antennafield_proxy.off() - antennafield_proxy.put_property(antenna_properties) - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - antennafield_proxy.power_hardware_on() - - # Verify all antennas are indicated to work - numpy.testing.assert_equal( - numpy.array([True] * CS001_TILES), antennafield_proxy.Antenna_Usage_Mask_R - ) - - # Verify only connected inputs + Antenna_Usage_Mask_R are true - # As well as dimensions of ANT_mask_RW must match control mapping - numpy.testing.assert_equal( - numpy.array([True] * 2 + [False] * (CS001_TILES - 2)), - antennafield_proxy.ANT_mask_RW, - ) - - # Verify recv proxy values unaffected as default for ANT_mask_RW is true - numpy.testing.assert_equal( - numpy.array([True] * 2 + [True] * (MAX_ANTENNA - 2)), - self.recv_proxy.ANT_mask_RW.flatten(), - ) - - def test_ANT_mask_RW_configured_after_Antenna_Usage_Mask_only_one_functioning_antenna( - self, - ): - """Verify if ANT_mask_RW values are correctly configured from - Antenna_Usage_Mask values (only second antenna is OK)""" - - antennafield_proxy = self.proxy - - # Broken antennas except second - antenna_status = numpy.array( - [AntennaStatus.BROKEN] - + [AntennaStatus.OK] - + [AntennaStatus.BROKEN] * (CS001_TILES - 2) - ) - antenna_use = numpy.array([AntennaUse.AUTO] * CS001_TILES) - antenna_properties = { - "Antenna_Status": antenna_status, - "Antenna_Use": antenna_use, - } - - # Configure control mapping to control all 96 inputs of recv device - mapping_properties = { - "Power_to_RECV_mapping": [-1, -1] * CS001_TILES, - "Control_to_RECV_mapping": - # [1, 0, 1, 1, 1, 2, 1, x ... 1, 95] - numpy.array([[1, x] for x in range(CS001_TILES)]).flatten(), - } - - # Cycle device and set properties - antennafield_proxy.off() - antennafield_proxy.put_property(antenna_properties) - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - antennafield_proxy.power_hardware_on() - - # Antenna_Usage_Mask_R should be false except one - numpy.testing.assert_equal( - numpy.array([False] + [True] + [False] * (CS001_TILES - 2)), - antennafield_proxy.Antenna_Usage_Mask_R, - ) - # device.power_hardware_on() writes Antenna_Usage_Mask_R to ANT_mask_RW - numpy.testing.assert_equal( - numpy.array([False] + [True] + [False] * (CS001_TILES - 2)), - antennafield_proxy.ANT_mask_RW, - ) - # ANT_mask_RW on antennafield writes to configured recv devices for all - # mapped inputs. Unmapped values at the end remain at True. - numpy.testing.assert_equal( - numpy.array( - [False] - + [True] - + [False] * (CS001_TILES - 2) - + [True] * (MAX_ANTENNA - CS001_TILES) - ), - self.recv_proxy.ANT_mask_RW.flatten(), - ) - - def test_antennafield_set_mapped_attribute_ignore_all(self): - """Verify RECV device attribute unaffected by antennafield if not mapped""" - - # Connect recvh/1 device but no control inputs - mapping_properties = { - "Power_to_RECV_mapping": [-1, -1] * CS001_TILES, - "Control_to_RECV_mapping": [-1, -1] * CS001_TILES, - } - - # Cycle device an put properties - antennafield_proxy = self.proxy - antennafield_proxy.off() - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - - # Set HBAT_PWR_on_RW to false on recv device and read results - self.recv_proxy.write_attribute( - "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA - ) - current_values = self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value - - # write true through antennafield - antennafield_proxy.write_attribute( - "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * CS001_TILES + super().setUp( + tiles=CS001_TILES, + afh_name="STAT/AFH/HBA0", + sdpfirmware_name="STAT/SDPFIRMWARE/HBA0", + sdp_name="STAT/SDP/HBA0", ) - # Test that original recv values for HBAT_PWR_on_RW match current - numpy.testing.assert_equal( - current_values, self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value - ) - - # Verify device did not enter FAULT state - self.assertEqual(DevState.ON, antennafield_proxy.state()) - - def test_antennafield_set_mapped_attribute(self): - """Verify RECV device attribute changed by antennafield if mapped inputs""" - - mapping_properties = { - "Power_to_RECV_mapping": [-1, -1] * CS001_TILES, - # Each pair is one mapping so 2 inputs are connected - "Control_to_RECV_mapping": [1, 0, 1, 1] + [-1, -1] * (CS001_TILES - 2), - } - - antennafield_proxy = self.proxy - antennafield_proxy.off() - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - - self.recv_proxy.write_attribute( - "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA - ) - - try: - antennafield_proxy.write_attribute( - "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * CS001_TILES - ) - numpy.testing.assert_equal( - numpy.array( - [[True] * N_elements * N_pol] * 2 - + [[False] * N_elements * N_pol] * (MAX_ANTENNA - 2) - ), - self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value, - ) - finally: - # Always restore recv again - self.recv_proxy.write_attribute( - "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA - ) - - # Verify device did not enter FAULT state - self.assertEqual(DevState.ON, antennafield_proxy.state()) - - def test_antennafield_set_mapped_attribute_all(self): - """Verify RECV device attribute changed by antennafield all inputs mapped""" - - mapping_properties = { - "Power_to_RECV_mapping": [-1, -1] * CS001_TILES, - "Control_to_RECV_mapping": - # [1, 0, 1, 1, 1, 2, 1, x ... 1, 95] - numpy.array([[1, x] for x in range(CS001_TILES)]).flatten(), - } - - antennafield_proxy = self.proxy - antennafield_proxy.off() - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - - self.recv_proxy.write_attribute( - "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA - ) - - try: - antennafield_proxy.write_attribute( - "HBAT_PWR_on_RW", [[True] * N_elements * N_pol] * CS001_TILES - ) - - # Mapped values went to True - numpy.testing.assert_equal( - numpy.array([[True] * N_elements * N_pol] * CS001_TILES), - self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value[:CS001_TILES], - ) - # Unmapped values went to False - numpy.testing.assert_equal( - numpy.array( - [[False] * N_elements * N_pol] * (MAX_ANTENNA - CS001_TILES) - ), - self.recv_proxy.read_attribute("HBAT_PWR_on_RW").value[CS001_TILES:], - ) - finally: - # Always restore recv again - self.recv_proxy.write_attribute( - "HBAT_PWR_on_RW", [[False] * N_elements * N_pol] * MAX_ANTENNA - ) - - # Verify device did not enter FAULT state - self.assertEqual(DevState.ON, antennafield_proxy.state()) - - def test_antennafield_set_mapped_attribute_small(self): - """Verify small RECV device attribute changed all inputs mapped""" - - mapping_properties = { - "Power_to_RECV_mapping": numpy.array( # X maps on power - [[1, x * 2 + 1] for x in range(CS001_TILES)] - ).flatten(), - "Control_to_RECV_mapping": numpy.array( # Y maps on control - [[1, x * 2 + 0] for x in range(CS001_TILES)] - ).flatten(), - } - - antennafield_proxy = self.proxy - antennafield_proxy.off() - antennafield_proxy.put_property(mapping_properties) - antennafield_proxy.boot() - - self.recv_proxy.write_attribute("RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu) - - try: - antennafield_proxy.write_attribute( - # [X, Y] - "RCU_band_select_RW", - [[1, 2]] * CS001_TILES, - ) - - # Mapped values were overwritten - numpy.testing.assert_equal( - # [Power, Control] - numpy.array([2, 1] * CS001_TILES), - self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten()[ - : (CS001_TILES * 2) - ], - ) - # Unmapped values remain at 0 - numpy.testing.assert_equal( - # [Power, Control] - numpy.array([0] * (MAX_ANTENNA - CS001_TILES * 2)), - self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten()[ - (CS001_TILES * 2) : - ], - ) - finally: - # Always restore recv again - self.recv_proxy.write_attribute( - "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu - ) - - # Verify device did not enter FAULT state - self.assertEqual(DevState.ON, antennafield_proxy.state()) - - def test_frequency_band(self): - # Test whether changes in frequency band propagate properly. - VALID_MODI = ["HBA_110_190", "HBA_170_230", "HBA_210_250"] - - properties = { - "Control_to_RECV_mapping": [1, 1] + [-1, -1] * (CS001_TILES - 1), - "Power_to_RECV_mapping": [1, 0] + [-1, -1] * (CS001_TILES - 1), - "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + [-1, -1] * (CS001_TILES - 2), - } - - antennafield_proxy = self.proxy - antennafield_proxy.off() - antennafield_proxy.put_property(properties) - antennafield_proxy.boot() - - for band in [b for b in bands.values() if b.name in VALID_MODI]: - # clear downstream settings - self.recv_proxy.write_attribute( - "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu - ) - self.sdp_proxy.write_attribute("nyquist_zone_RW", [[0] * S_pn] * N_pn) - - antennafield_proxy.write_attribute( - "Frequency_Band_RW", [[band.name, band.name]] * CS001_TILES - ) - # Wait for Tango attributes updating - time.sleep(1) - - # test whether clock propagated correctly - numpy.testing.assert_equal( - band.clock, self.sdpfirmware_proxy.clock_RW, err_msg=f"{band.name}" - ) - - # test whether nyquist zone propagated correctly (for both polarisations!) - numpy.testing.assert_equal( - numpy.array( - [band.nyquist_zone, band.nyquist_zone] * 2 + [0] * (N_pn * S_pn - 4) - ), - self.sdp_proxy.read_attribute("nyquist_zone_RW").value.flatten(), - err_msg=f"{band.name}", - ) - - # test whether rcu filter propagated correctly - numpy.testing.assert_equal( - numpy.array( - [band.rcu_band, band.rcu_band] + [0] * (N_rcu_inp * N_rcu - 2) - ), - self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten(), - err_msg=f"{band.name}", - ) - - # test whether reading back results in the same band for our inputs - numpy.testing.assert_equal( - numpy.array([band.name, band.name]), - antennafield_proxy.read_attribute("Frequency_Band_RW").value[0], - err_msg=f"{band.name}", - ) diff --git a/tangostationcontrol/integration_test/default/devices/base_device_classes/test_power_hierarchy.py b/tangostationcontrol/integration_test/default/devices/base_device_classes/test_power_hierarchy.py index 7a904f987..2794aab0f 100644 --- a/tangostationcontrol/integration_test/default/devices/base_device_classes/test_power_hierarchy.py +++ b/tangostationcontrol/integration_test/default/devices/base_device_classes/test_power_hierarchy.py @@ -7,18 +7,13 @@ Power Hierarchy module integration test import logging import tangostationcontrol -from tango import DevState, DeviceProxy, Database +from tango import Database from integration_test import base from integration_test.device_proxy import TestDeviceProxy -from lofar_station_client.common import CaseInsensitiveString -from tangostationcontrol.common.constants import N_rcu, N_rcu_inp -from tangostationcontrol.devices.base_device_classes.hierarchy_device import ( - NotFoundException, - HierarchyMatchingFilter, -) -from tangostationcontrol.devices.base_device_classes.power_hierarchy import ( - PowerHierarchyControlDevice, + +from integration_test.common.base_device_classes.power_hierarchy_tests import ( + DevicePowerHierarchyControlTests, ) logger = logging.getLogger() @@ -28,7 +23,6 @@ class TestPowerAvailableInStateAttribute(base.IntegrationTestCase): expected = { "StationManager": "HIBERNATE", "CCD": "HIBERNATE", - "PSOC": "HIBERNATE", "PCON": "HIBERNATE", "SDPFirmware": "STANDBY", "ProtectionControl": "HIBERNATE", @@ -71,345 +65,12 @@ class TestPowerAvailableInStateAttribute(base.IntegrationTestCase): ) -class TestPowerHierarchyControlDevice(base.IntegrationTestCase): - """Integration Test class for PowerHierarchyDevice methods""" - - pwr_attr_name = "hardware_powered_R" - - stationmanager_name = "STAT/StationManager/1" - ec_name = "STAT/EC/1" - antennafield_name = "STAT/AFH/HBA0" - aps_l0_name = "STAT/APS/L0" - aps_l1_name = "STAT/APS/L1" - aps_h0_name = "STAT/APS/H0" - apsct_name = "STAT/APSCT/H0" - apspu_h0_name = "STAT/APSPU/H0" - apspu_l0_name = "STAT/APSPU/L0" - apspu_l1_name = "STAT/APSPU/L1" - ccd_name = "STAT/CCD/1" - pcon_name = "STAT/PCON/1" - psoc_name = "STAT/PSOC/1" - sdp_name = "STAT/SDP/HBA0" - unb2_h0_name = "STAT/UNB2/H0" - unb2_l0_name = "STAT/UNB2/L0" - recvh_name = "STAT/RECVH/H0" - recvl_name = "STAT/RECVL/L0" - sdp_name = "STAT/SDP/HBA0" - sdpfirmware_name = "STAT/SDPFirmware/HBA0" +class TestPowerHierarchyControlDeviceHBA0(DevicePowerHierarchyControlTests): + __test__ = True def setUp(self): - super().setUp() - self.setup_all_devices() - - def _unacceptable_exceptions(self): - """Return the set of exceptions raised by the last state transition - in the StationManager, that we do not accept. - - We must accept exceptions since we do not emulate interaction with - actual hardware. In this function, we make sure to just ignore those - which we know will be raised even in a sunny-day scenario.""" - - result = [] - - for ex_str in self.stationmanager_proxy.last_requested_transition_exceptions_R: - # Skip "vetted" exceptions that involve switching power - # on actual hardware, which obviously won't power on in - # our simulators. - if "Failed to execute command_inout on device" in ex_str: - if "command power_hardware_on" in ex_str: - continue - if "command power_hardware_off" in ex_str: - continue - - # Anything left is not acceptable - result.append(ex_str) - - return result - - def setup_stationmanager_proxy(self): - """Initialise StationManager device""" - stationmanager_proxy = TestDeviceProxy(self.stationmanager_name) - # extend timeout for running commands, as state transitions can take a long time - stationmanager_proxy.set_timeout_millis(60000) - - stationmanager_proxy.off() - stationmanager_proxy.initialise() - stationmanager_proxy.on() - self.assertEqual(stationmanager_proxy.state(), DevState.ON) - return stationmanager_proxy - - def setup_proxy_off(self, device_name: str): - """Initialise proxy and turn off device""" - proxy = TestDeviceProxy(device_name) - proxy.off() - return proxy - - def setup_all_devices(self): - """Initialise all Tango devices needed for state transitions""" - self.stationmanager_proxy = self.setup_stationmanager_proxy() - - self.ec_proxy = self.setup_proxy_off(self.ec_name) - self.aps_l0_proxy = self.setup_proxy_off(self.aps_l0_name) - self.psoc_proxy = self.setup_proxy_off(self.psoc_name) - self.pcon_proxy = self.setup_proxy_off(self.pcon_name) - self.ccd_proxy = self.setup_proxy_off(self.ccd_name) - self.apspu_h0_proxy = self.setup_proxy_off(self.apspu_h0_name) - self.apspu_l0_proxy = self.setup_proxy_off(self.apspu_l0_name) - self.apsct_proxy = self.setup_proxy_off(self.apsct_name) - self.unb2_h0_proxy = self.setup_proxy_off(self.unb2_h0_name) - self.unb2_l0_proxy = self.setup_proxy_off(self.unb2_l0_name) - self.recvh_proxy = self.setup_proxy_off(self.recvh_name) - self.recvl_proxy = self.setup_proxy_off(self.recvl_name) - self.sdpfirmware_proxy = self.setup_proxy_off(self.sdpfirmware_name) - self.sdp_proxy = self.setup_proxy_off(self.sdp_name) - self.antennafield_proxy = self.setup_proxy_off(self.antennafield_name) + """Intentionally recreate the device object in each test""" - def test_power_sequence_definition(self): - """ - Test whether Power Sequence is correctly retrieved from the HierarchyDevice - """ - self.setup_stationmanager_proxy() - self.setup_all_devices() - - stationmanager_ph = PowerHierarchyControlDevice() - stationmanager_ph.init(self.stationmanager_name) - children_hierarchy = stationmanager_ph.children(depth=2) - - # Check if EC is child of StationManager - ec_name = self.ec_name.casefold() - self.assertTrue(ec_name in children_hierarchy) - self.assertTrue(isinstance(children_hierarchy[ec_name]["proxy"], DeviceProxy)) - - # Check if PSOC is child of EC - self.assertTrue( - self.psoc_name.casefold() in children_hierarchy[ec_name]["children"].keys() - ) - - # Check if EC retrieves correctly its parent state (StationManager -> ON) - ec_ph = PowerHierarchyControlDevice() - ec_ph.init(self.ec_name) - self.assertEqual(ec_ph.parent_state(), DevState.ON) - # Check if child reads correctly a parent attribute - self.assertEqual( - ec_ph.read_parent_attribute("Station_Name_R"), - "CS001", - ) - - def test_off_to_hibernate(self): - """Test Tango devices are correctly triggered in the OFF to HIBERNATE transition""" - self.assertEqual(self.psoc_proxy.state(), DevState.OFF) - self.assertEqual(self.pcon_proxy.state(), DevState.OFF) - self.assertEqual(self.ccd_proxy.state(), DevState.OFF) - # Switch from OFF to HIBERNATE - self.stationmanager_proxy.station_hibernate() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") - self.assertEqual( - self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" - ) - self.assertEqual(self.psoc_proxy.state(), DevState.ON) - self.assertEqual(self.pcon_proxy.state(), DevState.ON) - self.assertEqual(self.ccd_proxy.state(), DevState.ON) - - logger.info( - "Exceptions suppressed in test_off_to_hibernate: %s", - self.stationmanager_proxy.last_requested_transition_exceptions_R, - ) - - self.assertListEqual([], self._unacceptable_exceptions()) - - def test_hibernate_to_standby(self): - """ - Test whether Tango devices are correctly triggered in the HIBERNATE to STANDBY transition - """ - # Switch from OFF to HIBERNATE - self.stationmanager_proxy.station_hibernate() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") - self.assertEqual( - self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" - ) - self.assertEqual(self.apspu_h0_proxy.state(), DevState.OFF) - self.assertEqual(self.apspu_l0_proxy.state(), DevState.OFF) - self.assertEqual(self.apsct_proxy.state(), DevState.OFF) - self.assertEqual(self.unb2_h0_proxy.state(), DevState.OFF) - self.assertEqual(self.unb2_l0_proxy.state(), DevState.OFF) - self.assertEqual(self.recvh_proxy.state(), DevState.OFF) - self.assertEqual(self.recvl_proxy.state(), DevState.OFF) - self.assertEqual(self.sdpfirmware_proxy.state(), DevState.OFF) - # Switch from HIBERNATE to STANDBY - self.stationmanager_proxy.station_standby() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "STANDBY") - self.assertEqual( - self.stationmanager_proxy.requested_station_state_R.name, "STANDBY" - ) - self.assertEqual(self.apspu_h0_proxy.state(), DevState.ON) - self.assertEqual(self.apspu_l0_proxy.state(), DevState.ON) - self.assertEqual(self.apsct_proxy.state(), DevState.ON) - self.assertEqual(self.unb2_h0_proxy.state(), DevState.ON) - self.assertEqual(self.unb2_l0_proxy.state(), DevState.ON) - self.assertEqual(self.recvh_proxy.state(), DevState.ON) - self.assertEqual(self.recvl_proxy.state(), DevState.ON) - self.assertEqual(self.sdpfirmware_proxy.state(), DevState.ON) - # Check if SDP Firmware is booted with factory image - firmware_images = self.sdpfirmware_proxy.FPGA_boot_image_RW.tolist() - self.assertListEqual( - [0] * len(firmware_images), - firmware_images, - ) - - logger.info( - "Exceptions suppressed: %s", - self.stationmanager_proxy.last_requested_transition_exceptions_R, - ) - - self.assertListEqual([], self._unacceptable_exceptions()) - - def test_standby_to_on(self): - """ - Test whether Tango devices are correctly triggered in the STANDBY to ON transition - """ - # Switch from OFF to HIBERNATE - self.stationmanager_proxy.station_hibernate() - # Switch from HIBERNATE to STANDBY - self.stationmanager_proxy.station_standby() - # Switch from STANDBY to ON - self.assertEqual(self.sdp_proxy.state(), DevState.OFF) - self.assertEqual(self.antennafield_proxy.state(), DevState.OFF) - self.stationmanager_proxy.station_on() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "ON") - self.assertEqual(self.stationmanager_proxy.requested_station_state_R.name, "ON") - self.assertEqual(self.sdp_proxy.state(), DevState.ON) - self.assertEqual(self.antennafield_proxy.state(), DevState.ON) - - # Test if DTH and DAB are disabled - self.assertListEqual( - self.recvh_proxy.RCU_DTH_on_R.tolist(), - [[False] * N_rcu_inp] * N_rcu, - ) - self.assertListEqual( - self.antennafield_proxy.RCU_DAB_filter_on_RW.tolist(), - [False] * self.antennafield_proxy.nr_antennas_R, - ) - - logger.info( - "Exceptions suppressed: %s", - self.stationmanager_proxy.last_requested_transition_exceptions_R, - ) - - self.assertListEqual([], self._unacceptable_exceptions()) - - def test_on_to_standby(self): - """ - Test whether Tango devices are correctly triggered in the ON to STANDBY transition - """ - # Switch from OFF to HIBERNATE - self.stationmanager_proxy.station_hibernate() - # Switch from HIBERNATE to STANDBY - self.stationmanager_proxy.station_standby() - # Switch from STANDBY to ON - self.stationmanager_proxy.station_on() - # Reverse to STANDBY - self.stationmanager_proxy.station_standby() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "STANDBY") - self.assertEqual( - self.stationmanager_proxy.requested_station_state_R.name, "STANDBY" - ) - self.assertEqual(self.sdp_proxy.state(), DevState.OFF) - self.assertEqual(self.antennafield_proxy.state(), DevState.OFF) - - logger.info( - "Exceptions suppressed: %s", - self.stationmanager_proxy.last_requested_transition_exceptions_R, - ) - - self.assertListEqual([], self._unacceptable_exceptions()) - - def test_standby_to_hibernate(self): - """ - Test whether Tango devices are correctly triggered in the STANDBY to HIBERNATE transition - """ - # Switch from OFF to HIBERNATE - self.stationmanager_proxy.station_hibernate() - # Switch from HIBERNATE to STANDBY - self.stationmanager_proxy.station_standby() - # Reverse to HIBERNATE - self.stationmanager_proxy.station_hibernate() - self.assertEqual(self.stationmanager_proxy.station_state_R.name, "HIBERNATE") - self.assertEqual( - self.stationmanager_proxy.requested_station_state_R.name, "HIBERNATE" - ) - self.assertEqual(self.apspu_h0_proxy.state(), DevState.OFF) - self.assertEqual(self.apspu_l0_proxy.state(), DevState.OFF) - self.assertEqual(self.apsct_proxy.state(), DevState.OFF) - self.assertEqual(self.unb2_h0_proxy.state(), DevState.OFF) - self.assertEqual(self.unb2_l0_proxy.state(), DevState.OFF) - self.assertEqual(self.recvh_proxy.state(), DevState.OFF) - self.assertEqual(self.recvl_proxy.state(), DevState.OFF) - self.assertEqual(self.sdpfirmware_proxy.state(), DevState.OFF) - - logger.info( - "Exceptions suppressed: %s", - self.stationmanager_proxy.last_requested_transition_exceptions_R, - ) - - self.assertListEqual([], self._unacceptable_exceptions()) - - def test_branch_child_deep_tree(self): - """ - Test whether Tango devices are correctly retrieved with branch_child function - """ - self.setup_all_devices() - # Create a Hierarchy Device from Device STAT/RECVH/H0 - recvh_ph = PowerHierarchyControlDevice() - recvh_ph.init(self.recvh_name) - self.assertEqual(recvh_ph.parent(), "stat/apspu/h0") - # Branch child method must not return its direct parent - self.assertRaises( - NotFoundException, - recvh_ph.branch_child, - "*/apspu/h*", - HierarchyMatchingFilter.REGEX, - ) - # Test if returns another device with the right query - self.assertEqual( - recvh_ph.branch_child("*/apspu/*", HierarchyMatchingFilter.REGEX).name(), - CaseInsensitiveString(self.apspu_l0_name), - ) - # Test if returns a device in the tree with depth > 1 - self.assertEqual( - recvh_ph.branch_child("*/aps/l*", HierarchyMatchingFilter.REGEX).name(), - CaseInsensitiveString(self.aps_l0_name), - ) - - def test_branch_children_names_deep_tree(self): - """ - Test whether Tango devices are correctly retrieved with - branch_children_names function - """ - self.setup_all_devices() - # Create a Hierarchy Device from Device STAT/RECVH/H0 - recvh_ph = PowerHierarchyControlDevice() - recvh_ph.init(self.recvh_name) - self.assertEqual(recvh_ph.parent(), "stat/apspu/h0") - self.assertEqual(recvh_ph.children(), {}) - # Branch_children_names method must not return its direct parent - self.assertListEqual( - recvh_ph.branch_children_names("*/apspu/h*", HierarchyMatchingFilter.REGEX), - [], - ) - # Test if returns another device with the right query - self.assertListEqual( - recvh_ph.branch_children_names("*/apspu/*", HierarchyMatchingFilter.REGEX), - [ - CaseInsensitiveString(self.apspu_l0_name), - CaseInsensitiveString(self.apspu_l1_name), - ], - ) - # Test if returns a device in the tree with depth > 1 - self.assertListEqual( - recvh_ph.branch_children_names("*/aps/*", HierarchyMatchingFilter.REGEX), - [ - CaseInsensitiveString(self.aps_l0_name), - CaseInsensitiveString(self.aps_l1_name), - CaseInsensitiveString(self.aps_h0_name), - ], + super().setUp( + "CS001", "STAT/AFH/HBA0", "STAT/SDP/HBA0", "STAT/sdpfirmware/HBA0" ) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py b/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py index 67035f2ee..013926e56 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_beamlet.py @@ -1,129 +1,22 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -import time -from ctypes import c_short +from integration_test.common.device_beamlet_tests import BeamletDeviceTests -import numpy -import numpy.testing - -from integration_test.default.devices.base import TestDeviceBase - -from tango import DevState -from tangostationcontrol.common.constants import ( - N_beamlets_ctrl, - S_pn, - CLK_200_MHZ, - CLK_160_MHZ, - N_pn, - A_pn, -) - - -class TestDeviceBeamlet(TestDeviceBase): - """Integration test class for device Beamlet""" +class TestDeviceBeamletHBA0(BeamletDeviceTests): __test__ = True def setUp(self): """Intentionally recreate the device object in each test""" - super().setUp("STAT/Beamlet/HBA0") - - self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0", defaults=True) - self.sdp_proxy.nyquist_zone_RW = [[2] * S_pn] * N_pn - - self.sdpfirmware_proxy = self.setup_proxy( - "STAT/SDPFirmware/HBA0", defaults=True - ) - self.sdpfirmware_proxy.clock_RW = CLK_200_MHZ - - def test_pointing_to_zenith(self): - self.proxy.initialise() - self.proxy.on() - - # The subband frequency of HBA subband 0 is 200 MHz, - # so its period is 5 ns. A delay of 2.5e-9 seconds is thus half a period, - # and result in a 180 degree phase offset. - delays = numpy.array([[[2.5e-9] * N_pn] * A_pn] * N_beamlets_ctrl) - - calculated_bf_weights = self.proxy.calculate_bf_weights(delays.flatten()) - - # With a unit weight of 2**14, we thus expect beamformer weights of -2**14 + 0j, - # which is 49152 when read as an uint32. - self.assertEqual(-(2**14), c_short(49152).value) # check our calculations - expected_bf_weights = numpy.array( - [49152] * N_pn * A_pn * N_beamlets_ctrl, dtype=numpy.uint32 - ) - numpy.testing.assert_almost_equal(expected_bf_weights, calculated_bf_weights) + super().setUp("STAT/Beamlet/HBA0", "STAT/SDP/HBA0", "STAT/sdpfirmware/HBA0") - def test_subband_select_change(self): - # Change subband - self.proxy.off() - self.proxy.initialise() - self.assertEqual(DevState.STANDBY, self.proxy.state()) - self.proxy.subband_select_RW = [10] * N_beamlets_ctrl - self.proxy.on() - self.assertEqual(DevState.ON, self.proxy.state()) - # The subband frequency of HBA subband 10 is 201953125 Hz - # so its period is 4.95 ns ca, half period is 2.4758e-9 - delays = numpy.array([[[2.4758e-9] * N_pn] * A_pn] * N_beamlets_ctrl) - calculated_bf_weights_subband_10 = self.proxy.calculate_bf_weights( - delays.flatten() - ) - - self.assertEqual(-(2**14), c_short(49152).value) # check our calculations - expected_bf_weights_10 = numpy.array( - [49152] * N_pn * A_pn * N_beamlets_ctrl, dtype=numpy.uint32 - ) - numpy.testing.assert_almost_equal( - expected_bf_weights_10, calculated_bf_weights_subband_10 - ) - - def test_sdp_clock_change(self): - sdpfirmware_proxy = self.sdpfirmware_proxy - self.proxy.initialise() - self.proxy.subband_select_RW = numpy.array( - list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)), - dtype=numpy.uint32, - ) - self.proxy.on() - - # any non-zero delay should result in different weights for different clocks - delays = numpy.array([[[2.5e-9] * N_pn] * A_pn] * N_beamlets_ctrl) - - sdpfirmware_proxy.clock_RW = CLK_200_MHZ - time.sleep(3) # wait for beamlet device to process change event - calculated_bf_weights_200 = self.proxy.calculate_bf_weights(delays.flatten()) - - sdpfirmware_proxy.clock_RW = CLK_160_MHZ - time.sleep(3) # wait for beamlet device to process change event - calculated_bf_weights_160 = self.proxy.calculate_bf_weights(delays.flatten()) - - sdpfirmware_proxy.clock_RW = CLK_200_MHZ - time.sleep(3) # wait for beamlet device to process change event - calculated_bf_weights_200_v2 = self.proxy.calculate_bf_weights(delays.flatten()) - - # outcome should be changed back and forth across clock changes - self.assertTrue((calculated_bf_weights_200 != calculated_bf_weights_160).all()) - self.assertTrue( - (calculated_bf_weights_200 == calculated_bf_weights_200_v2).all() - ) +class TestDeviceBeamletHBA1(BeamletDeviceTests): + __test__ = True - # change subbands - self.proxy.off() - self.proxy.initialise() - self.proxy.subband_select_RW = [317] * N_beamlets_ctrl - self.proxy.on() - calculated_bf_weights_200_v3 = self.proxy.calculate_bf_weights(delays.flatten()) - self.assertTrue( - (calculated_bf_weights_200_v2 != calculated_bf_weights_200_v3).all() - ) + def setUp(self): + """Intentionally recreate the device object in each test""" - sdpfirmware_proxy.clock_RW = CLK_160_MHZ - time.sleep(1) # wait for beamlet device to process change event - calculated_bf_weights_160_v2 = self.proxy.calculate_bf_weights(delays.flatten()) - self.assertTrue( - (calculated_bf_weights_160 != calculated_bf_weights_160_v2).all() - ) + super().setUp("STAT/Beamlet/HBA1", "STAT/SDP/HBA1", "STAT/sdpfirmware/HBA1") diff --git a/tangostationcontrol/integration_test/default/devices/test_device_bst.py b/tangostationcontrol/integration_test/default/devices/test_device_bst.py index 62441c3ca..41bf97784 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_bst.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_bst.py @@ -1,19 +1,22 @@ # Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -from integration_test.default.devices.base import TestDeviceBase +from integration_test.common.device_bst_tests import BSTDeviceTests -class TestDeviceBST(TestDeviceBase): +class TestDeviceBSTHBA0(BSTDeviceTests): __test__ = True def setUp(self): """Intentionally recreate the device object in each test""" - super().setUp("STAT/BST/HBA0") - def test_device_read_all_attributes(self): - # We need to connect to SDP first to read some of our attributes - self.setup_proxy("STAT/sdpfirmware/HBA0", defaults=True) - self.setup_proxy("STAT/SDP/HBA0", defaults=True) + super().setUp("STAT/BST/HBA0", "STAT/sdpfirmware/HBA0", "STAT/SDP/HBA0") - super().test_device_read_all_attributes() + +class TestDeviceBSTHBA1(BSTDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("STAT/BST/HBA1", "STAT/sdpfirmware/HBA1", "STAT/SDP/HBA1") diff --git a/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py b/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py index c57c6e221..02568e6ff 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_digitalbeam.py @@ -1,233 +1,40 @@ -# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 -import logging -import time +from tangostationcontrol.common.constants import CS001_TILES -import numpy -import timeout_decorator +from integration_test.common.device_digitalbeam_tests import DigitalBeamDeviceTests -from integration_test.device_proxy import TestDeviceProxy -from integration_test.default.devices.base import TestDeviceBase -from tangostationcontrol.common.constants import ( - MAX_ANTENNA, - N_beamlets_ctrl, - N_pn, - A_pn, - CLK_200_MHZ, - CLK_160_MHZ, - CS001_TILES, -) -from tangostationcontrol.devices.base_device_classes.antennafield_device import ( - AntennaStatus, - AntennaUse, -) -logger = logging.getLogger() - -# tests will break in weird ways if our constants change beyond these constraints -assert CS001_TILES >= 2 -assert MAX_ANTENNA >= 2 - - -class TestDeviceDigitalBeam(TestDeviceBase): +class TestDeviceDigitalBeamHBA0(DigitalBeamDeviceTests): __test__ = True - antenna_status_ok = numpy.array([AntennaStatus.OK] * MAX_ANTENNA) - antenna_status_only_second = numpy.array( - [AntennaStatus.BROKEN] - + [AntennaStatus.OK] - + [AntennaStatus.BROKEN] * (MAX_ANTENNA - 2) - ) - antenna_use_ok = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA) - - antennafield_iden = "STAT/AFH/HBA0" - beamlet_iden = "STAT/Beamlet/HBA0" - recv_iden = "STAT/RECVH/H0" - sdpfirmware_iden = "STAT/SDPFirmware/HBA0" - sdp_iden = "STAT/SDP/HBA0" - def setUp(self): """Intentionally recreate the device object in each test""" - super().setUp("STAT/DigitalBeam/HBA0") - - self.recv_proxy = self.setup_proxy(self.recv_iden, defaults=True) - self.sdpfirmware_proxy = self.setup_proxy(self.sdpfirmware_iden, defaults=True) - self.sdp_proxy = self.setup_proxy(self.sdp_iden) - self.beamlet_proxy = self.initialise_beamlet_proxy() - NR_TILES = CS001_TILES - control_mapping = [[1, i] for i in range(NR_TILES)] - sdp_mapping = [[i // 6, i % 6] for i in range(NR_TILES)] - self.antennafield_proxy = self.setup_proxy( - self.antennafield_iden, - cb=lambda x: { - x.put_property( - { - "Control_to_RECV_mapping": numpy.array( - control_mapping - ).flatten(), - "Antenna_to_SDP_Mapping": numpy.array(sdp_mapping).flatten(), - "Antenna_Status": self.antenna_status_ok, - "Antenna_Use": self.antenna_use_ok, - "Antenna_Cables": ["50m", "80m"] * (CS001_TILES // 2), - "Antenna_Sets": ["FIRST", "ALL"], - "Antenna_Set_Masks": [ - "1" + ("0" * (NR_TILES - 1)), - "1" * NR_TILES, - ], - "Antenna_Type": "HBA", - } - ) - }, + super().setUp( + CS001_TILES, + "STAT/digitalbeam/HBA0", + "STAT/AFH/HBA0", + "STAT/beamlet/HBA0", + "STAT/SDP/HBA0", + "STAT/sdpfirmware/HBA0", + "STAT/recvh/h0", ) - self.addCleanup(TestDeviceProxy.test_device_turn_off, self.beamlet_iden) - - def initialise_beamlet_proxy(self): - beamlet_proxy = TestDeviceProxy(self.beamlet_iden) - beamlet_proxy.off() - beamlet_proxy.initialise() - return beamlet_proxy - - def test_pointing_to_zenith_clock_change(self): - self.beamlet_proxy.on() - - # Set first (default) clock configuration - self.sdpfirmware_proxy.clock_RW = CLK_200_MHZ - time.sleep(1) - - self.proxy.initialise() - self.proxy.Tracking_enabled_RW = False - self.proxy.on() - - # Point to Zenith - self.proxy.set_pointing( - numpy.array( - [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R - ).flatten() - ) - - # beam weights should now be non-zero, we don't actually check their values for correctness - FPGA_bf_weights_pp_clock200 = self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() - self.assertNotEqual(0, sum(FPGA_bf_weights_pp_clock200)) - - self.beamlet_proxy = self.initialise_beamlet_proxy() - self.beamlet_proxy.on() - - # Change clock configuration - self.sdpfirmware_proxy.clock_RW = CLK_160_MHZ - time.sleep(1) - - FPGA_bf_weights_pp_clock160 = self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() - # Assert some values are different - self.assertNotEqual( - sum(FPGA_bf_weights_pp_clock160), sum(FPGA_bf_weights_pp_clock200) - ) - - def test_pointing_to_zenith_subband_change(self): - self.beamlet_proxy.subband_select_RW = numpy.array( - list(range(317)) + [316] + list(range(318, N_beamlets_ctrl)), - dtype=numpy.uint32, - ) - self.beamlet_proxy.on() - - self.proxy.initialise() - self.proxy.Tracking_enabled_RW = False - self.proxy.on() - - # Point to Zenith - self.proxy.set_pointing( - numpy.array( - [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R - ).flatten() - ) - # Store values with first subband configuration - FPGA_bf_weights_pp_subband_v1 = ( - self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() - ) - # Restart beamlet proxy - self.beamlet_proxy = self.initialise_beamlet_proxy() - self.beamlet_proxy.subband_select_RW = [317] * N_beamlets_ctrl - self.beamlet_proxy.on() - - # Store values with second subband configuration - FPGA_bf_weights_pp_subband_v2 = ( - self.beamlet_proxy.FPGA_bf_weights_pp_RW.flatten() - ) - # Assert some values are different - self.assertNotEqual( - sum(FPGA_bf_weights_pp_subband_v1), sum(FPGA_bf_weights_pp_subband_v2) - ) - - def test_set_pointing_masked_enable(self): - """Verify that only selected inputs are written""" - - self.proxy.initialise() - self.proxy.Tracking_enabled_RW = False - self.proxy.on() - - # Enable first input - self.proxy.Antenna_Set_RW = "FIRST" - - # fill weights with values the beamformer will never use (|x| > 1) - impossible_values = numpy.array([[2] * N_beamlets_ctrl * A_pn] * N_pn) - self.beamlet_proxy.FPGA_bf_weights_pp_RW = impossible_values - - self.proxy.set_pointing( - numpy.array( - [["AZELGEO", "0rad", "1.570796rad"]] * self.proxy.nr_beamlets_R - ).flatten() - ) - - # Verify all impossible values are replaced with other values for all inputs - # which should be non-zero for the first antenna, and zero for the rest - FPGA_bf_weights_pp_RW = self.beamlet_proxy.FPGA_bf_weights_pp_RW.reshape( - (N_pn * A_pn, -1) - ) - - # first antenna should have values from the beamformer, so not 0 and not 2 - self.assertTrue( - numpy.all(numpy.not_equal(0, FPGA_bf_weights_pp_RW[0, :])), - f"{FPGA_bf_weights_pp_RW}", - ) - self.assertTrue( - numpy.all(numpy.not_equal(2, FPGA_bf_weights_pp_RW[0, :])), - f"{FPGA_bf_weights_pp_RW}", - ) - - # rest of the antennas should have been given a weight of 0, as they - # were not in the usage mask. - self.assertTrue( - numpy.all(numpy.equal(0, FPGA_bf_weights_pp_RW[1:CS001_TILES, :])), - f"{FPGA_bf_weights_pp_RW}", - ) - - @timeout_decorator.timeout(15) - def test_beam_tracking_90_percent_interval(self): - """Verify that the beam tracking operates within 95% of interval""" +class TestDeviceDigitalBeamHBA1(DigitalBeamDeviceTests): + __test__ = True - self.proxy.initialise() - self.proxy.Tracking_enabled_RW = True - self.proxy.on() + def setUp(self): + """Intentionally recreate the device object in each test""" - interval = float( - self.proxy.get_property("Beam_tracking_interval")["Beam_tracking_interval"][ - 0 - ] + super().setUp( + CS001_TILES, + "STAT/digitalbeam/HBA1", + "STAT/AFH/HBA1", + "STAT/beamlet/HBA1", + "STAT/SDP/HBA1", + "STAT/sdpfirmware/HBA1", + "STAT/recvh/h0", ) - - # Allow beam tracking time to settle - time.sleep(interval * 3) - - # We have to poll at regular interval due to not working subscribe - # events - for _ in range(0, 5): - error = self.proxy.Pointing_error_R[0] - self.assertTrue( - -interval * 0.10 < error < interval * 0.10, - f"Error: {error} larger than {interval * 0.10}", - ) - logger.info("BeamTracking error: %s", error) - time.sleep(interval) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_metadata.py b/tangostationcontrol/integration_test/default/devices/test_device_metadata.py index 23d2ef5d1..e1716ba39 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_metadata.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_metadata.py @@ -13,7 +13,6 @@ class TestMetadataDevice(TestDeviceBase): def setUp(self): super().setUp("STAT/Metadata/1") - self.psoc_proxy = self.setup_proxy("STAT/PSOC/1") def test_send_metadata(self): """Turn on the device and emit metadata""" diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py b/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py index bf253955a..e46c0743f 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_observation_control.py @@ -1,326 +1,35 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -import json -import logging -import time -from datetime import datetime -from datetime import timedelta - -import numpy -from integration_test.default.devices.base import TestDeviceBase -from tango import DevFailed, DevState - from tangostationcontrol.common.constants import CS001_TILES from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_hba_immediate, + get_observation_settings_hba_core_immediate, ) -from timeout_decorator import timeout_decorator -logger = logging.getLogger() +from integration_test.common.device_observation_control_tests import ( + DeviceObservationControlTests, +) -class TestObservationControlDevice(TestDeviceBase): +class TestDeviceObservationControlHBA0(DeviceObservationControlTests): __test__ = True - ANTENNA_TO_SDP_MAPPING = [ - "0", - "0", - "0", - "1", - "0", - "2", - "0", - "3", - "0", - "4", - "0", - "5", - "1", - "0", - "1", - "1", - "1", - "2", - "1", - "3", - "1", - "4", - "1", - "5", - "2", - "0", - "2", - "1", - "2", - "2", - "2", - "3", - "2", - "4", - "2", - "5", - "3", - "0", - "3", - "1", - "3", - "2", - "3", - "3", - "3", - "4", - "3", - "5", - ] - antennafield_name = "STAT/AFH/HBA0" - - VALID_JSON = get_observation_settings_hba_immediate().to_json() - EXPECTED_OBS_ID = json.loads(VALID_JSON)["antenna_fields"][0]["observation_id"] - EXPECTED_ANTENNA_FIELD = json.loads(VALID_JSON)["antenna_fields"][0][ - "antenna_field" - ] - def setUp(self): - super().setUp("STAT/ObservationControl/1") - self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True) - self.sdpfirmware_proxy = self.setup_proxy("STAT/SDPFirmware/HBA0") - self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0") - self.metadata_proxy = self.setup_proxy("STAT/Metadata/1") - - control_mapping = [[1, i] for i in range(CS001_TILES)] - self.antennafield_proxy = self.setup_proxy( - self.antennafield_name, - defaults=True, - restore_properties=True, - cb=lambda x: { - x.put_property( - { - "Antenna_Set": "ALL", - "Power_to_RECV_mapping": numpy.array(control_mapping).flatten(), - "Antenna_to_SDP_Mapping": self.ANTENNA_TO_SDP_MAPPING, - } - ) - }, - ) - - self.beamlet_proxy = self.setup_proxy("STAT/Beamlet/HBA0", defaults=True) - self.digitalbeam_proxy = self.setup_proxy( - "STAT/DigitalBeam/HBA0", defaults=True - ) - self.tilebeam_proxy = self.setup_proxy("STAT/TileBeam/HBA0", defaults=True) - - def on_device_assert(self, proxy): - """Transition the device to ON and assert intermediate states - - This will repeatedly call ``stop_all_observations_now`` in turn calling - ``_destroy_all_observation_field_devices`` cleaning the Database from exported - devices - """ - - 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(self.EXPECTED_OBS_ID)) - self.assertFalse(self.proxy.is_observation_running(54321)) - - def test_add_observation_now(self): - """Test starting an observation now""" - - self.on_device_assert(self.proxy) - - # Integration test JSON has no start time so will start immediately even when - # using `add_observation` - self.proxy.add_observation(self.VALID_JSON) - - self.assertTrue(self.proxy.is_any_observation_running()) - self.assertTrue(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - self.assertTrue(self.proxy.is_antenna_field_active(self.EXPECTED_ANTENNA_FIELD)) - - self.proxy.stop_observation_now(self.EXPECTED_OBS_ID) - - self.assertFalse(self.proxy.is_any_observation_running()) - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - self.assertFalse( - self.proxy.is_antenna_field_active(self.EXPECTED_ANTENNA_FIELD) + """Intentionally recreate the device object in each test""" + + json = get_observation_settings_hba_core_immediate().to_json() + + super().setUp( + CS001_TILES, + json, + "STAT/AFH/HBA0", + "STAT/recvh/h0", + "STAT/SDP/HBA0", + "STAT/sdpfirmware/HBA0", + "STAT/beamlet/HBA0", + "STAT/digitalbeam/HBA0", + "STAT/tilebeam/HBA0", + "STAT/sst/HBA0", + "STAT/xst/HBA0", + "STAT/bst/HBA0", ) - - def test_add_observation_future(self): - """Test starting an observation in the future""" - - self.on_device_assert(self.proxy) - - parameters = json.loads(self.VALID_JSON) - for antenna_field in parameters["antenna_fields"]: - antenna_field["start_time"] = ( - datetime.now() + timedelta(days=1) - ).isoformat() - antenna_field["stop_time"] = ( - datetime.now() + timedelta(days=2) - ).isoformat() - - self.proxy.add_observation(json.dumps(parameters)) - - self.assertIn(self.EXPECTED_OBS_ID, self.proxy.observations_R) - self.assertNotIn(self.EXPECTED_OBS_ID, self.proxy.running_observations_R) - self.assertFalse(self.proxy.is_any_observation_running()) - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - - self.proxy.stop_observation_now(self.EXPECTED_OBS_ID) - - def test_add_observation_multiple(self): - """Test starting multiple observations""" - - second_observation_json = json.loads(self.VALID_JSON) - second_observation_json["antenna_fields"][0]["observation_id"] = 54321 - - self.on_device_assert(self.proxy) - - self.proxy.add_observation(self.VALID_JSON) - self.proxy.add_observation(json.dumps(second_observation_json)) - - self.assertTrue(self.proxy.is_any_observation_running()) - self.assertTrue(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - self.assertTrue(self.proxy.is_observation_running(54321)) - - self.proxy.stop_observation_now(self.EXPECTED_OBS_ID) - self.proxy.stop_observation_now(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_now, -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_now, 2) - - def test_is_any_observation_running_after_stop_all_observations_now(self): - """Test whether is_any_observation_running conforms when we start & stop an observation""" - - self.on_device_assert(self.proxy) - - self.proxy.add_observation(self.VALID_JSON) - self.proxy.stop_all_observations_now() - - # Test false - self.assertFalse(self.proxy.is_any_observation_running()) - - def test_start_stop_observation_now(self): - """Test starting and stopping an observation""" - - self.on_device_assert(self.proxy) - - # uses ID 12345 - self.proxy.add_observation(self.VALID_JSON) - self.proxy.stop_observation_now(self.EXPECTED_OBS_ID) - - # Test false - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - - 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["antenna_fields"][0]["observation_id"] = 54321 - - self.on_device_assert(self.proxy) - - # uses ID 5 - self.proxy.add_observation(self.VALID_JSON) - self.proxy.add_observation(json.dumps(second_observation_json)) - self.proxy.stop_all_observations_now() - - # Test false - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - 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) - for station in parameters["antenna_fields"]: - station["observation_id"] = -1 - - self.on_device_assert(self.proxy) - self.assertRaises(DevFailed, self.proxy.add_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.add_observation, "{}") - - def test_check_and_convert_parameters_invalid_antenna_set(self): - """Test invalid antenna set name""" - parameters = json.loads(self.VALID_JSON) - for station in parameters["antenna_fields"]: - station["antenna_set"] = "ZZZ" - self.on_device_assert(self.proxy) - self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters)) - - def test_check_and_convert_parameters_invalid_time(self): - """Test invalid parameter detection""" - - parameters = json.loads(self.VALID_JSON) - for antenna_field in parameters["antenna_fields"]: - antenna_field["stop_time"] = ( - datetime.now() - timedelta(seconds=1) - ).isoformat() - - self.on_device_assert(self.proxy) - self.assertRaises(DevFailed, self.proxy.add_observation, json.dumps(parameters)) - - @timeout_decorator.timeout(60) - def test_add_observation_callbacks(self): - """Test adding an observation and checking start / stop callbacks work""" - - self.on_device_assert(self.proxy) - - parameters = json.loads(self.VALID_JSON) - for antenna_field in parameters["antenna_fields"]: - antenna_field["start_time"] = ( - datetime.now() + timedelta(seconds=5) - ).isoformat() - antenna_field["stop_time"] = ( - datetime.now() + timedelta(seconds=20) - ).isoformat() - - self.proxy.add_observation(json.dumps(parameters)) - - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - - time.sleep(5) - - while not self.proxy.is_observation_running(self.EXPECTED_OBS_ID): - logging.info("Waiting for observation to start...") - time.sleep(0.1) - - self.assertTrue(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) - self.assertGreater(self.metadata_proxy.messages_published_R, 0) - - time.sleep(20) - - while self.proxy.is_observation_running(self.EXPECTED_OBS_ID): - logging.info("Waiting for observation to stop...") - time.sleep(0.1) - - self.assertFalse(self.proxy.is_observation_running(self.EXPECTED_OBS_ID)) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py index 812d065a2..1b0e78653 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_observation_field.py @@ -27,7 +27,7 @@ from tangostationcontrol.devices.base_device_classes.antennafield_device import ) from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_hba_immediate, + get_observation_settings_hba_core_immediate, ) from tangostationcontrol.common.env_decorators import ( restore_properties_for_devices, @@ -108,7 +108,7 @@ class TestDeviceObservationField(TestDeviceBase): def setUp(self): super().setUp("STAT/ObservationField/1") self.VALID_JSON = json.dumps( - json.loads(get_observation_settings_hba_immediate().to_json())[ + json.loads(get_observation_settings_hba_core_immediate().to_json())[ "antenna_fields" ][0] ) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_psoc.py b/tangostationcontrol/integration_test/default/devices/test_device_psoc.py deleted file mode 100644 index e8152f668..000000000 --- a/tangostationcontrol/integration_test/default/devices/test_device_psoc.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 - -from integration_test.default.devices.base import TestDeviceBase - - -class TestDevicePSOC(TestDeviceBase): - __test__ = True - - def setUp(self): - super().setUp("STAT/PSOC/1") diff --git a/tangostationcontrol/integration_test/default/devices/test_device_sdp.py b/tangostationcontrol/integration_test/default/devices/test_device_sdp.py index 26b31d061..633d3f1bd 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_sdp.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_sdp.py @@ -1,14 +1,28 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 -from integration_test.default.devices.base import TestDeviceBase +from integration_test.common.device_sdp_tests import SDPDeviceTests -class TestDeviceSDP(TestDeviceBase): +class TestSDPDeviceHBA0(SDPDeviceTests): + """Integration base test class for device HBA0""" + __test__ = True def setUp(self): - """Intentionally recreate the device object in each test""" - super().setUp("STAT/SDP/HBA0") + super().setUp( + sdp_name="STAT/SDP/HBA0", + sdpfirmware_name="STAT/SDPFIRMWARE/HBA0", + ) + + +class TestSDPDeviceHBA1(SDPDeviceTests): + """Integration base test class for device HBA1""" - self.setup_proxy("STAT/SDPFirmware/HBA0") + __test__ = True + + def setUp(self): + super().setUp( + sdp_name="STAT/SDP/HBA1", + sdpfirmware_name="STAT/SDPFIRMWARE/HBA1", + ) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py b/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py index ef2b1c28c..0c10055e6 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_sdpfirmware.py @@ -1,24 +1,26 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 -from integration_test.default.devices.base import TestDeviceBase +from integration_test.common.device_sdpfirmware_tests import SDPFirmwareDeviceTests -from tangostationcontrol.common.constants import N_pn -from tangostationcontrol.common.env_decorators import ensure_device_boots +class TestSDPFirmwareDeviceHBA0(SDPFirmwareDeviceTests): + """Integration base test class for device HBA0""" -class TestDeviceSDPFirmware(TestDeviceBase): - """Integration test class for device SDPFirmware""" + __test__ = True + + def setUp(self): + super().setUp( + sdpfirmware_name="STAT/SDPFIRMWARE/HBA0", + ) + + +class TestSDPFirmwareDeviceHBA1(SDPFirmwareDeviceTests): + """Integration base test class for device HBA1""" __test__ = True def setUp(self): - """Intentionally recreate the device object in each test""" - super().setUp("STAT/SDPFirmware/HBA0") - - @ensure_device_boots() - def test_device_sdpfirmware_read_attribute(self): - """Test if we can read an attribute obtained over OPC-UA""" - self.assertListEqual( - [True] * N_pn, list(self.proxy.TR_fpga_communication_error_R) + super().setUp( + sdpfirmware_name="STAT/SDPFIRMWARE/HBA1", ) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_sst.py b/tangostationcontrol/integration_test/default/devices/test_device_sst.py index 52244b2b4..421cbb8bc 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_sst.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_sst.py @@ -1,68 +1,25 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -import socket -import sys -import time +from integration_test.common.device_sst_tests import SSTDeviceTests -from integration_test.default.devices.base import TestDeviceBase -from tangostationcontrol.common.env_decorators import ensure_device_boots - -from tango import DevState - - -class TestDeviceSST(TestDeviceBase): - """Integration test class for device SST""" +class TestDeviceSSTHBA0(SSTDeviceTests): __test__ = True def setUp(self): """Intentionally recreate the device object in each test""" - super().setUp("STAT/SST/HBA0") - - self.sdpfirmware_proxy = self.setup_proxy( - "STAT/SDPFirmware/HBA0", defaults=True - ) - - @ensure_device_boots() - def test_device_sst_send_udp(self): - port_property = {"Statistics_Client_TCP_Port": "4998"} - self.proxy.put_property(port_property) - - self.assertEqual(DevState.ON, self.proxy.state()) - - s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s1.connect(("device-sst.service.consul", 5001)) - - # TODO(Corne): Change me into an actual SST packet - s1.send("Hello World!".encode("UTF-8")) - s1.close() + super().setUp("STAT/SST/HBA0", "STAT/sdpfirmware/HBA0") - @ensure_device_boots() - def test_device_sst_connect_tcp_receive(self): - port_property = {"Statistics_Client_TCP_Port": "5111"} - self.proxy.put_property(port_property) - time.sleep(2) - - s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s1.connect(("device-sst.service.consul", 5011)) - - s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s2.connect(("device-sst.service.consul", 5111)) - - time.sleep(2) - - # TODO(Corne): Change me into an actual SST packet - m_data = "Hello World!".encode("UTF-8") - s1.send(m_data) - - time.sleep(2) +class TestDeviceSSTHBA1(SSTDeviceTests): + __test__ = True - data = s2.recv(sys.getsizeof(m_data)) + UDP_PORT = 5021 + TCP_PORT = 5121 - s1.close() - s2.close() + def setUp(self): + """Intentionally recreate the device object in each test""" - self.assertEqual(m_data, data) + super().setUp("STAT/SST/HBA1", "STAT/sdpfirmware/HBA1") diff --git a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py index fc9e208e1..c07aceb9f 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_tilebeam.py @@ -1,256 +1,38 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -import logging -import datetime -import json -import time +from tangostationcontrol.common.constants import CS001_TILES -import numpy -import timeout_decorator +from integration_test.common.device_tilebeam_tests import TileBeamDeviceTests -from integration_test.device_proxy import TestDeviceProxy -from integration_test.default.devices.base import TestDeviceBase - -from tangostationcontrol.common.constants import ( - CS001_TILES, - MAX_ANTENNA, - N_elements, - N_pol, -) -from tangostationcontrol.devices.base_device_classes.antennafield_device import ( - AntennaStatus, - AntennaUse, -) -from tangostationcontrol.common.env_decorators import ( - ensure_device_boots, - restore_properties_for_devices, -) - -logger = logging.getLogger() - - -class NumpyEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, numpy.ndarray): - return obj.tolist() - return json.JSONEncoder.default(self, obj) - - -class TestDeviceTileBeam(TestDeviceBase): - """Integration test class for device Tilebeam""" +class TestDeviceTilebeamHBA0(TileBeamDeviceTests): __test__ = True - antennafield_name = "STAT/AFH/HBA0" - POINTING_DIRECTION = numpy.array( - [["J2000", "0rad", "0rad"]] * CS001_TILES - ).flatten() - - @restore_properties_for_devices([antennafield_name]) def setUp(self): - """Setup Tilebeam""" - super().setUp("STAT/TileBeam/HBA0") - - self.station_manager_proxy = self.setup_proxy("STAT/StationManager/1") - self.recv_proxy = self.setup_proxy("STAT/RECVH/H0", defaults=True) - self.sdpfirmware_proxy = self.setup_proxy( - "STAT/SDPFirmware/HBA0", defaults=True - ) - self.sdp_proxy = self.setup_proxy("STAT/SDP/HBA0", defaults=True) - self.antennafield_proxy = self.setup_proxy( - self.antennafield_name, cb=self.setup_antennafield_property - ) - - # check if AntennaField really exposes the expected number of tiles - self.assertEqual(CS001_TILES, self.antennafield_proxy.nr_antennas_R) - - def setup_antennafield_property(self, proxy: TestDeviceProxy): - """Setup AntennaField""" - control_mapping = [[1, i] for i in range(CS001_TILES)] - antenna_status = numpy.array([AntennaStatus.OK] * MAX_ANTENNA) - antenna_use = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA) - proxy.put_property( - { - "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(), - "Antenna_Status": antenna_status, - "Antenna_Use": antenna_use, - } - ) - - # check if AntennaField really exposes the expected number of tiles - self.assertEqual(CS001_TILES, proxy.nr_antennas_R) - - @ensure_device_boots() - def test_delays_dims(self): - """Verify delays are retrieved with correct dimensions""" - delays = self.proxy.delays(self.POINTING_DIRECTION) - self.assertEqual(CS001_TILES * N_elements, len(delays)) - - @ensure_device_boots() - def test_set_pointing(self): - """Verify if set pointing procedure is correctly executed""" - # setup BEAM - self.proxy.Tracking_enabled_RW = False - - # Verify attribute is present (all zeros if never used before) - delays_r1 = numpy.array( - self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value - ) - - self.assertIsNotNone(delays_r1) - - time.sleep(3) - - # Verify writing operation does not lead to errors - self.proxy.set_pointing(self.POINTING_DIRECTION) # write values to RECV - delays_r2 = numpy.array( - self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value - ) - - self.assertIsNotNone(delays_r2) - - # Verify delays changed (to be discussed) - # self.assertFalse((delays_r1==delays_r2).all()) - - @ensure_device_boots() - def test_pointing_to_zenith(self): - self.proxy.Tracking_enabled_RW = False - - # Point to Zenith - self.proxy.set_pointing( - numpy.array([["AZELGEO", "0rad", "1.570796rad"]] * CS001_TILES).flatten() - ) - - calculated_HBAT_delay_steps = numpy.array( - self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value - ) - - expected_HBAT_delay_steps = numpy.array( - [[15] * N_elements * N_pol] * CS001_TILES, dtype=numpy.int64 - ) - - numpy.testing.assert_equal( - calculated_HBAT_delay_steps, expected_HBAT_delay_steps - ) - - @ensure_device_boots() - def test_pointing_across_horizon(self): - antennafield_proxy = self.antennafield_proxy - - self.proxy.Tracking_enabled_RW = False - - # point at north on the horizon - self.proxy.set_pointing(["AZELGEO", "0rad", "0rad"] * CS001_TILES) - - # obtain delays of the X polarisation of all the elements of the first tile - north_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[ - 0 - ].reshape(4, 4, 2)[:, :, 0] - - # delays must differ under rotation, or our test will give a false positive - self.assertNotEqual( - north_beam_delay_steps.tolist(), - numpy.rot90(north_beam_delay_steps).tolist(), - ) - # 90, 180 and 270 degrees - for angle in (1.570796, 3.141593, 4.712389): - # point at angle degrees (90=E, 180=S, 270=W) - self.proxy.set_pointing(["AZELGEO", f"{angle}rad", "0rad"] * CS001_TILES) - - # obtain delays of the X polarisation of all the elements of the first tile - angled_beam_delay_steps = antennafield_proxy.HBAT_BF_delay_steps_stage_RW[ - 0 - ].reshape(4, 4, 2)[:, :, 0] - - expected_delay_steps = numpy.rot90( - north_beam_delay_steps, k=-(int((angle * 180) / numpy.pi) / 90) - ) - - self.assertListEqual( - expected_delay_steps.tolist(), - angled_beam_delay_steps.tolist(), - msg=f"angle={angle}", - ) + """Intentionally recreate the device object in each test""" - @ensure_device_boots() - def test_delays_same_as_LOFAR_ref_pointing(self): - self.proxy.Tracking_enabled_RW = False - - # Point to LOFAR 1 ref pointing (0.929342, 0.952579, J2000) - pointings = numpy.array( - [["J2000", "0.929342rad", "0.952579rad"]] * CS001_TILES - ).flatten() - # Need to set the time to '2022-01-18 11:19:35' - timestamp = datetime.datetime(2022, 1, 18, 11, 19, 35).timestamp() - - parameters = {"pointing_direction": pointings, "timestamp": timestamp} - - json_string = json.dumps(parameters, cls=NumpyEncoder) - self.proxy.set_pointing_for_specific_time(json_string) - - calculated_HBAT_delay_steps = numpy.array( - self.antennafield_proxy.read_attribute("HBAT_BF_delay_steps_stage_RW").value - ) # dims (CS001_TILES, 32) - - # Check all delay steps are zero with small margin - # [24, 25, 27, 28, 17, 18, 20, 21, 10, 11, 13, 14, 3, 4, 5, 7] These are the real values from LOFAR. - # The [3] = 28 diff is explained that we match the closest delay step and LOFAR 1 wants the one with - # in 0.2ns but if it can't it will do a int(delay / 0.5ns) so we get slightly different results but - # they can be explained. - expected_HBAT_delay_steps = numpy.repeat( - numpy.array( - [24, 25, 27, 29, 17, 18, 20, 21, 10, 11, 13, 14, 3, 4, 5, 7], - dtype=numpy.int64, - ), - 2, - ) - numpy.testing.assert_equal( - calculated_HBAT_delay_steps[0], expected_HBAT_delay_steps - ) - numpy.testing.assert_equal( - calculated_HBAT_delay_steps[CS001_TILES - 1], - expected_HBAT_delay_steps, + super().setUp( + CS001_TILES, + "STAT/tilebeam/HBA0", + "STAT/AFH/HBA0", + "STAT/SDP/HBA0", + "STAT/sdpfirmware/HBA0", + "STAT/recvh/h0", ) - @ensure_device_boots() - def test_tilebeam_tracking(self): - # check if we're really tracking - self.assertTrue(self.proxy.Tracking_enabled_R) - # point somewhere - new_pointings = [("J2000", f"{tile}rad", "0rad") for tile in range(CS001_TILES)] - self.proxy.Pointing_direction_RW = new_pointings - - # check pointing - self.assertListEqual( - new_pointings[0:2], list(self.proxy.Pointing_direction_R[0:2]) - ) - - @timeout_decorator.timeout(120) - def test_beam_tracking_95_percent_interval(self): - """Verify that the beam tracking operates within 95% of interval""" +class TestDeviceTilebeamHBA1(TileBeamDeviceTests): + __test__ = True - self.proxy.initialise() - self.proxy.Tracking_enabled_RW = True - self.proxy.on() + def setUp(self): + """Intentionally recreate the device object in each test""" - interval = float( - self.proxy.get_property("Beam_tracking_interval")["Beam_tracking_interval"][ - 0 - ] + super().setUp( + CS001_TILES, + "STAT/tilebeam/HBA1", + "STAT/AFH/HBA1", + "STAT/SDP/HBA1", + "STAT/sdpfirmware/HBA1", + "STAT/recvh/h0", ) - - # Allow beam tracking time to settle - time.sleep(interval * 3) - - # We have to poll at regular interval due to not working subscribe - # events - for _ in range(0, 5): - error = self.proxy.Pointing_error_R[0] - self.assertTrue( - -interval * 0.05 < error < interval * 0.05, - f"Error: {error} larger than {interval * 0.05}", - ) - logger.info("BeamTracking error: %s", error) - time.sleep(interval) diff --git a/tangostationcontrol/integration_test/default/devices/test_device_unb2.py b/tangostationcontrol/integration_test/default/devices/test_device_unb2.py index 2b0575da5..8d2860f99 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_unb2.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_unb2.py @@ -4,7 +4,7 @@ from integration_test.default.devices.base import TestDeviceBase -class TestDeviceUNB2(TestDeviceBase): +class TestDeviceUNB2H0(TestDeviceBase): __test__ = True def setUp(self): diff --git a/tangostationcontrol/integration_test/default/devices/test_device_xst.py b/tangostationcontrol/integration_test/default/devices/test_device_xst.py index 9b9ff37dd..4bd88c53a 100644 --- a/tangostationcontrol/integration_test/default/devices/test_device_xst.py +++ b/tangostationcontrol/integration_test/default/devices/test_device_xst.py @@ -1,16 +1,22 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) # SPDX-License-Identifier: Apache-2.0 -from integration_test.default.devices.base import TestDeviceBase +from integration_test.common.device_xst_tests import XSTDeviceTests -class TestDeviceXST(TestDeviceBase): +class TestDeviceXSTHBA0(XSTDeviceTests): __test__ = True def setUp(self): """Intentionally recreate the device object in each test""" - super().setUp("STAT/XST/HBA0") - self.sdpfirmware_proxy = self.setup_proxy( - "STAT/SDPFirmware/HBA0", defaults=True - ) + super().setUp("STAT/XST/HBA0", "STAT/sdpfirmware/HBA0") + + +class TestDeviceXSTHBA1(XSTDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("STAT/XST/HBA1", "STAT/sdpfirmware/HBA1") diff --git a/tangostationcontrol/integration_test/default/devices/test_observation_client.py b/tangostationcontrol/integration_test/default/devices/test_observation_client.py index 58576f3c2..fb1290d53 100644 --- a/tangostationcontrol/integration_test/default/devices/test_observation_client.py +++ b/tangostationcontrol/integration_test/default/devices/test_observation_client.py @@ -10,7 +10,7 @@ from lofar_station_client.observation.station_observation import ( from integration_test import base from integration_test.device_proxy import TestDeviceProxy from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_hba_immediate, + get_observation_settings_hba_core_immediate, ) from tango import DevState @@ -57,7 +57,9 @@ class TestObservation(base.IntegrationTestCase): """Test of the observation_wrapper class basic functionality""" # convert the JSON specification to a dict for this class - specification_dict = loads(get_observation_settings_hba_immediate().to_json()) + specification_dict = loads( + get_observation_settings_hba_core_immediate().to_json() + ) # create an observation class using the dict and as host just get it using a # util function diff --git a/tangostationcontrol/integration_test/remote_station/__init__.py b/tangostationcontrol/integration_test/remote_station/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tangostationcontrol/integration_test/remote_station/antennafield/__init__.py b/tangostationcontrol/integration_test/remote_station/antennafield/__init__.py new file mode 100644 index 000000000..68ddd5cdc --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/antennafield/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 diff --git a/tangostationcontrol/integration_test/remote_station/antennafield/test_device_hba.py b/tangostationcontrol/integration_test/remote_station/antennafield/test_device_hba.py new file mode 100644 index 000000000..ea842bf02 --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/antennafield/test_device_hba.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from tangostationcontrol.common.constants import RS307_TILES + +from integration_test.common.device_hba_tests import HBADeviceTests + + +class TestHBADevice(HBADeviceTests): + """Integration base test class for device HBA""" + + __test__ = True + + def setUp(self): + super().setUp( + tiles=RS307_TILES, + afh_name="STAT/AFH/HBA", + sdpfirmware_name="STAT/SDPFIRMWARE/HBA", + sdp_name="STAT/SDP/HBA", + ) diff --git a/tangostationcontrol/integration_test/remote_station/base_device_classes/__init__.py b/tangostationcontrol/integration_test/remote_station/base_device_classes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tangostationcontrol/integration_test/remote_station/base_device_classes/test_power_hierarchy.py b/tangostationcontrol/integration_test/remote_station/base_device_classes/test_power_hierarchy.py new file mode 100644 index 000000000..efe116924 --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/base_device_classes/test_power_hierarchy.py @@ -0,0 +1,21 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +"""Power Hierarchy HBA integration test""" + +import logging + +from integration_test.common.base_device_classes.power_hierarchy_tests import ( + DevicePowerHierarchyControlTests, +) + +logger = logging.getLogger() + + +class TestPowerHierarchyControlDeviceHBA(DevicePowerHierarchyControlTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("RS307", "STAT/AFH/HBA", "STAT/SDP/HBA", "STAT/sdpfirmware/HBA") diff --git a/tangostationcontrol/integration_test/remote_station/test_device_beamlet.py b/tangostationcontrol/integration_test/remote_station/test_device_beamlet.py new file mode 100644 index 000000000..d2a550d59 --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_beamlet.py @@ -0,0 +1,11 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_beamlet_tests import BeamletDeviceTests + + +class TestDeviceBeamletHBA(BeamletDeviceTests): + __test__ = True + + def setUp(self): + super().setUp("STAT/Beamlet/HBA", "STAT/SDP/HBA", "STAT/sdpfirmware/HBA") diff --git a/tangostationcontrol/integration_test/remote_station/test_device_bst.py b/tangostationcontrol/integration_test/remote_station/test_device_bst.py new file mode 100644 index 000000000..4d7515cde --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_bst.py @@ -0,0 +1,13 @@ +# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_bst_tests import BSTDeviceTests + + +class TestDeviceBSTHBA(BSTDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("STAT/BST/HBA", "STAT/sdpfirmware/HBA", "STAT/SDP/HBA") diff --git a/tangostationcontrol/integration_test/remote_station/test_device_digitalbeam.py b/tangostationcontrol/integration_test/remote_station/test_device_digitalbeam.py new file mode 100644 index 000000000..7a170b2bf --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_digitalbeam.py @@ -0,0 +1,23 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from tangostationcontrol.common.constants import RS307_TILES + +from integration_test.common.device_digitalbeam_tests import DigitalBeamDeviceTests + + +class TestDeviceDigitalBeamHBA(DigitalBeamDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp( + RS307_TILES, + "STAT/digitalbeam/HBA", + "STAT/AFH/HBA", + "STAT/beamlet/HBA", + "STAT/SDP/HBA", + "STAT/sdpfirmware/HBA", + "STAT/recvh/h0", + ) diff --git a/tangostationcontrol/integration_test/remote_station/test_device_observation_control.py b/tangostationcontrol/integration_test/remote_station/test_device_observation_control.py new file mode 100644 index 000000000..bed51697c --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_observation_control.py @@ -0,0 +1,37 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from tangostationcontrol.common.constants import RS307_TILES +from tangostationcontrol.test.dummy_observation_settings import ( + get_observation_settings_hba_core_immediate, +) + +from integration_test.common.device_observation_control_tests import ( + DeviceObservationControlTests, +) + + +class TestDeviceObservationControlHBA(DeviceObservationControlTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + obs_data = get_observation_settings_hba_core_immediate() + obs_data.station = "rs307" + obs_data.antenna_fields[0].antenna_field = "HBA" + + super().setUp( + RS307_TILES, + obs_data.to_json(), + "STAT/AFH/HBA", + "STAT/recvh/h0", + "STAT/SDP/HBA", + "STAT/sdpfirmware/HBA", + "STAT/beamlet/HBA", + "STAT/digitalbeam/HBA", + "STAT/tilebeam/HBA", + "STAT/sst/HBA", + "STAT/xst/HBA", + "STAT/bst/HBA", + ) diff --git a/tangostationcontrol/integration_test/remote_station/test_device_sdp.py b/tangostationcontrol/integration_test/remote_station/test_device_sdp.py new file mode 100644 index 000000000..657904c1a --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_sdp.py @@ -0,0 +1,16 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_sdp_tests import SDPDeviceTests + + +class TestSDPDeviceHBA(SDPDeviceTests): + """Integration base test class for device HBA""" + + __test__ = True + + def setUp(self): + super().setUp( + sdp_name="STAT/SDP/HBA", + sdpfirmware_name="STAT/SDPFIRMWARE/HBA", + ) diff --git a/tangostationcontrol/integration_test/remote_station/test_device_sdpfirmware.py b/tangostationcontrol/integration_test/remote_station/test_device_sdpfirmware.py new file mode 100644 index 000000000..788ad90e8 --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_sdpfirmware.py @@ -0,0 +1,15 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_sdpfirmware_tests import SDPFirmwareDeviceTests + + +class TestSDPFirmwareDeviceHBA(SDPFirmwareDeviceTests): + """Integration base test class for device HBA""" + + __test__ = True + + def setUp(self): + super().setUp( + sdpfirmware_name="STAT/SDPFIRMWARE/HBA", + ) diff --git a/tangostationcontrol/integration_test/remote_station/test_device_sst.py b/tangostationcontrol/integration_test/remote_station/test_device_sst.py new file mode 100644 index 000000000..44661fa7b --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_sst.py @@ -0,0 +1,13 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_sst_tests import SSTDeviceTests + + +class TestDeviceBSTHBA(SSTDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("STAT/SST/HBA", "STAT/sdpfirmware/HBA") diff --git a/tangostationcontrol/integration_test/remote_station/test_device_tilebeam.py b/tangostationcontrol/integration_test/remote_station/test_device_tilebeam.py new file mode 100644 index 000000000..c8c783daf --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_tilebeam.py @@ -0,0 +1,40 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from tangostationcontrol.common.constants import RS307_TILES + +from integration_test.common.device_tilebeam_tests import TileBeamDeviceTests + + +class TestDeviceTilebeamHBA(TileBeamDeviceTests): + __test__ = True + + def setUp(self): + super().setUp( + RS307_TILES, + "STAT/tilebeam/HBA", + "STAT/AFH/HBA", + "STAT/SDP/HBA", + "STAT/sdpfirmware/HBA", + "STAT/recvh/h0", + ) + + self.LOFAR_REF_POINTING = [ + 21, + 24, + 27, + 30, + 14, + 17, + 20, + 23, + 8, + 11, + 14, + 17, + 1, + 4, + 7, + 10, + ] + self.LOFAR_REF_REPEATS = 2 diff --git a/tangostationcontrol/integration_test/remote_station/test_device_xst.py b/tangostationcontrol/integration_test/remote_station/test_device_xst.py new file mode 100644 index 000000000..43c486feb --- /dev/null +++ b/tangostationcontrol/integration_test/remote_station/test_device_xst.py @@ -0,0 +1,13 @@ +# Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy) +# SPDX-License-Identifier: Apache-2.0 + +from integration_test.common.device_xst_tests import XSTDeviceTests + + +class TestDeviceXSTHBA(XSTDeviceTests): + __test__ = True + + def setUp(self): + """Intentionally recreate the device object in each test""" + + super().setUp("STAT/XST/HBA", "STAT/sdpfirmware/HBA") diff --git a/tangostationcontrol/tangostationcontrol/clients/snmp/attribute_classes.py b/tangostationcontrol/tangostationcontrol/clients/snmp/attribute_classes.py index 3dbe6c163..7d2e60ade 100644 --- a/tangostationcontrol/tangostationcontrol/clients/snmp/attribute_classes.py +++ b/tangostationcontrol/tangostationcontrol/clients/snmp/attribute_classes.py @@ -165,86 +165,6 @@ class SNMPAttribute: return result -class PSOC_sim: - """ - This class mimics the desired behavior of the PSOC for the points we - are interested in. It is a replacement for the SNMPAttribute attribute class - """ - - sockets_states = 8 * [True] - - def __init__( - self, - comm=None, - mib=None, - name=None, - idx=None, - dtype=None, - dim_x=None, - dim_y=None, - scaling_factor=1, - ): - if None in (name, idx, dim_x, dim_y): - raise Exception("Expected a value for argument name, idx dim_x and dim_y") - - self.name = name - - if dim_x == 0: - dim_x = 1 - if dim_y == 0: - dim_y = 1 - - self.len = dim_y * dim_x - self.idx = idx - 1 - - def write_function(self, value): - """Simulated attribute write function""" - # we only write to sPDUOutletCtl and only as a scalar - if isinstance(value, list): - value = value[0] - - if self.name == "sPDUOutletCtl": - if value == "outletOn": - PSOC_sim.sockets_states[self.idx] = True - elif value == "outletOff": - PSOC_sim.sockets_states[self.idx] = False - else: - raise ValueError( - "Only acceptable values are: 'outletOn' and 'outletOff'" - ) - else: - raise NameError( - f"pcon does not have any write attributes named: {self.name}" - ) - - def read_function(self): - """Simulated attribute read function""" - if self.name == "sPDUOutletCtl": - return_val = [ - "outletOn" if PSOC_sim.sockets_states[i + self.idx] else "outletOff" - for i in range(self.len) - ] - - if self.len == 1: - return return_val[0] - return return_val - - if self.name == "sPDUMasterState": - return_val = "" - return return_val.join( - "On " if i else "Off " for i in PSOC_sim.sockets_states - ) - - if self.name == "sysUpTime": - # return arbitrary value of 60s uptime - return 60000 - - if self.name == "rPDULoadStatusLoad": - # give a random value in amps - return 2 - raise NameError(f"pcon does not have any read attributes named: {self.name}") - - class PCON_sim: """ This class mimics the desired behavior of the PCON for the points we diff --git a/tangostationcontrol/tangostationcontrol/common/constants.py b/tangostationcontrol/tangostationcontrol/common/constants.py index 300d5863b..55af5593c 100644 --- a/tangostationcontrol/tangostationcontrol/common/constants.py +++ b/tangostationcontrol/tangostationcontrol/common/constants.py @@ -121,6 +121,9 @@ DEFAULT_N_HBA_TILES = 48 # number of tiles in the CS001 station CS001_TILES = 24 +# number of tiles in the RS307 station +RS307_TILES = 48 + # The uint32 weight representing 1.0 for the fixed-point math in SDP's packed cint16 weights for subbands and beam forming. SDP_UNIT_WEIGHT = 2**14 diff --git a/tangostationcontrol/tangostationcontrol/devices/__init__.py b/tangostationcontrol/tangostationcontrol/devices/__init__.py index ff3d864da..ae7dbd414 100644 --- a/tangostationcontrol/tangostationcontrol/devices/__init__.py +++ b/tangostationcontrol/tangostationcontrol/devices/__init__.py @@ -14,7 +14,6 @@ from .observation_field import ObservationField from .observation_control import ObservationControl from .metadata import Metadata from .pcon import PCON -from .psoc import PSOC from .recv.recvh import RECVH from .recv.recvl import RECVL from .sdp.beamlet import Beamlet @@ -44,7 +43,6 @@ __all__ = [ "ObservationControl", "Metadata", "PCON", - "PSOC", "RECVL", "RECVH", "Beamlet", diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py index 6fdd418b2..9e750b124 100644 --- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py +++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/power_hierarchy.py @@ -96,12 +96,6 @@ class PowerHierarchyControlDevice(AbstractHierarchyDevice): # TODO(JDM): wait for CCDTR to be powered on before booting it? self._boot_device(device) - # PSOC: Power on CCD - if device_class_matches(device, "PSOC"): - logger.info("Powering on %s: CCD", device) - device.power_hardware_on() - logger.info("Powering on %s: Succesful: CCD", device) - # CCD: Power on clock if device_class_matches(device, "CCD"): logger.info("Powering on %s: Clock", device) diff --git a/tangostationcontrol/tangostationcontrol/devices/psoc.py b/tangostationcontrol/tangostationcontrol/devices/psoc.py deleted file mode 100644 index 17375fc7f..000000000 --- a/tangostationcontrol/tangostationcontrol/devices/psoc.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 - -""" PSOC Device Server for LOFAR2.0 - -""" - -import logging -from datetime import timedelta - -import numpy -from attribute_wrapper.attribute_wrapper import AttributeWrapper -from tango.server import device_property, command -from tangostationcontrol.clients.snmp.attribute_classes import PSOC_sim - -# Additional import -from tangostationcontrol.common.lofar_logging import ( - device_logging_to_python, -) -from tangostationcontrol.common.states import DEFAULT_COMMAND_STATES -from tangostationcontrol.devices.base_device_classes.snmp_device import SNMPDevice -from tangostationcontrol.common.device_decorators import only_in_states -from tangostationcontrol.metrics import device_metrics - -logger = logging.getLogger() - -__all__ = ["PSOC"] - - -@device_logging_to_python() -@device_metrics() -class PSOC(SNMPDevice): - """PSOC Device Server for LOFAR2.0""" - - # number of sockets the psoc has - PSOC_SOCKETS = 8 - - # ----------------- - # Device Properties - # ----------------- - PSOC_sockets = device_property(dtype=[str], mandatory=True) - - # ---------- - # Attributes - # ---------- - sockets_state_R = AttributeWrapper( - comms_annotation={"mib": "PowerNet-MIB", "name": "sPDUOutletCtl", "index": 1}, - dims=(8,), - datatype=str, - ) - master_state_R = AttributeWrapper( - comms_annotation={"mib": "PowerNet-MIB", "name": "sPDUMasterState"}, - datatype=str, - ) - current_load_R = AttributeWrapper( - comms_annotation={ - "mib": "PowerNet-MIB", - "name": "rPDULoadStatusLoad", - "index": 1, - }, - datatype=numpy.int64, - ) - uptime_R = AttributeWrapper( - comms_annotation={"mib": "SNMPv2-MIB", "name": "sysUpTime"}, - datatype=numpy.int64, - ) - - # -------- - # overloaded functions - # -------- - - def configure_for_initialise(self): - """user code here. is called when the state is set to STANDBY""" - - # make sure all sockets are named - if len(self.PSOC_sockets) != self.PSOC_SOCKETS: - raise Exception( - f"At least {self.PSOC_NOF_SOCKETS} names are required to be given. \ - You can simply leave any unused sockets as empty strings" - ) - else: - # create a dict with the name of the sockets being keys for the socket number - # (e.g: "socket_nr_1": 1) - self.socket_dict = { - self.PSOC_sockets[f]: f + 1 for f in range(len(self.PSOC_sockets)) - } - logger.debug( - "Configured PSOC with the following socket names: %s", self.PSOC_sockets - ) - - super().configure_for_initialise(simulator_class=PSOC_sim) - - 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 - """ - - try: - socket_nr = self.socket_dict[socket_name] - except Exception as exc: - raise Exception( - ( - f"This is not a valid socket name, please make sure " - f"it is one of the following: {self.socket_dict.keys()}" - ) - ) from exc - - # get the correct value to set - if _on: - socket_set = "outletOn" - else: - socket_set = "outletOff" - - # create the SNMPAttribute for the correct socket - attr = self.SNMP_attribute_class( - 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]) - - def _power_hardware_on(self): - self.power_ccd_on() - - def _power_hardware_off(self): - self.power_ccd_off() - - # -------- - # Commands - # -------- - - @command(dtype_in=str) - def socket_on(self, socket_name): - """Turn socket on""" - self._toggle_socket(socket_name, _on=True) - logger.debug("Turned socket %s on", {socket_name}) - - @command(dtype_in=str) - def socket_off(self, socket_name): - """Turn socket off""" - self._toggle_socket(socket_name, _on=False) - logger.debug("Turned socket %s off", {socket_name}) - - @command(dtype_out=str) - def readable_uptime(self): - """ - This function returns a readable string of the uptime. - """ - # prepares this object for the readable_uptime command - uptime_attr = self.SNMP_attribute_class( - self.snmp_manager.SNMP_comm, - "SNMPv2-MIB", - name="sysUpTime", - idx=0, - dtype=numpy.int64, - dim_x=1, - dim_y=0, - scaling_factor=0.01, - ) - # for whatever reason, the uptime is given in hundredts of a second - return str(timedelta(seconds=uptime_attr.read_function())) - - @command() - @only_in_states(DEFAULT_COMMAND_STATES) - def power_ccd_on(self): - """Turn on 230V to the CCD""" - self.socket_on("socket_1") - - @command() - @only_in_states(DEFAULT_COMMAND_STATES) - def power_ccd_off(self): - """Turn off 230V to the CCD""" - self.socket_off("socket_1") diff --git a/tangostationcontrol/tangostationcontrol/devices/types.py b/tangostationcontrol/tangostationcontrol/devices/types.py index 01216170e..43c4d67bb 100644 --- a/tangostationcontrol/tangostationcontrol/devices/types.py +++ b/tangostationcontrol/tangostationcontrol/devices/types.py @@ -27,7 +27,6 @@ class DeviceTypes(str, Enum): ObservationControl = "ObservationControl" Metadata = "Metadata" PCON = "PCON" - PSOC = "PSOC" RECV = "RECV" RECVH = "RECVH" RECVL = "RECVL" diff --git a/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py index b0b1a2f29..8b8d89634 100644 --- a/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py +++ b/tangostationcontrol/tangostationcontrol/test/dummy_observation_settings.py @@ -13,7 +13,7 @@ from tangostationcontrol.configuration import ( XST, ) -SETTINGS_TWO_FIELDS = ObservationSettings( +SETTINGS_TWO_FIELDS_CORE = ObservationSettings( "cs001", [ ObservationFieldSettings( @@ -43,7 +43,7 @@ SETTINGS_TWO_FIELDS = ObservationSettings( ], ) -SETTINGS_HBA_IMMEDIATE = ObservationSettings( +SETTINGS_HBA_CORE_IMMEDIATE = ObservationSettings( "cs001", [ ObservationFieldSettings( @@ -71,11 +71,11 @@ SETTINGS_HBA_IMMEDIATE = ObservationSettings( ) -def get_observation_settings_two_fields() -> ObservationSettings: +def get_observation_settings_two_fields_core() -> ObservationSettings: """Get an observation with two antenna fields""" - return copy.deepcopy(SETTINGS_TWO_FIELDS) + return copy.deepcopy(SETTINGS_TWO_FIELDS_CORE) -def get_observation_settings_hba_immediate() -> ObservationSettings: +def get_observation_settings_hba_core_immediate() -> ObservationSettings: """Get an observation with one antenna field and no start time""" - return copy.deepcopy(SETTINGS_HBA_IMMEDIATE) + return copy.deepcopy(SETTINGS_HBA_CORE_IMMEDIATE) diff --git a/tangostationcontrol/test/clients/test_snmp_client.py b/tangostationcontrol/test/clients/test_snmp_client.py index 3db587e7f..3812ffa46 100644 --- a/tangostationcontrol/test/clients/test_snmp_client.py +++ b/tangostationcontrol/test/clients/test_snmp_client.py @@ -16,7 +16,6 @@ from tangostationcontrol.clients.snmp.snmp_client import SNMPComm from tangostationcontrol.clients.snmp.attribute_classes import ( SNMPAttribute, PCON_sim, - PSOC_sim, ) @@ -328,42 +327,6 @@ class TestSNMPSimulators(base.TestCase): pwr_status.read_function(), 1, "Could not read single value attribute" ) - # read socket 5 out of the socket list - socket_5 = PSOC_sim(name="sPDUOutletCtl", idx=5, dim_x=1, dim_y=0) - self.assertEqual( - socket_5.read_function(), - "outletOn", - "Could not read single value out of array", - ) - - # read all sockets at once - all_sockets = PSOC_sim(name="sPDUOutletCtl", idx=1, dim_x=8, dim_y=0) - all_socket_expected = 8 * ["outletOn"] - self.assertListEqual( - all_sockets.read_function(), - all_socket_expected, - "Could not read entire array at once", - ) - - # set socket 5 to off - socket_5.write_function("outletOff") - self.assertEqual( - socket_5.read_function(), - "outletOff", - "could not write value to sPDUOutletCtl", - ) - - # also check the full socket list - all_socket_expected[4] = "outletOff" - self.assertListEqual( - all_sockets.read_function(), all_socket_expected, "list updated incorrectly" - ) - - # test reading an attribute that does not exist - fake_name = PSOC_sim(name="fake_name", idx=1, dim_x=1, dim_y=0) - with self.assertRaises(NameError): - _ = fake_name.read_function() - class TestMibLoading(base.TestCase): """Test class for MIB file load operations""" diff --git a/tangostationcontrol/test/devices/base_device_classes/test_hierarchy_device.py b/tangostationcontrol/test/devices/base_device_classes/test_hierarchy_device.py index 139837bdc..1db28e067 100644 --- a/tangostationcontrol/test/devices/base_device_classes/test_hierarchy_device.py +++ b/tangostationcontrol/test/devices/base_device_classes/test_hierarchy_device.py @@ -286,7 +286,7 @@ class TestHierarchyDevice(device_base.DeviceTestCase): "stat/notexist/*", hierarchy_device.HierarchyMatchingFilter.REGEX ) - TEST_CHILDREN_ROOT = ["stat/ccd/1", "stat/psoc/1", "stat/afl/lba"] + TEST_CHILDREN_ROOT = ["stat/ccd/1", "stat/afl/lba"] TEST_CHILDREN_ANTENNAFIELD = [ "stat/sdp/1", @@ -306,7 +306,6 @@ class TestHierarchyDevice(device_base.DeviceTestCase): # Calls to get_property follow depth-first order TEST_GET_PROPERTY_CALLS = [ {TEST_PROPERTY_NAME: []}, # cdd/1 - {TEST_PROPERTY_NAME: []}, # psoc/1 {TEST_PROPERTY_NAME: TEST_CHILDREN_ANTENNAFIELD}, # afl/1 {TEST_PROPERTY_NAME: TEST_CHILDREN_SDP}, # sdp/1 {TEST_PROPERTY_NAME: []}, # xst/1 @@ -329,17 +328,12 @@ class TestHierarchyDevice(device_base.DeviceTestCase): def test_fn(test_hierarchy: TestHierarchyDevice.ConcreteHierarchy): test = test_hierarchy.children(depth=-1) - # Test ccd and psoc have no children + # Test ccd has no children self.assertEqual( 0, len(test["stat/ccd/1"]["children"]), msg=f'{test["stat/ccd/1"]["children"]}', ) - self.assertEqual( - 0, - len(test["stat/psoc/1"]["children"]), - msg=f'{test["stat/psoc/1"]["children"]}', - ) # Test antennafield has 4 children self.assertEqual(4, len(test["stat/afl/lba"]["children"])) diff --git a/tangostationcontrol/test/devices/test_psoc_device.py b/tangostationcontrol/test/devices/test_psoc_device.py deleted file mode 100644 index aa09074a4..000000000 --- a/tangostationcontrol/test/devices/test_psoc_device.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2022 ASTRON (Netherlands Institute for Radio Astronomy) -# SPDX-License-Identifier: Apache-2.0 - -from test.devices import device_base - - -class TestPSOCDevice(device_base.DeviceTestCase): - # some dummy values for mandatory properties - - psoc_properties = { - "SNMP_community": "public", - "SNMP_host": "10.87.2.145", - "SNMP_version": 1, - "SNMP_mib_dir": "devices/psoc_mib/PowerNet-MIB.mib", - "SNMP_timeout": 5.0, - "PSOC_sockets": [ - "socket_1", - "socket_2", - "socket_3", - "socket_4", - "socket_5", - "socket_6", - "socket_7", - "socket_8", - ], - } - - def setUp(self): - # DeviceTestCase setUp patches lofar_device DeviceProxy - super(TestPSOCDevice, self).setUp() diff --git a/tangostationcontrol/test/observation/test_observation.py b/tangostationcontrol/test/observation/test_observation.py index 748913ea1..7ada08ce2 100644 --- a/tangostationcontrol/test/observation/test_observation.py +++ b/tangostationcontrol/test/observation/test_observation.py @@ -9,8 +9,8 @@ from tango import DevState from tangostationcontrol.observation.observation import Observation from tangostationcontrol.observation import observation_field from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_two_fields, - get_observation_settings_hba_immediate, + get_observation_settings_two_fields_core, + get_observation_settings_hba_core_immediate, ) from test import base @@ -19,7 +19,7 @@ from test import base @mock.patch("tango.Util.instance") class TestObservation(base.TestCase): def test_properties(self, _): - sut = Observation("DMR", get_observation_settings_two_fields()) + sut = Observation("DMR", get_observation_settings_two_fields_core()) self.assertEqual(5, sut.observation_id) self.assertEqual(["HBA0", "LBA"], sut.antenna_fields) @@ -27,7 +27,7 @@ class TestObservation(base.TestCase): def mocked_observation_field_base(m_obs_field) -> Observation: """Base function for tests using mocked ObservationField instances""" - obs_settings = get_observation_settings_two_fields() + obs_settings = get_observation_settings_two_fields_core() sut = Observation("DMR", obs_settings) for antenna_field in obs_settings.antenna_fields: @@ -67,11 +67,11 @@ class TestObservation(base.TestCase): self.assertTrue(sut.is_partially_running()) def test_create_devices(self, tu_mock): - sut = Observation("DMR", get_observation_settings_two_fields()) + sut = Observation("DMR", get_observation_settings_two_fields_core()) sut.create_devices() self.assertEqual( - len(get_observation_settings_two_fields().antenna_fields), + len(get_observation_settings_two_fields_core().antenna_fields), len(sut._observation_fields), ) @@ -102,7 +102,7 @@ class TestObservation(base.TestCase): obs_field.start.assert_called_once() def test_update_observation_state(self, tu_mock): - sut = Observation("DMR", get_observation_settings_hba_immediate()) + sut = Observation("DMR", get_observation_settings_hba_core_immediate()) sut._stop_antenna_field = mock.Mock() sut._start_antenna_field = mock.Mock() @@ -128,7 +128,7 @@ class TestObservation(base.TestCase): def test_observation_callback(self, tu_mock): """Test the start / stop observation callbacks""" - t_params = get_observation_settings_hba_immediate() + t_params = get_observation_settings_hba_core_immediate() t_callback = mock.Mock() @@ -163,7 +163,9 @@ class TestObservation(base.TestCase): def bound_callback(self, obs_id: int): """Test passing bound (self) callback""" self.assertEqual( - get_observation_settings_hba_immediate().antenna_fields[0].observation_id, + get_observation_settings_hba_core_immediate() + .antenna_fields[0] + .observation_id, obs_id, ) @@ -171,7 +173,7 @@ class TestObservation(base.TestCase): """Test calling bound callback""" sut = Observation( tango_domain="DMR", - parameters=get_observation_settings_hba_immediate(), + parameters=get_observation_settings_hba_core_immediate(), start_callback=self.bound_callback, ) sut._stop_antenna_field = mock.Mock() diff --git a/tangostationcontrol/test/observation/test_observation_controller.py b/tangostationcontrol/test/observation/test_observation_controller.py index 6381f588a..e7178393f 100644 --- a/tangostationcontrol/test/observation/test_observation_controller.py +++ b/tangostationcontrol/test/observation/test_observation_controller.py @@ -10,7 +10,7 @@ from unittest import mock from tango import DevFailed from tangostationcontrol.observation import observation_controller as obs_module from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_two_fields, + get_observation_settings_two_fields_core, ) from test import base @@ -126,7 +126,7 @@ class TestObservationController(base.TestCase): self.assertListEqual(["LBA"], sut.active_antenna_fields) def test_add_observation(self, _m_tango_util): - settings = get_observation_settings_two_fields() + settings = get_observation_settings_two_fields_core() for antenna_field in settings.antenna_fields: antenna_field.stop_time = (datetime.now() + timedelta(days=1)).isoformat() @@ -154,7 +154,7 @@ class TestObservationController(base.TestCase): def test_stop_callback(self, _m_tango_util): """Test that the _stop_callback correctly cleans up observations""" - settings = get_observation_settings_two_fields() + settings = get_observation_settings_two_fields_core() for antenna_field in settings.antenna_fields: antenna_field.stop_time = (datetime.now() + timedelta(days=1)).isoformat() @@ -170,7 +170,7 @@ class TestObservationController(base.TestCase): self.assertEqual(0, len(sut)) def test_add_observation_failed(self, _m_tango_util): - settings = get_observation_settings_two_fields() + settings = get_observation_settings_two_fields_core() for antenna_field in settings.antenna_fields: antenna_field.stop_time = (datetime.now() + timedelta(days=1)).isoformat() diff --git a/tangostationcontrol/test/observation/test_observation_field.py b/tangostationcontrol/test/observation/test_observation_field.py index a18685765..0cb841391 100644 --- a/tangostationcontrol/test/observation/test_observation_field.py +++ b/tangostationcontrol/test/observation/test_observation_field.py @@ -11,7 +11,7 @@ from tango import DevState, DevFailed from tangostationcontrol.observation import observation_field from tangostationcontrol.common.proxies.proxy import create_device_proxy from tangostationcontrol.test.dummy_observation_settings import ( - get_observation_settings_two_fields, + get_observation_settings_two_fields_core, ) from test import base @@ -21,7 +21,7 @@ from test import base class TestObservationField(base.TestCase): def test_properties(self, _): sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) self.assertEqual(5, sut.observation_id) self.assertEqual("HBA0", sut.antenna_field) @@ -30,7 +30,7 @@ class TestObservationField(base.TestCase): def test_create_observation_device(self, tu_mock): sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) sut.create_observation_field_device() @@ -41,7 +41,7 @@ class TestObservationField(base.TestCase): def test_create_observation_device_fail(self, tu_mock): """Test creation failed""" sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) with mock.patch.object(observation_field, "logger") as m_logger: @@ -55,13 +55,13 @@ class TestObservationField(base.TestCase): def test_initialise_observation_field(self, dp_mock, tu_mock): importlib.reload(sys.modules[create_device_proxy.__module__]) sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) sut.initialise_observation_field() self.assertEqual( dp_mock.return_value.observation_field_settings_RW, - get_observation_settings_two_fields().antenna_fields[0].to_json(), + get_observation_settings_two_fields_core().antenna_fields[0].to_json(), ) dp_mock.return_value.Initialise.assert_called() @@ -69,7 +69,7 @@ class TestObservationField(base.TestCase): def test_start(self, dp_mock, tu_mock): importlib.reload(sys.modules[create_device_proxy.__module__]) sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) sut.initialise_observation_field() sut.start() @@ -79,7 +79,7 @@ class TestObservationField(base.TestCase): def test_stop(self, tu_mock): importlib.reload(sys.modules[observation_field.ObservationField.__module__]) sut = observation_field.ObservationField( - "DMR", get_observation_settings_two_fields().antenna_fields[0] + "DMR", get_observation_settings_two_fields_core().antenna_fields[0] ) dp_mock = Mock() -- GitLab