From d93c99c0dcb93811424d40849ce510d62a92d2a9 Mon Sep 17 00:00:00 2001
From: Dantali0n <info@dantalion.nl>
Date: Wed, 10 Aug 2022 15:26:10 +0200
Subject: [PATCH] L2SS-891: Refactor of DTS code
---
lofar_station_client/RCUs.py | 6 -
lofar_station_client/dts/__init__.py | 0
lofar_station_client/dts/bands.py | 163 +++
lofar_station_client/dts/common.py | 51 +
lofar_station_client/dts/constants.py | 75 ++
lofar_station_client/dts/index.py | 167 +++
lofar_station_client/dts/outside.py | 719 ++++++++++++
lofar_station_client/dts/plot.py | 617 ++++++++++
lofar_station_client/dts_outside.py | 1420 ------------------------
lofar_station_client/processing_lib.py | 467 --------
requirements.txt | 8 +-
tests/dts/__init__.py | 0
tests/dts/test_bands.py | 49 +
tests/dts/test_index.py | 84 ++
14 files changed, 1929 insertions(+), 1897 deletions(-)
delete mode 100644 lofar_station_client/RCUs.py
create mode 100644 lofar_station_client/dts/__init__.py
create mode 100644 lofar_station_client/dts/bands.py
create mode 100644 lofar_station_client/dts/common.py
create mode 100644 lofar_station_client/dts/constants.py
create mode 100644 lofar_station_client/dts/index.py
create mode 100644 lofar_station_client/dts/outside.py
create mode 100644 lofar_station_client/dts/plot.py
delete mode 100644 lofar_station_client/dts_outside.py
delete mode 100644 lofar_station_client/processing_lib.py
create mode 100644 tests/dts/__init__.py
create mode 100644 tests/dts/test_bands.py
create mode 100644 tests/dts/test_index.py
diff --git a/lofar_station_client/RCUs.py b/lofar_station_client/RCUs.py
deleted file mode 100644
index 2637079..0000000
--- a/lofar_station_client/RCUs.py
+++ /dev/null
@@ -1,6 +0,0 @@
-RCU2L = [0, 1, 2, 3, 4, 5]
-RCU2H = [8, 9]
-# RCU2H = [8]
-RCU2Hpwr = [8] # Should be even numbers
-RCU2Hctl = [9] # Should be even numbers
-# RCU2Hctl = [rcu + 1 for rcu in RCU2Hpwr] # This is then odd numbers
diff --git a/lofar_station_client/dts/__init__.py b/lofar_station_client/dts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lofar_station_client/dts/bands.py b/lofar_station_client/dts/bands.py
new file mode 100644
index 0000000..73d8908
--- /dev/null
+++ b/lofar_station_client/dts/bands.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Band methods and conversions to and from frequencies"""
+
+import copy
+from typing import List
+from typing import Dict
+
+import numpy as np
+
+from lofar_station_client.dts import constants
+from lofar_station_client.dts import index
+
+
+def get_valid_band_names():
+ """Return the valid band names."""
+
+ return copy.deepcopy(constants.BANDLIMS)
+
+
+def get_band_name_and_subband_index_for_frequency(freqs):
+ """Get band name and subband index for one or more frequencies.
+
+ :param freqs: one or more frequencies in Hz
+ :return: list of band names (see get_valid_band_names)
+ sbi = one or more indices of the subbands for which the frequencies
+ should be returned
+ """
+ # make sure freqs is a list
+ if not isinstance(freqs, list):
+ freqs = [freqs]
+ # get band name and subband index
+ freqs = np.array(freqs)
+ band_names = []
+ for freq in freqs:
+ band_name = get_band_name_for_frequency(freq)
+ band_names.append(band_name)
+ # get indices for band_name "LB"
+ sbis = np.round(freqs * constants.CST_N_SUB / constants.CST_FS)
+ # and update for the other bands:
+ for idx in np.where(np.array(band_names) == "HB1")[0]:
+ sbis[idx] = 2 * constants.CST_N_SUB - sbis[idx]
+ for idx in np.where(np.array(band_names) == "HB2")[0]:
+ sbis[idx] = sbis[idx] - 2 * constants.CST_N_SUB
+ return band_names, sbis
+
+
+def get_band_name_for_frequency(freq: float, bandlims: Dict[str, float] = None):
+ """Get band name for frequency
+
+ :param freq: one frequency in Hz
+ :param bandlims: dict with band names and their limits (default: LOFAR2 bands)
+ :return: name of analog band (see get_valid_band_names),
+ empty string ("") if unsuccessful
+ """
+ if bandlims is None:
+ bandlims = constants.BANDLIMS
+ for band_name in bandlims:
+ if np.min(bandlims[band_name]) <= freq <= np.max(bandlims[band_name]):
+ return band_name
+ return ""
+
+
+def get_frequency_for_band_name(band_name):
+ """Get all frequencies for given band name
+
+ :param band_name: name of analog band (see get_valid_band_names)
+ :return: frequencies in Hz for the selected band
+ """
+
+ return get_frequency_for_band_name_and_subband_index(band_name, sbi=np.arange(512))
+
+
+def get_frequency_for_band_name_and_subband_index(band_name, sbi):
+ """Get frequency for band name and subband(s)
+
+ :param band_name: Name of analog band (see get_valid_band_names)
+ :param sbi: One or more indices of the subbands for which the frequencies
+ should be returned
+ :return: frequencies in Hz for the selected band and subband
+ """
+ # check input
+ valid_band_names = get_valid_band_names()
+ if band_name not in valid_band_names and band_name not in constants.LB_ALIASES:
+ raise Exception(
+ f"Unknown band_name '{band_name}'. Valid band_names are: {valid_band_names}"
+ )
+ # generate frequencies
+ freqs = sbi * constants.CST_FS / constants.CST_N_SUB
+ if band_name == "HB1":
+ return 200e6 - freqs
+ if band_name == "HB2":
+ return 200e6 + freqs
+ # else: # LB
+ return freqs
+
+
+def get_band_names(
+ rcu_band_select_r, rcu_pcb_version_r, lb_tags: List = None, hb_tags: List = None
+):
+ """Get the frequency band names per receiver, based on their band selection & RCU.
+
+ PCB name is used to determine the band (Low Band "LB" or High Band "HB",
+ None otherwise). The band selection is added as a postfix.
+ RCU PCB version can also be used, but then the default tags will not be sufficient
+
+ rcu_pcb_version_r can also hold IDs as returned by recv.RCU_PCB_ID_R
+ In that case, the lb_tags and hb_tags should be passed on by the user
+
+ :param rcu_band_select_r: As returned by recv.RCU_band_select_R
+ :param rcu_pcb_version_r: As returned by recv.RCU_PCB_version_R
+ :param lb_tags: Substrings in RCU_PCB_version_R fields to indicate Low Band
+ Default: `["RCU2L"]`
+ :param hb_tags: Substrings in RCU_PCB_version_R fields to indicate High Band
+ Default: `["RCU2H"]`
+ :return: list of strings indicating the band name per receiver.
+ Returns `None` if no band name could be determined
+
+ """
+
+ # if IDs are passed on, convert to list of strings
+ if isinstance(rcu_pcb_version_r, np.ndarray):
+ rcu_pcb_version_r = [str(x) for x in rcu_pcb_version_r]
+ #
+ if lb_tags is None:
+ lb_tags = ["RCU2L"]
+ if hb_tags is None:
+ hb_tags = ["RCU2H"]
+
+ n_signal_indices = rcu_band_select_r.shape[0] * rcu_band_select_r.shape[1]
+ band_name_per_signal_index = [None] * n_signal_indices
+
+ for rcu_index, _ in enumerate(rcu_band_select_r):
+ for rcu_input_index, _ in enumerate(rcu_band_select_r[rcu_index]):
+ signal_index = index.rcu_index_and_rcu_input_index_2_signal_index(
+ rcu_index, rcu_input_index
+ )
+ this_band = None
+ for tag in lb_tags:
+ if tag in rcu_pcb_version_r[rcu_index]:
+ this_band = f"LB{rcu_band_select_r[rcu_index][rcu_input_index]:1}"
+ break
+ if this_band is None: # continue with high band
+ for tag in hb_tags:
+ if tag in rcu_pcb_version_r[rcu_index]:
+ this_band = (
+ f"HB{rcu_band_select_r[rcu_index][rcu_input_index]:1}"
+ )
+ break
+ band_name_per_signal_index[signal_index] = this_band
+ return band_name_per_signal_index
diff --git a/lofar_station_client/dts/common.py b/lofar_station_client/dts/common.py
new file mode 100644
index 0000000..8d2ecf6
--- /dev/null
+++ b/lofar_station_client/dts/common.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Common methods shared across DTS not further classified"""
+
+
+def devices_list_2_dict(devices_list, device_keys=None):
+ """Convert a list of devices to a dictionary with known keys
+
+ Devices are selected based on substring presence in the devices name
+
+ :param devices_list: list of Tango devices
+ :param device_keys: list of keys, should be a substring of the device names
+ :return: dict of devices with known keys
+ """
+ if device_keys is None:
+ device_keys = [
+ "boot",
+ "unb2",
+ "recv",
+ "sdp",
+ "sst",
+ "bst",
+ "xst",
+ "digitalbeam",
+ "tilebeam",
+ "beamlet",
+ "apsct",
+ "apspu",
+ ]
+ devices_dict = {}
+ if isinstance(devices_list, dict):
+ # if by accident devices_list is already a dict,
+ # then simply return the dict
+ return devices_list
+ for device in devices_list:
+ for device_key in device_keys:
+ if device_key in device.name().lower():
+ devices_dict[device_key] = device
+ return devices_dict
diff --git a/lofar_station_client/dts/constants.py b/lofar_station_client/dts/constants.py
new file mode 100644
index 0000000..c2f13aa
--- /dev/null
+++ b/lofar_station_client/dts/constants.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Constants used by DTS"""
+
+# pylint: disable=consider-using-f-string
+
+RCU2L = [0, 1, 2, 3, 4, 5]
+RCU2H = [8, 9]
+RCU2HPWR = [8] # Should be even numbers
+# TODO(Corne): Why is this not an even number if it should be?!
+RCU2HCTL = [9] # Should be even numbers
+
+CST_N_SUB = 512 # number of subbands as output of subband generation in firmware
+CST_FS = 100e6 # sampling frequency in Hz
+BANDLIMS = {"LB": [0, 100e6], "HB1": [100e6, 200e6], "HB2": [200e6, 300e6]}
+LB_ALIASES = ["LB1", "LB2"]
+
+# define some constants for the setup:
+N_FPGA = 4 # number of fpgas
+
+APS_LOCATION_LABELS = ["Slot%02d" % nr for nr in range(32)]
+
+# positions of RCU boards. (0,0) is bottom, left
+APS_RCU_POS_X = (
+ [11, 10, 9, 8, 7, 6]
+ + [11, 10, 9, 8, 7] * 2
+ + [4, 3, 2, 1, 0] * 2
+ + [5, 4, 3, 2, 1, 0]
+)
+APS_RCU_POS_Y = [2] * 6 + [1] * 5 + [0] * 5 + [2] * 5 + [1] * 5 + [0] * 6
+# positions of the other modules in the APS
+APS_MOD_POS_X = [0, 1, 3, 4, 5]
+APS_MOD_POS_Y = [0] * 5
+
+# if the names are correctly provided by the RCU2s,
+# then update plot_subband_statistics() (see annotation there)
+# and remove these two lines:
+# TODO(Corne): Improve mechanism for defining / undefining these variables
+RCU2L_TAGS = ["8393812", "8416938", "8469028", "8386883", "8374523"]
+RCU2H_TAGS = ["8514859", "8507272"]
+
+PLOT_DIR = "inspection_plots"
+STATION_NAME = "DTS-Outside"
+
+RCU_INDICES = range(32)
+
+# RCU Mask:
+RCU2L_MASK = [rcu_nr in RCU2L for rcu_nr in range(32)]
+
+# unused
+# TODO(Corne): Remove these they are unused, or add functionality
+LBA_MASK = [[True] * 3 if rcu_nr in RCU2L else [False] * 3 for rcu_nr in range(32)]
+# signal inputs connected to HBAT power:
+SI_HBAT_PWR = [signal_index * 3 + x for signal_index in RCU2HPWR for x in range(3)]
+# signal inputs. HBAT control connect to first element:
+SI_NBAT_CTL = [signal_index * 3 + x for signal_index in RCU2HCTL for x in range(3)]
+HBA_RCU_MASK_PWR = [rcui in RCU2HPWR for rcui in RCU_INDICES]
+HBA_RCU_MASK_CTRL = [rcui in RCU2HCTL for rcui in RCU_INDICES]
+
+# HBA masks
+HBA_MASK = [[True] * 3 if rcui in RCU2H else [False] * 3 for rcui in RCU_INDICES]
+HBA_PWR_MASK = [[True] * 3 if rcui in RCU2HPWR else [False] * 3 for rcui in RCU_INDICES]
+HBA_CTL_MASK = [[True] * 3 if rcui in RCU2HCTL else [False] * 3 for rcui in RCU_INDICES]
diff --git a/lofar_station_client/dts/index.py b/lofar_station_client/dts/index.py
new file mode 100644
index 0000000..ca81f46
--- /dev/null
+++ b/lofar_station_client/dts/index.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Conversions between signal and node indexes"""
+
+import datetime
+from typing import Tuple
+
+from deprecate import deprecated
+import numpy as np
+
+
+def signal_index_2_processing_node_index(signal_index) -> int:
+ """Convert signal index to processing node index.
+
+ Can be used to determine on which processing node the signal is processed.
+ """
+ return (signal_index % 192) // 12
+
+
+def signal_index_2_processing_node_input_index(signal_index) -> int:
+ """Convert signal index to processing node input index.
+
+ Can be used to determine wich input to the processing node is used for the signal.
+ """
+ return (signal_index % 192) % 12
+
+
+def signal_index_2_uniboard_index(signal_index) -> int:
+ """Convert signal index to UniBoard index.
+
+ Can be used to determine on which UniBoard the signal is processed.
+ """
+ return signal_index // 48
+
+
+def signal_index_2_rcu_index(signal_index) -> int:
+ """Convert signal index to RCU index.
+
+ Can be used to determine which RCU is used to provide the signal.
+ """
+ return (signal_index // 6) * 2 + signal_index % 2
+
+
+def signal_index_2_rcu_input_index(signal_index) -> int:
+ """Convert signal index to RCU input index.
+
+ Can be used to determine which input to the RCU is used for the signal.
+ """
+ return (signal_index // 2) % 3
+
+
+def signal_index_2_aps_index(signal_index) -> int:
+ """Convert signal index to APS index.
+
+ Can be used to determine in which Antenna Processing Subrack this signal is
+ processed.
+ """
+ return signal_index // 96
+
+
+def signal_input_2_rcu_index_and_rcu_input_index(signal_index) -> Tuple[int, int]:
+ """Given the gsi, get the RCU index and the RCU input index in one call."""
+
+ return (
+ signal_index_2_rcu_index(signal_index),
+ signal_index_2_rcu_input_index(signal_index),
+ )
+
+
+# the inverse:
+def rcu_index_2_signal_index(rcu_index):
+ """Convert RCU index to signal indices."""
+ return rcu_index_and_rcu_input_index_2_signal_index(rcu_index)
+
+
+def rcu_index_and_rcu_input_index_2_signal_index(rcu_index, rcu_input_index=None):
+ """Convert RCU index and RCU input index to signal index."""
+
+ if rcu_input_index is None:
+ rcu_input_index = np.array([0, 1, 2])
+
+ return rcu_index + rcu_input_index * 2 + (rcu_index // 2) * 4
+
+
+@deprecated(target=signal_index_2_rcu_input_index, deprecated_in="0.2", remove_in="0.5")
+def gsi_2_rcu_input_index(gsi):
+ """Replaced by signal_index_2_rcu_input_index
+
+ :deprecated: Please use signal_index_2_rcu_input_index.
+ """
+
+ return signal_index_2_rcu_input_index(gsi)
+
+
+@deprecated(target=signal_index_2_rcu_index, deprecated_in="0.2", remove_in="0.5")
+def gsi_2_rcu_index(gsi):
+ """Replaced by signal_index_2_rcu_index
+
+ `lofar_station_control`
+
+ :deprecated: Please use signal_index_2_rcu_index.
+ """
+
+ return signal_index_2_rcu_index(gsi)
+
+
+@deprecated(
+ target=signal_input_2_rcu_index_and_rcu_input_index,
+ deprecated_in="0.2",
+ remove_in="0.5",
+)
+def gsi_2_rcu_index_and_rcu_input_index(gsi):
+ """Replaced by signal_input_2_rcu_index_and_rcu_input_index
+
+ :deprecated: Please use signal_input_2_rcu_index_and_rcu_input_index.
+ """
+
+ return signal_input_2_rcu_index_and_rcu_input_index(gsi)
+
+
+@deprecated(target=rcu_index_2_signal_index, deprecated_in="0.2", remove_in="0.5")
+def rcu_index_2_gsi(rcu_index):
+ """Replaced by rcu_index_2_signal_index
+
+ :deprecated: Please use rcu_index_2_signal_index.
+ """
+ return rcu_index_2_signal_index(rcu_index)
+
+
+@deprecated(
+ target=rcu_index_and_rcu_input_index_2_signal_index,
+ deprecated_in="0.2",
+ remove_in="0.5",
+)
+def rcu_index_and_rcu_input_index_2_gsi(rcu_index, rcu_input_index=None):
+ """Replaced by rcu_index_and_rcu_input_index_2_signal_index
+
+ :deprecated: Please use rcu_index_and_rcu_input_index_2_signal_index.
+ """
+ return rcu_index_and_rcu_input_index_2_signal_index(rcu_index, rcu_input_index)
+
+
+def get_timestamp(format_specifier=None):
+ """Get the timestamp in standard format
+
+ :param format_specifier: format specifier. iso format (default)
+ "filename": filename without spaces and special
+ characters, format: yyyymmddThhmmss
+
+ :return: Current timestamp in requested format
+ """
+ timestamp = datetime.datetime.isoformat(datetime.datetime.now())
+ if format_specifier == "filename":
+ timestamp = datetime.datetime.now().strftime("%Y%m%dT%H%M%S")
+ return timestamp
diff --git a/lofar_station_client/dts/outside.py b/lofar_station_client/dts/outside.py
new file mode 100644
index 0000000..ea58376
--- /dev/null
+++ b/lofar_station_client/dts/outside.py
@@ -0,0 +1,719 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""DTS outside functionality such as restarting and configuring
+
+Functions to:
+- restart the Dwingeloo Test Station (Outside)
+- configure the Dwingeloo Test Station (Outside) to a defined default
+
+Boudewijn Hut
+"""
+
+import time
+import datetime
+import os
+import logging
+
+import numpy as np
+
+from lofar_station_client.dts import common
+from lofar_station_client.dts import constants
+from lofar_station_client.dts import plot
+
+folders = [constants.PLOT_DIR]
+for folder in folders:
+ if not os.path.exists(folder):
+ os.makedirs(folder)
+
+logger = logging.getLogger()
+
+
+def init_and_set_to_default_config(devices, skip_boot=False, silent=False):
+ """Restart system and set to default configuration
+
+ :param devices: struct of software devices of m&c of the station name
+
+ :return: True if an error ocurred, False otherwise
+ """
+
+ original_log_level = logger.getEffectiveLevel()
+ if silent:
+ logger.setLevel(logging.WARNING)
+
+ found_error = False
+ # initialisation
+ if not skip_boot:
+ logger.info("Start initialisation")
+ found_error = found_error | initialise(devices)
+ else:
+ logger.info("SKIPPING INITIALISATION (skip_init was set to True)")
+ logger.info("Check initialisation")
+ found_error = found_error | check_initialise(devices)
+ # configuring
+ logger.info("Start setting to default configuration")
+ found_error = found_error | set_to_default_configuration(devices)
+ logger.info("Check setting to default configuration")
+ found_error = found_error | check_set_to_default_configuration(
+ devices, readonly=False
+ ) # readonly=False, only at first time after init
+ # plot statistics data
+ logger.info("Plot statistics data")
+ plot.plot_statistics(devices)
+
+ if silent and original_log_level != logging.NOTSET:
+ logger.setLevel(original_log_level)
+
+ return found_error
+
+
+def initialise(devices, timeout=90):
+ """Initialise the system
+
+ :param timeout: Timeout to flag an error in seconds
+ :return: true if error, false otherwise
+ """
+
+ devices = common.devices_list_2_dict(devices)
+ boot = devices["boot"]
+ recv = devices["recv"]
+ sst = devices["sst"]
+ unb2 = devices["unb2"]
+
+ # initialise boot device
+ boot.put_property({"DeviceProxy_Time_Out": 60})
+ boot.off()
+ boot.initialise()
+ boot.on()
+
+ # increase timeouts for reboot
+ logger.debug("Time out settings prior to increase for reboot:")
+ logger.debug(recv.get_timeout_millis())
+ logger.debug(unb2.get_timeout_millis())
+ logger.debug(sst.get_timeout_millis())
+
+ recv.set_timeout_millis(30000)
+ unb2.set_timeout_millis(60000)
+ sst.set_timeout_millis(20000)
+
+ unb2.put_property({"UNB2_On_Off_timeout": 20})
+ recv.put_property({"RCU_On_Off_timeout": 60})
+
+ # reboot system
+ boot.reboot()
+ logger.info("Rebooting now")
+ time_start = time.time()
+ time_dt = time.time() - time_start
+
+ # indicate progress
+ while boot.booting_R and (time_dt < timeout):
+ if time_dt % 5 < 1: # print every 5 seconds
+ logger.info("Initialisation at %f%%: %s", boot.progress_R, boot.status_R)
+ time.sleep(1)
+ time_dt = time.time() - time_start
+ logger.info("Initialisation took %d seconds", time_dt)
+ # check for errors
+ found_error = False
+ if boot.booting_R:
+ found_error = True
+ logger.warning(
+ "Warning! Initialisation still ongoing after timeout (%d sec)", timeout
+ )
+ if boot.uninitialised_devices_R:
+ found_error = True
+ logger.error("Warning! Did not initialise %s.", boot.uninitialised_devices_R)
+ return found_error
+
+
+def check_initialise(devices):
+ """Check the initialisation of the system by an independent monitoring point.
+
+ Note that this method should be used after calling initialise()
+
+ All checks that are directly related to the boot device are already
+ in the method initialise()
+ """
+
+ found_error = False
+ found_error = found_error | check_firmware_loaded(devices)
+ found_error = found_error | check_clock_locked(devices)
+ return found_error
+
+
+def check_firmware_loaded(devices):
+ """Verify that the firmware has loaded in the fpgas
+
+ :return: True if not loaded, False otherwise
+ """
+ devices = common.devices_list_2_dict(devices)
+ sdp = devices["sdp"]
+ res = sdp.FPGA_boot_image_R
+ logger.debug("")
+ logger.debug("Checking sdp.FPGA_boot_image_R:")
+ logger.debug("Reply: %s", res)
+ if not np.array_equal(res[0 : constants.N_FPGA], np.ones(constants.N_FPGA)):
+ logger.debug("Firmware is not loaded!")
+ return True
+ logger.debug("Ok - Firmware loaded")
+ return False
+
+
+def check_clock_locked(devices):
+ """Verify that the APSCT clock is locked
+
+ :return: True if not loaded, False otherwise
+ """
+
+ devices = common.devices_list_2_dict(devices)
+ apsct = devices["apsct"]
+ res = apsct.APSCT_PLL_200MHz_error_R
+
+ logger.debug("")
+ logger.debug("Checking apsct.APSCT_PLL_200MHz_error_R:")
+ logger.debug("Reply: %s", res)
+ if res:
+ logger.debug("APSCT clock not locked!")
+ return True
+ logger.debug("Ok - APSCT clock locked")
+ return False
+
+
+def set_to_default_configuration(devices):
+ """Set the station to its default configuration
+
+ :return: True if error found, False otherwise
+ """
+
+ found_error = False
+ logger.info("Start configuring Low Receivers")
+ found_error = found_error | set_rcul_to_default_config(devices)
+ logger.info("Start configuring High Receivers")
+ found_error = found_error | set_rcuh_to_default_config(devices)
+ logger.info("Start configuring Station Digital Processor")
+ found_error = found_error | set_sdp_to_default_config(devices)
+ logger.info("Start configuring Subband Statistics")
+ found_error = found_error | set_sdp_sst_to_default_config(devices)
+ logger.info("Start configuring Crosslet Statistics")
+ found_error = found_error | set_sdp_xst_to_default_config(devices)
+ logger.info("Start configuring Beamlet Statistics")
+ found_error = found_error | set_sdp_bst_to_default_config(devices)
+ return found_error
+
+
+def set_rcul_to_default_config(devices):
+ """Set RCU Low to its default configuration"""
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+
+ # Set RCUs Lows in default modes
+
+ recv.set_defaults()
+
+ # Set attenuator, antenna power etc.
+ recv.RCU_band_select_RW = set_rcu_ant_masked(
+ recv, constants.LBA_MASK, recv.RCU_band_select_R, [[1] * 3] * 32
+ ) # 1 = 30-80 MHz
+ recv.RCU_PWR_ANT_on_RW = set_rcu_ant_masked(
+ recv, constants.LBA_MASK, recv.RCU_PWR_ANT_on_R, [[True] * 3] * 32
+ ) # Off
+ recv.RCU_attenuator_dB_RW = set_rcu_ant_masked(
+ recv, constants.LBA_MASK, recv.RCU_attenuator_dB_R, [[0] * 3] * 32
+ ) # 0dB attenuator
+ wait_rcu_receiver_busy(recv)
+
+ #
+ found_error = False
+ return found_error
+
+
+def set_rcuh_to_default_config(devices):
+ """Set RCU High to its default configuration"""
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+
+ # Setup HBA RCUs and HBA Tiles
+
+ # Set RCU in correct mode
+ rcu_modes = recv.RCU_band_select_R
+ rcu_modes[8] = [2, 2, 2] # 2 = 110-180 MHz, 1 = 170-230 MHz, 4 = 210-270 MHz ??
+ rcu_modes[9] = [2, 2, 2]
+ recv.RCU_band_select_RW = set_rcu_ant_masked(
+ recv, constants.HBA_MASK, recv.RCU_band_select_R, rcu_modes
+ )
+ wait_rcu_receiver_busy(recv)
+
+ # Switch on the Antenna
+ recv.RCU_PWR_ANT_on_RW = set_rcu_ant_masked(
+ recv, constants.HBA_PWR_MASK, recv.RCU_PWR_ANT_on_R, [[False] * 3] * 32
+ ) # Switch off
+ wait_rcu_receiver_busy(recv)
+ recv.HBAT_PWR_LNA_on_RW = set_rcu_hba_mask(
+ recv, constants.HBA_CTL_MASK, recv.HBAT_PWR_LNA_on_R, [[True] * 32] * 96
+ ) # LNA default on
+ recv.HBAT_BF_delay_steps_RW = set_rcu_hba_mask(
+ recv, constants.HBA_CTL_MASK, recv.HBAT_BF_delay_steps_R, [[0] * 32] * 96
+ ) # Default
+ recv.HBAT_PWR_on_RW = set_rcu_hba_mask(
+ recv, constants.HBA_CTL_MASK, recv.HBAT_PWR_on_R, [[True] * 32] * 96
+ ) # Default
+ wait_rcu_receiver_busy(recv)
+ recv.RCU_PWR_ANT_on_RW = set_rcu_ant_masked(
+ recv, constants.HBA_PWR_MASK, recv.RCU_PWR_ANT_on_R, [[True] * 3] * 32
+ ) # Switch on
+ wait_rcu_receiver_busy(recv)
+ # default in the tile: after power-on, zero delay
+ # delays should be set by the tile beam device
+ # recv.HBAT_BF_delay_steps_RW=[[1]*32]*32
+ # wait_receiver_busy(recv)
+ # by default: equal delay settings for all elements --> Pointing to zenith
+
+ # TODO(Boudewijn): read values and only update the ones that need to be changed.
+ # Could be a function that manages the masks.
+ logging.info(recv.RCU_PWR_ANT_on_RW)
+ logging.info("")
+ logging.info("done")
+
+ found_error = False
+ return found_error
+
+
+####
+# RCU specific functions and variables for DTS-Outside
+####
+
+# General RCU functions
+
+
+def wait_rcu_receiver_busy(recv):
+ """Wait for the Receiver Translators busy monitoring point to return False"""
+
+ while recv.RECVTR_translator_busy_R:
+ time.sleep(0.1)
+
+
+def set_rcu_ant_masked(recv, mask, old_value, new_value):
+ """Set the antenna mask for the Receiver Translator"""
+
+ recv.ANT_mask_RW = mask
+
+ for imask, maski in enumerate(mask):
+ for rcu_input_index in range(3):
+ if maski[rcu_input_index]:
+ old_value[imask][rcu_input_index] = new_value[imask][rcu_input_index]
+ # this used to be the following one liner, but that line was too long.
+ # Here is it, but cut in pieces:
+ # old_value[imask] =
+ # [new_value[imask][j] if maski[j] else old_value[imask][j] for j in range(3)]
+ return old_value
+
+
+def set_rcu_hba_mask(recv, mask, old_value, new_value):
+ """Set the hba mask for the Receiver Translator"""
+
+ # TODO(Boudewijn): Is this function equal to set_ant_masked()? Can this
+ # definition be removed?
+ recv.ANT_mask_RW = mask
+ for imask, maski in enumerate(mask):
+ for i in range(3):
+ if maski[i]:
+ old_value[imask * 3 + i] = new_value[imask * 3 + i]
+ else:
+ old_value[imask * 3 + i] = old_value[imask * 3 + i]
+ return old_value
+
+
+def set_sdp_to_default_config(devices):
+ """Set RCU High to its default configuration
+
+ TODO(Corne): Update this docstring to reflect actual method functionality
+ """
+
+ devices = common.devices_list_2_dict(devices)
+ sdp = devices["sdp"]
+ #
+ # Set SDP in default modes
+ #
+
+ logging.info("Start configuring SDP to default")
+ sdp.set_defaults()
+ # should be part of sdp.set_defaults:
+ next_ring = [False] * 16
+ next_ring[3] = True
+ sdp.FPGA_ring_use_cable_to_previous_rn_RW = [True] + [False] * 15
+ sdp.FPGA_ring_use_cable_to_next_rn_RW = next_ring
+ sdp.FPGA_ring_nof_nodes_RW = [constants.N_FPGA] * 16
+ sdp.FPGA_ring_node_offset_RW = [0] * 16
+
+ #
+ found_error = False
+ return found_error
+
+
+def set_sdp_sst_to_default_config(devices):
+ """Set SSTs to default"""
+
+ devices = common.devices_list_2_dict(devices)
+ sst = devices["sst"]
+
+ sst.set_defaults()
+ # prepare for subband stati
+ sst.FPGA_sst_offload_weighted_subbands_RW = [
+ True
+ ] * 16 # should be in set_defaults()
+
+ #
+ found_error = False
+ return found_error
+
+
+def set_sdp_xst_to_default_config(devices):
+ """Set XSTs to default"""
+
+ devices = common.devices_list_2_dict(devices)
+ xst = devices["xst"]
+
+ # prepare for correlations
+ int_time = 1 # used for 'medium' measurement using statistics writer
+ subband_step_size = 7 # 0 is no stepping
+ n_xst_subbands = 7
+ subband_select = [subband_step_size, 0, 1, 2, 3, 4, 5, 6]
+
+ xst.set_defaults()
+ # should be part of set_defaults()
+ xst.FPGA_xst_processing_enable_RW = [False] * 16
+ # this (line above) should be first, then configure and enable
+ xst.fpga_xst_ring_nof_transport_hops_RW = [3] * 16 # [((n_fgpa/2)+1)]*16
+
+ crosslets = [0] * 16
+ for fpga_nr in range(constants.N_FPGA):
+ crosslets[fpga_nr] = n_xst_subbands
+ xst.FPGA_xst_offload_nof_crosslets_RW = crosslets
+
+ xst.FPGA_xst_subband_select_RW = [subband_select] * 16
+ xst.FPGA_xst_integration_interval_RW = [int_time] * 16
+
+ xst.FPGA_xst_processing_enable_RW = [True] * 16
+ #
+ found_error = False
+ return found_error
+
+
+def set_sdp_bst_to_default_config(devices):
+ """Set XSTs to default"""
+
+ devices = common.devices_list_2_dict(devices)
+ beamlet = devices["beamlet"]
+
+ # prepare for beamformer
+ beamlet.set_defaults()
+
+ beamlet.FPGA_bf_ring_nof_transport_hops_RW = [[1]] * 3 + [[0]] * 13
+ # line above should be part of set_defaults(), specifically for DTS-Outside
+
+ beamletoutput_mask = [False] * 16
+ beamletoutput_mask[3] = True
+ # beamlet.FPGA_beamlet_output_enable_RW=beamletoutput_mask
+ beamlet.FPGA_beamlet_output_hdr_eth_destination_mac_RW = ["40:a6:b7:2d:4f:68"] * 16
+ beamlet.FPGA_beamlet_output_hdr_ip_destination_address_RW = ["192.168.0.249"] * 16
+
+ # define weights
+ # dim: FPGA, input, subband
+ weights_xx = beamlet.FPGA_bf_weights_xx_R.reshape([16, 6, 488])
+ weights_yy = beamlet.FPGA_bf_weights_yy_R.reshape([16, 6, 488])
+ # make a beam for the HBA
+ weights_xx[:][:] = 0
+ weights_yy[:][:] = 0
+ for signal_index in [0, 1]: # range(8*3,9*3+3):
+ unb2i = signal_index // 12
+ signal_index_2 = signal_index // 2
+ signal_index_2 = signal_index_2 % 6
+ if signal_index % 2 == 0:
+ weights_xx[unb2i, signal_index_2] = 16384
+ else:
+ weights_yy[unb2i, signal_index_2] = 16384
+
+ beamlet.FPGA_bf_weights_xx_RW = weights_xx.reshape([16, 6 * 488])
+ beamlet.FPGA_bf_weights_yy_RW = weights_yy.reshape([16, 6 * 488])
+
+ blt = beamlet.FPGA_beamlet_subband_select_R.reshape([16, 12, 488])
+ blt[:] = np.array(range(10, 10 + 488))[np.newaxis, np.newaxis, :]
+ beamlet.FPGA_beamlet_subband_select_RW = blt.reshape([16, 12 * 488])
+
+ #
+ found_error = False
+ return found_error
+
+
+def enable_outputs(
+ devices, enable_sst=True, enable_xst=True, enable_bst=True, enable_beam_output=True
+):
+ """Enable the station outputs after everything is set."""
+
+ devices = common.devices_list_2_dict(devices)
+ if enable_beam_output:
+ sdp = devices["sdp"]
+ sdp.FPGA_processing_enable_RW = [True] * 16
+ if enable_sst:
+ sst = devices["sst"]
+ sst.FPGA_sst_offload_enable_RW = [True] * 16
+ if enable_xst:
+ xst = devices["xst"]
+ xst.FPGA_xst_offload_enable_R = [True] * 16
+ if enable_bst:
+ beamlet = devices["beamlet"]
+ beamlet.FPGA_beamlet_output_enable_RW = [i == 3 for i in range(16)]
+
+ #
+ found_error = False
+ return found_error
+
+
+def check_set_to_default_configuration(devices, readonly=True):
+ """Check the default configuration of the system to be set correctly."""
+
+ found_error = False
+ found_error = found_error | check_default_configuration_low_receivers(devices)
+ found_error = found_error | check_default_configuration_hbat(
+ devices, readonly=readonly
+ )
+ found_error = found_error | check_rcu_fpga_interface(devices, si_to_check=range(18))
+ found_error = found_error | check_set_sdp_xst_to_default_config(
+ devices, lane_mask=range(0, 6)
+ )
+
+ plot.print_fpga_input_statistics(devices)
+
+ return found_error
+
+
+def check_default_configuration_low_receivers(devices, mask=None):
+ """Check that the LNAs are on"""
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ found_error = False
+
+ if mask is None:
+ mask = constants.LBA_MASK
+
+ res = recv.RCU_PWR_ANT_on_R
+ if len(mask) > 0:
+ res = res[mask]
+ logging.debug("")
+ logging.debug("Checking recv.RCU_PWR_ANT_on_R:")
+ logging.debug("Reply%s: %s}", " (masked)" if len(mask) > 0 else "", res)
+ if not res.all():
+ logging.info("One or more LBAs are powered off!")
+ found_error = True
+ else:
+ logging.debug("Ok - Low Receivers operational")
+ return found_error
+
+
+def check_default_configuration_hbat(devices, readonly=True):
+ """Check that the HBA Tiles are on"""
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ hbat_si = [rcu * 3 + r_input for rcu in constants.RCU2HPWR for r_input in range(3)]
+ if not readonly:
+ recv.RECVTR_monitor_rate_RW = 1 # for this checking
+ time.sleep(3)
+
+ found_error = False
+ found_error = found_error | check_rcu_vin(devices, si_mask=hbat_si)
+ found_error = found_error | check_hbat_vout(devices, si_mask=hbat_si)
+ found_error = found_error | check_hbat_iout(devices, si_mask=hbat_si)
+
+ if not readonly:
+ recv.RECVTR_monitor_rate_RW = 10
+
+ # TODO(Boudewijn): - what is checked for here?
+ logging.info(recv.HBAT_PWR_LNA_on_R[8:10])
+ logging.info(recv.ANT_mask_RW[8:10])
+
+ return found_error
+
+
+def check_rcu_vin(devices, si_mask=None, valid_range=None):
+ """Verify HBA Tile is powered by comparing V_in against signal inputs thresholds.
+
+ If no si_mask is given, all returned values are compared.
+ """
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ if si_mask is None:
+ si_mask = []
+ if valid_range is None:
+ valid_range = [30, 50]
+
+ res = recv.RCU_PWR_ANT_VIN_R
+ if len(si_mask) > 0:
+ res = [res[si // 3][si % 3] for si in si_mask]
+ logger.debug("")
+ logger.debug("Checking recv.RCU_PWR_ANT_VIN_R:")
+ logger.debug("Reply%s: %s", " (masked)" if len(si_mask) > 0 else "", res)
+ if not (
+ np.all(valid_range[0] < np.array(res))
+ and np.all(np.array(res) < valid_range[1])
+ ):
+ logging.warning("No power at input RCU2!")
+ return True
+
+ logging.debug("Ok - Power at input RCU2")
+ return False
+
+
+def check_hbat_vout(devices, si_mask=None, valid_range=None):
+ """Verify HBA Tile is powered by comparing V_out against signal input thresholds
+
+ If no si_mask is given, all returned values are compared.
+ """
+
+ # Vout should be 48 +- 1 V
+ devices = common.devices_list_2_dict(devices)
+
+ recv = devices["recv"]
+ if si_mask is None:
+ si_mask = []
+ if valid_range is None:
+ valid_range = [30, 50]
+
+ res = recv.RCU_PWR_ANT_VOUT_R
+ if len(si_mask) > 0:
+ res = [res[si // 3][si % 3] for si in si_mask]
+
+ logging.debug("")
+ logging.debug("Checking recv.RCU_PWR_ANT_VOUT_R:")
+ logging.debug("Reply%s: %s", " (masked)" if len(si_mask) > 0 else "", res)
+
+ if not (
+ np.all(valid_range[0] < np.array(res))
+ and np.all(np.array(res) < valid_range[1])
+ ):
+ logging.warning("Not all HBA Tiles powered on!")
+ return True
+
+ logging.debug("Ok - HBA Tiles powered on")
+ return False
+
+
+def check_hbat_iout(devices, si_mask=None, valid_range=None):
+ """Verify HBA Tile is powered by comparing I_out against signal input thresholds
+
+ If no si_mask is given, all returned values are compared.
+ """
+
+ # Iout = 0.7 +- 0.1 Amp
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ if si_mask is None:
+ si_mask = []
+ if valid_range is None:
+ valid_range = [0.6, 0.8]
+
+ res = recv.RCU_PWR_ANT_IOUT_R
+ if len(si_mask) > 0:
+ res = [res[si // 3][si % 3] for si in si_mask]
+ logging.debug("")
+ logging.debug("Checking recv.RCU_PWR_ANT_IOUT_R:")
+ logging.debug("Reply%s: %s", " (masked)" if len(si_mask) > 0 else "", res)
+ if not (
+ np.all(valid_range[0] < np.array(res))
+ and np.all(np.array(res) < valid_range[1])
+ ):
+ logging.warning("Not all HBA Tiles powered on (draw current)!")
+ return True
+
+ logging.debug("Ok - HBA Tiles powered on (draw current)")
+ return False
+
+
+def check_rcu_fpga_interface(devices, si_to_check=range(18)):
+ """Function to check the RCU - FPGA interface
+
+ if error occurs, try to reboot by resetting Uniboard (switch)
+ if only one works than the APSCT board is not making contact with the backplane.
+ (Can take up to 3 times to get it right..)
+ 23/6/2022T15:42 errorcode 0x4h: xxxxx on all of them.
+ Error descriptions can be found here:
+ https://support.astron.nl/jira/browse/L2SDP-774
+ """
+ devices = common.devices_list_2_dict(devices)
+ sdp = devices["sdp"]
+ found_error = False
+ # TODO(Boudewijn): the number of calls can be reduced
+ for signal_index in si_to_check:
+ lock = sdp.FPGA_jesd204b_csr_dev_syncn_R[signal_index // 12][signal_index % 12]
+ if lock != 1:
+ found_error = True
+ print(f"Transceiver SI: {signal_index} is not locked {lock}")
+ err0 = sdp.FPGA_jesd204b_rx_err0_R[signal_index // 12][signal_index % 12]
+ if err0 != 0:
+ found_error = True
+ print(f"SI {signal_index} has JESD err0: 0x{err0:x}h")
+ err1 = sdp.FPGA_jesd204b_rx_err1_R[signal_index // 12][signal_index % 12]
+ if (err1 & 0x02FF) != 0:
+ found_error = True
+ print(f"SI {signal_index} has JESD err1: 0x{err1:x}h")
+ return found_error
+
+
+def check_set_sdp_xst_to_default_config(
+ devices, lane_mask=None, time_1=None, time_dt=2.5
+):
+ """Check sdp/xst configuration by checking the returned timestamps for:
+
+ 1. all being the same
+ 2. not epoch zero (at 1 Jan 1970 00:00:00)
+ 3. within 5 seconds from the local clock timestamp (now +/- 2.5 sec)
+
+ :param time_1: number of seconds since the epoch to compare against
+ Default: None --> now()
+ :param time_dt: delta in seconds around time_1, in which the xst timestamps
+ should fall.
+ """
+
+ devices = common.devices_list_2_dict(devices)
+ xst = devices["xst"]
+ if lane_mask is None:
+ lane_mask = []
+ if time_1 is None:
+ time_1 = datetime.datetime.now().timestamp()
+ res = xst.xst_timestamp_R
+ if len(lane_mask) > 0: # mask lanes
+ res = [res[lane] for lane in lane_mask]
+ logging.debug("")
+ logging.debug("Checking xst.xst_timestamp_R:")
+ logging.debug("Reply%s: %s", " (masked)" if len(lane_mask) > 0 else "", res)
+ logging.debug("Converted:")
+ logging.debug(
+ "\n".join([f"{time.asctime(time.gmtime(xst_time))}" for xst_time in res])
+ )
+ if not (res == res[0]).all():
+ logging.warning("Not all xst timestamps are equal!")
+ return True
+ if res[0] == 0:
+ logging.warning("One or more xst timestamps are zero!")
+ return True
+ if abs(res[0] - time_1) > time_dt:
+ logging.warning(
+ "One or more xst timestamps are outside the specified time window!"
+ )
+ return True
+ logging.debug("Ok - xst configured correctly")
+ return False
diff --git a/lofar_station_client/dts/plot.py b/lofar_station_client/dts/plot.py
new file mode 100644
index 0000000..4f28156
--- /dev/null
+++ b/lofar_station_client/dts/plot.py
@@ -0,0 +1,617 @@
+# -*- coding: utf-8 -*-
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Plotting functionality for DTS"""
+
+# pylint: disable=R0914,C0209,R0915,R0913
+
+import logging
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+from lofar_station_client.dts import bands
+from lofar_station_client.dts import constants
+from lofar_station_client.dts import common
+from lofar_station_client.dts import index as plib
+
+logger = logging.getLogger()
+
+
+def print_fpga_input_statistics(devices):
+ """Print FPGA input statistics"""
+ devices = common.devices_list_2_dict(devices)
+ sdp = devices["sdp"]
+
+ # get data from device
+ dc_offset = sdp.FPGA_signal_input_mean_R
+ signal_input_rms = sdp.FPGA_signal_input_rms_R
+ lock_to_adc = sdp.FPGA_jesd204b_csr_dev_syncn_R
+ err0 = sdp.FPGA_jesd204b_rx_err0_R
+ err1 = sdp.FPGA_jesd204b_rx_err1_R
+ count = sdp.FPGA_jesd204b_csr_rbd_count_R
+ signal_index = np.reshape(np.arange(count.shape[0] * count.shape[1]), count.shape)
+ # get some info on the hardware connections
+ pn_index = plib.signal_index_2_processing_node_index(signal_index)
+ pn_input_index = plib.signal_index_2_processing_node_input_index(signal_index)
+ rcu_index = plib.signal_index_2_rcu_index(signal_index)
+ rcu_input_index = plib.signal_index_2_rcu_input_index(signal_index)
+
+ # print
+ print("+------------+-------------+----------------------------------------------+")
+ print("| Input | Processed by| Statistics of input |")
+ print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
+ print("|RCU|RCU| SI | Node | Node | DC | RMS | Lock| Err0 | Err1 | Count|")
+ print("|idx|inp| | Index| Input| Offset | (LSB) | | (0x) | (0x) | (0x) |")
+ print("| |idx| | | Index| (LSB) | | | | | |")
+ print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
+ pfmt = (
+ "| %02d| %1d |%3d | %02d | %2d | %7.2f | %7.2f | %3s | %04x | %04x | %04x |"
+ )
+ for rcui, rcuii, signali, pni, pnii, dco, rms, lock, err_0, err_1, cnt in zip(
+ rcu_index.flatten(),
+ rcu_input_index.flatten(),
+ signal_index.flatten(),
+ pn_index.flatten(),
+ pn_input_index.flatten(),
+ dc_offset.flatten(),
+ signal_input_rms.flatten(),
+ lock_to_adc.flatten(),
+ err0.flatten(),
+ err1.flatten(),
+ count.flatten(),
+ ):
+ print(
+ pfmt % (rcui, rcuii, signali, pni, pnii, dco, rms, lock, err_0, err_1, cnt)
+ )
+ print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
+
+
+def report_setup_configuration(devices, save_fig=True):
+ """Read name and version data from the setup and print that as a report"""
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ apsct = devices["apsct"]
+ apspu = devices["apspu"]
+ unb2 = devices["unb2"]
+ sdp = devices["sdp"]
+ # Receiver / High Band Antenna Tile
+
+ # APS Hardware
+ # APS / Receiver Unit2 side
+ rcu2_pcb_ids = recv.RCU_PCB_ID_R.tolist()
+ rcu2_pcb_nrs = list(recv.RCU_PCB_number_R)
+ rcu2_pcb_vrs = list(recv.RCU_PCB_version_R)
+
+ # APS / UniBoard2 side
+ # TODO(Boudewijn): FIXME - shouldn't this be a list with multiple entries?
+ # See also LOFAR2-10774 (APSCT)
+ apsct_pcb_ids = [apsct.APSCT_PCB_ID_R]
+ apsct_pcb_nrs = [apsct.APSCT_PCB_number_R]
+ apsct_pcb_vrs = [apsct.APSCT_PCB_version_R]
+ # TODO(Boudewijn): FIXME - shoudln't this be a list with multiple entries?
+ # See also LOFAR2-11434 (APSPU)
+ apspu_pcb_ids = [apspu.APSPU_PCB_ID_R]
+ apspu_pcb_nrs = [apspu.APSPU_PCB_number_R]
+ apspu_pcb_vrs = [apspu.APSPU_PCB_version_R]
+ unb2_pcb_ids = unb2.UNB2_PCB_ID_R.tolist()
+ unb2_pcb_nrs = list(unb2.UNB2_PCB_number_R)
+ unb2_pcb_vrs = list(sdp.FPGA_hardware_version_R)
+ # TODO(Boudewijn): FIXME:
+ # FPGA_hardware_version_R returns hardware version info per FPGA. Hardware is shared
+ # per 4 fpgas, so why repeating values here?
+ unb2_pcb_vrs = [unb2_pcb_vrs[idx] for idx in [0, 4, 8, 12]]
+
+ # TODO(Boudewijn): FIXME:
+ # fixing list lengths for a large station with multiple APS
+ # this makes sure that the list indexing below works
+ n_aps = 4
+
+ apsct_pcb_ids = apsct_pcb_ids + [0] * (n_aps - len(apsct_pcb_ids))
+ apsct_pcb_nrs = apsct_pcb_nrs + [""] * (n_aps - len(apsct_pcb_nrs))
+ apsct_pcb_vrs = apsct_pcb_vrs + [""] * (n_aps - len(apsct_pcb_vrs))
+
+ apspu_pcb_ids = apspu_pcb_ids + [0] * (n_aps * 2 - len(apspu_pcb_ids))
+ apspu_pcb_nrs = apspu_pcb_nrs + [""] * (n_aps * 2 - len(apspu_pcb_nrs))
+ apspu_pcb_vrs = apspu_pcb_vrs + [""] * (n_aps * 2 - len(apspu_pcb_vrs))
+
+ unb2_pcb_ids = unb2_pcb_ids + [0] * (n_aps * 2 - len(unb2_pcb_ids))
+ unb2_pcb_nrs = unb2_pcb_nrs + [""] * (n_aps * 2 - len(unb2_pcb_nrs))
+ unb2_pcb_vrs = unb2_pcb_vrs + [""] * (n_aps * 2 - len(unb2_pcb_vrs))
+
+ logging.debug("APSPU:")
+ logging.debug("apsct_pcb_ids: %s", apsct_pcb_ids)
+ logging.debug("apsct_pcb_nrs: %s", apsct_pcb_nrs)
+ logging.debug("apsct_pcb_vrs: %s", apsct_pcb_vrs)
+ logging.debug("APSPU:")
+ logging.debug("apspu_pcb_ids: %s", apspu_pcb_ids)
+ logging.debug("apspu_pcb_nrs: %s", apspu_pcb_nrs)
+ logging.debug("apspu_pcb_vrs: %s", apspu_pcb_vrs)
+ logging.debug("UNB2:")
+ logging.debug("unb2_pcb_ids: %s", unb2_pcb_ids)
+ logging.debug("unb2_pcb_nrs: %s", unb2_pcb_nrs)
+ logging.debug("unb2_pcb_vrs: %s", unb2_pcb_vrs)
+
+ aps_pcb_ids = [
+ apspu_pcb_ids[1],
+ unb2_pcb_ids[0],
+ apsct_pcb_ids[0],
+ apspu_pcb_ids[0],
+ unb2_pcb_ids[1],
+ ]
+ aps_pcb_nrs = [
+ apspu_pcb_nrs[1],
+ unb2_pcb_nrs[0],
+ apsct_pcb_nrs[0],
+ apspu_pcb_nrs[0],
+ unb2_pcb_nrs[1],
+ ]
+ aps_pcb_vers = [
+ apsct_pcb_vrs[1],
+ unb2_pcb_vrs[0],
+ apsct_pcb_vrs[0],
+ apsct_pcb_vrs[0],
+ unb2_pcb_vrs[1],
+ ]
+
+ if not save_fig:
+ fig_filenames = []
+ else:
+ fig_filenames = [
+ f'{constants.PLOT_DIR}/{plib.get_timestamp("filename")}_aps_config.png',
+ f'{constants.PLOT_DIR}/{plib.get_timestamp("filename")}_aps_config.pdf',
+ ]
+ plot_aps_configuration(
+ rcu2_pcb_ids,
+ rcu2_pcb_nrs,
+ rcu2_pcb_vrs,
+ aps_pcb_ids,
+ aps_pcb_nrs,
+ aps_pcb_vers,
+ filenames=fig_filenames,
+ )
+
+ # Services
+ # Firmware images
+ firmware_versions = list(sdp.FPGA_firmware_version_R)
+ # make sure there are versions for at least n_aps subracks
+ firmware_versions = firmware_versions + [""] * (n_aps * 8 - len(firmware_versions))
+ aps_services = {}
+ for apsi, fpgai in zip(sorted(list(range(n_aps)) * 8), range(n_aps * 8)):
+ aps_services["aps%i-fpga%02d" % (apsi, fpgai)] = firmware_versions[fpgai]
+ aps_services[f"aps{apsi}-unb2tr" % apsi] = "" # TODO(Boudewijn):
+ aps_services[f"aps{apsi}-recvtr" % apsi] = "" # TODO(Boudewijn):
+ aps_services[f"aps{apsi}-apscttr" % apsi] = "" # TODO(Boudewijn):
+ aps_services[f"aps{apsi}-apsputr" % apsi] = "" # TODO(Boudewijn):
+ # Translators
+ sdptr_software_versions = [sdp.TR_software_version_R]
+ sdptr_software_versions = sdptr_software_versions + [""] * (
+ 2 - len(sdptr_software_versions)
+ )
+ station_services = {}
+ station_services["sdptr0"] = sdptr_software_versions[0]
+ station_services["sdptr1"] = sdptr_software_versions[1]
+ station_services["ccdtr"] = "" # TODO(Boudewijn): currently missing
+ # station control tango devices
+ for dev in devices:
+ station_services[devices[dev].name().lower()] = devices[dev].version_R
+ if not save_fig:
+ figs = []
+ else:
+ figs = [
+ f"{constants.PLOT_DIR}/{plib.get_timestamp('filename')}"
+ "_station_services.png",
+ f"{constants.PLOT_DIR}/{plib.get_timestamp('filename')}"
+ "_station_services.pdf",
+ ]
+ plot_services_configuration(aps_services, station_services, filenames=figs)
+
+
+def plot_aps_configuration(
+ rcu_pcb_identifiers,
+ rcu_pcb_numbers,
+ rcu_pcb_versions,
+ aps_pcb_ids,
+ aps_pcb_nrs,
+ aps_pcb_vers,
+ filenames=None,
+):
+ """Plot the configuration of the Antenna Processing Subrack, both sides."""
+
+ fig, axs = plt.subplots(1, 2, figsize=(20, 8))
+
+ if filenames is None:
+ filenames = [
+ f"{constants.PLOT_DIR}/aps_config.png",
+ f"{constants.PLOT_DIR}/aps_config.pdf",
+ ]
+
+ plot_aps_rcu2_configuration(
+ axs[0], rcu_pcb_identifiers, rcu_pcb_numbers, rcu_pcb_versions
+ )
+ plot_aps_unb2_configuration(axs[1], aps_pcb_ids, aps_pcb_nrs, aps_pcb_vers)
+ for filename in filenames:
+ fig.savefig(filename)
+ fig.show()
+
+
+def plot_aps_rcu2_configuration(
+ plot_ax,
+ rcu_pcb_identifiers,
+ rcu_pcb_numbers,
+ rcu_pcb_versions,
+ rcu_locations=constants.APS_LOCATION_LABELS,
+ rcu_pos_x=None,
+ rcu_pos_y=None,
+):
+ """Plot the configuration of the Antenna Processing Subrack, RCU2 side."""
+
+ if rcu_pos_x is None:
+ rcu_pos_x = constants.APS_RCU_POS_X
+ if rcu_pos_y is None:
+ rcu_pos_y = constants.APS_RCU_POS_Y
+ plot_ax.plot(0, 0)
+ plot_dx = plot_dy = 0.9
+ plot_ax.set_xlim(-1 + plot_dx, np.max(rcu_pos_x) + 1)
+ plot_ax.set_ylim(-1 + plot_dy, np.max(rcu_pos_y) + 1)
+
+ for plot_x, plot_y, loc, pcb_id, pcb_nr, pcb_v in zip(
+ rcu_pos_x,
+ rcu_pos_y,
+ rcu_locations,
+ rcu_pcb_identifiers,
+ rcu_pcb_numbers,
+ rcu_pcb_versions,
+ ):
+ plot_ax.plot(
+ [plot_x, plot_x + plot_dx, plot_x + plot_dx, plot_x, plot_x],
+ [plot_y, plot_y, plot_y + plot_dy, plot_y + plot_dy, plot_y],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.text(plot_x + plot_dx / 2, plot_y + plot_dy, loc, va="top", ha="center")
+ plot_ax.text(
+ plot_x + plot_dx / 2,
+ plot_y + plot_dy / 2,
+ "%s\n%s\n%s" % (pcb_v, pcb_id, pcb_nr),
+ va="center",
+ ha="center",
+ rotation=90,
+ )
+ if pcb_id == 0 and pcb_v == "" and pcb_nr == "":
+ plot_ax.plot(
+ [plot_x, plot_x + plot_dx],
+ [plot_y, plot_y + plot_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.plot(
+ [plot_x + plot_dx, plot_x],
+ [plot_y, plot_y + plot_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.set_title("Configuration of RCU2s")
+ plot_ax.set_xticks([])
+ plot_ax.set_yticks([])
+
+
+def plot_aps_unb2_configuration(
+ plot_ax,
+ aps_pcb_identifiers,
+ aps_pcb_numbers,
+ aps_pcb_versions,
+ mod_locations=constants.APS_LOCATION_LABELS,
+ mod_pos_x=None,
+ mod_pos_y=None,
+):
+ """Plot the configuration of the Antenna Processing Subrack, UNB2 side."""
+
+ if mod_pos_x is None:
+ mod_pos_x = constants.APS_MOD_POS_X
+ if mod_pos_y is None:
+ mod_pos_y = constants.APS_MOD_POS_Y
+ plot_ax.plot(0, 0)
+ plot_dx = 0.9
+ plot_dy = 3.9
+ plot_ax.set_xlim(-1 + plot_dx, np.max(mod_pos_x) + 2)
+ plot_ax.set_ylim(-1 + plot_dx, np.max(mod_pos_y) + 4)
+
+ for plot_x, plot_y, loc, pcb_id, pcb_nr, pcb_v in zip(
+ mod_pos_x,
+ mod_pos_y,
+ mod_locations,
+ aps_pcb_identifiers,
+ aps_pcb_numbers,
+ aps_pcb_versions,
+ ):
+ plot_ax.plot(
+ [plot_x, plot_x + plot_dx, plot_x + plot_dx, plot_x, plot_x],
+ [plot_y, plot_y, plot_y + plot_dy, plot_y + plot_dy, plot_y],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.text(plot_x + plot_dx / 2, plot_y + plot_dy, loc, va="top", ha="center")
+ plot_ax.text(
+ plot_x + plot_dx / 2,
+ plot_y + plot_dy / 2,
+ "%s\n%s\n%s" % (pcb_v, pcb_id, pcb_nr),
+ va="center",
+ ha="center",
+ rotation=90,
+ )
+ if pcb_id == 0 and pcb_v == "" and pcb_nr == "":
+ plot_ax.plot(
+ [plot_x, plot_x + plot_dx],
+ [plot_y, plot_y + plot_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.plot(
+ [plot_x + plot_dx, plot_x],
+ [plot_y, plot_y + plot_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.set_title("Configuration of UniBoard2 side")
+ plot_ax.set_xticks([])
+ plot_ax.set_yticks([])
+
+
+def plot_services_configuration(
+ aps_services,
+ station_services,
+ filenames=None,
+):
+ """Plot the service version information in the setup."""
+
+ if filenames is None:
+ filenames = [
+ f"{constants.PLOT_DIR}/station_services.png",
+ f"{constants.PLOT_DIR}/station_services.pdf",
+ ]
+
+ fig, plot_ax = plt.subplots(1, 1, figsize=(16, 15))
+
+ aps_service_dx = 0.9
+ aps_service_dy = 0.9
+ first_aps_service = sorted(aps_services)[0][5:]
+ plot_y = plot_y0 = len(aps_services) / 2 + 2
+ for aps_service in sorted(aps_services):
+ plot_x = 0 if (aps_service[3] in "01") else 1
+ plot_y = (
+ plot_y0
+ if (aps_service[5:] == first_aps_service and aps_service[3] in "02")
+ else plot_y - 1
+ )
+ plot_y = (
+ plot_y - 1
+ if (aps_service[5:] == first_aps_service and aps_service[3] in "13")
+ else plot_y
+ )
+ plot_ax.plot(
+ [plot_x, plot_x + aps_service_dx, plot_x + aps_service_dx, plot_x, plot_x],
+ [plot_y, plot_y, plot_y + aps_service_dy, plot_y + aps_service_dy, plot_y],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.text(
+ plot_x,
+ plot_y + aps_service_dy / 2,
+ aps_service,
+ va="center",
+ ha="left",
+ color="black",
+ )
+ plot_ax.text(
+ plot_x + aps_service_dx,
+ plot_y + aps_service_dy / 2,
+ aps_services[aps_service],
+ va="center",
+ ha="right",
+ color="blue",
+ )
+ if aps_services[aps_service] == "":
+ plot_ax.plot(
+ [plot_x, plot_x + aps_service_dx],
+ [plot_y, plot_y + aps_service_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.plot(
+ [plot_x + aps_service_dx, plot_x],
+ [plot_y, plot_y + aps_service_dy],
+ color="black",
+ alpha=0.2,
+ )
+
+ stat_service_dx = 1.9
+ stat_service_dy = 0.9
+ plot_x = 0
+ plot_y = 0
+ for stat_service in sorted(station_services):
+ plot_ax.plot(
+ [
+ plot_x,
+ plot_x + stat_service_dx,
+ plot_x + stat_service_dx,
+ plot_x,
+ plot_x,
+ ],
+ [
+ plot_y,
+ plot_y,
+ plot_y + stat_service_dy,
+ plot_y + stat_service_dy,
+ plot_y,
+ ],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.text(
+ plot_x,
+ plot_y + stat_service_dy / 2,
+ stat_service,
+ va="center",
+ ha="left",
+ color="black",
+ )
+ plot_ax.text(
+ plot_x + stat_service_dx,
+ plot_y + stat_service_dy / 2,
+ station_services[stat_service],
+ va="center",
+ ha="right",
+ color="blue",
+ )
+ if station_services[stat_service] == "":
+ plot_ax.plot(
+ [plot_x, plot_x + stat_service_dx],
+ [plot_y, plot_y + stat_service_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_ax.plot(
+ [plot_x + stat_service_dx, plot_x],
+ [plot_y, plot_y + stat_service_dy],
+ color="black",
+ alpha=0.2,
+ )
+ plot_y -= 1
+ plot_ax.set_title("Services in setup")
+ plot_ax.set_xticks([])
+ plot_ax.set_yticks([])
+
+ for filename in filenames:
+ fig.savefig(filename)
+ fig.show()
+
+
+def plot_statistics(devices, save_fig=True):
+ """Plot the statistics: SST, XST and BST"""
+
+ logging.info("Plot subband statistics")
+ if not save_fig:
+ figs = []
+ else:
+ figs = [
+ f"{constants.PLOT_DIR}/{plib.get_timestamp('filename')}"
+ "_subband_statistics.png",
+ f"{constants.PLOT_DIR}/{plib.get_timestamp('filename')}"
+ "_subband_statistics.pdf",
+ ]
+ plot_subband_statistics(devices, filenames=figs)
+
+ # TODO(Boudewijn):
+ # if not silent:
+ # plib.log("Plot crosslet statistics")
+ # TODO(Boudewijn):
+ # if not silent:
+ # plib.log("Plot beamlet statistics")
+
+
+def plot_subband_statistics(
+ devices,
+ xlim=None,
+ ylim=None,
+ filenames=None,
+):
+ """Make the subband statistics plot"""
+
+ if filenames is None:
+ filenames = [
+ f"{constants.PLOT_DIR}/subband_statistics.png",
+ f"{constants.PLOT_DIR}/subband_statistics.pdf",
+ ]
+
+ if xlim is None:
+ xlim = [-5, 305]
+ if ylim is None:
+ ylim = [-105, -30]
+ sst_data, band_names, frequency_axes = _get_subband_statistics(devices)
+
+ fig, _ = plt.subplots(1, 1, figsize=(18, 6))
+ for signal_index in range(len(sst_data)):
+ if band_names[signal_index] is None:
+ continue
+ freq = frequency_axes[band_names[signal_index]]
+ # plot data in dB full scale
+ plot_data = 10 * np.log10(sst_data[signal_index, :] + 1) - 128 - 6 * 4
+ # since we're lacking antenna names,
+ # show the RCU input info as label
+ rcu_index, rcu_input_index = plib.signal_input_2_rcu_index_and_rcu_input_index(
+ signal_index
+ )
+ label = "RCU%02d, Inp%d" % (rcu_index, rcu_input_index)
+ plt.plot(freq / 1e6, plot_data, label=label)
+ plt.grid()
+ plt.legend(bbox_to_anchor=(1, 1), loc="upper left")
+ plt.xlim(xlim)
+ plt.ylim(ylim)
+ plt.xlabel("Frequency (MHz)")
+ plt.ylabel("Power (dB full scale)")
+ plt.suptitle("Subband Statistics for all fully-connected receivers")
+ plt.title(f"{constants.STATION_NAME}; {plib.get_timestamp()}")
+
+ for filename in filenames:
+ fig.savefig(filename)
+ fig.show()
+
+
+def _get_subband_statistics(devices):
+ """Get subband statistics data from the setup, including proper axes and labels"""
+
+ devices = common.devices_list_2_dict(devices)
+ recv = devices["recv"]
+ sst = devices["sst"]
+
+ # get band names for the current configuration
+ cur_band_names = bands.get_band_names(
+ recv.RCU_band_select_R,
+ # TODO(Boudewijn): when correct LCNs are provided
+ # in "RCU_PCB_number_R", uncomment the
+ # following line:
+ # recv.RCU_PCB_number_R,
+ # and remove the following line:
+ recv.RCU_PCB_ID_R,
+ lb_tags=constants.RCU2L_TAGS,
+ hb_tags=constants.RCU2H_TAGS,
+ )
+
+ # replace the aliases of LB1 and LB2 into LB: (same frequency axis)
+ for alias in constants.LB_ALIASES:
+ while alias in cur_band_names:
+ cur_band_names[cur_band_names.index(alias)] = "LB"
+ # get frequency axes
+
+ # TODO(Boudewijn): get frequency axes correct (indexing is incorrect/inconsequent)
+ key1 = "HB2"
+ key2 = "HB1"
+ tmp_key = "magic_band"
+ while key1 in cur_band_names:
+ cur_band_names[cur_band_names.index(key1)] = tmp_key
+ while key2 in cur_band_names:
+ cur_band_names[cur_band_names.index(key1)] = key1
+ while tmp_key in cur_band_names:
+ cur_band_names[cur_band_names.index(tmp_key)] = key2
+
+ frequency_axes = {}
+ for band_name in bands.get_valid_band_names():
+ if band_name in cur_band_names:
+ frequency_axes[band_name] = bands.get_frequency_for_band_name(band_name)
+ # get sst data
+ sst_data = sst.sst_r
+ # make sure header data is of correct size
+ cur_band_names = cur_band_names + [None] * (len(sst_data) - len(cur_band_names))
+ return sst_data, cur_band_names, frequency_axes
diff --git a/lofar_station_client/dts_outside.py b/lofar_station_client/dts_outside.py
deleted file mode 100644
index 1fd015f..0000000
--- a/lofar_station_client/dts_outside.py
+++ /dev/null
@@ -1,1420 +0,0 @@
-# dts_outside.py: Module with functions to restart dts outside and
-# put it in a default configuration
-#
-# -*- coding: utf-8 -*-
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-""" module dts_outside
-
-This module contains functions to:
-- restart the Dwingeloo Test Station (Outside)
-- configure the Dwingeloo Test Station (Outside) to a defined default
-
-Boudewijn Hut
-"""
-
-import sys
-import time
-import datetime
-import os
-import numpy as np
-import matplotlib.pyplot as plt
-import processing_lib as plib
-import RCUs
-
-
-# define some constants for the setup:
-N_FPGA = 4 # number of fpgas
-
-APS_LOCATION_LABELS = [
- "Slot%02d" % nr for nr in range(32) # pylint: disable=consider-using-f-string
-]
-# positions of RCU boards. (0,0) is bottom, left
-APS_RCU_POS_X = (
- [11, 10, 9, 8, 7, 6]
- + [11, 10, 9, 8, 7] * 2
- + [4, 3, 2, 1, 0] * 2
- + [5, 4, 3, 2, 1, 0]
-)
-APS_RCU_POS_Y = [2] * 6 + [1] * 5 + [0] * 5 + [2] * 5 + [1] * 5 + [0] * 6
-# positions of the other modules in the APS
-APS_MOD_POS_X = [0, 1, 3, 4, 5]
-APS_MOD_POS_Y = [0] * 5
-
-# if the names are correctly provided by the RCU2s,
-# then update plot_subband_statistics() (see annotation there)
-# and remove these two lines:
-RCU2L_TAGS = ["8393812", "8416938", "8469028", "8386883", "8374523"]
-RCU2H_TAGS = ["8514859", "8507272"]
-
-PLOT_DIR = "inspection_plots"
-STATION_NAME = "DTS-Outside"
-
-# RCU Masks:
-# LBA masks
-RCU2L_MASK = [rcu_nr in RCUs.RCU2L for rcu_nr in range(32)]
-LBA_MASK = [[True] * 3 if rcu_nr in RCUs.RCU2L else [False] * 3 for rcu_nr in range(32)]
-# HBA masks
-# signal inputs connected to HBAT power:
-si_hbat_pwr = [signal_index * 3 + x for signal_index in RCUs.RCU2Hpwr for x in range(3)]
-# signal inputs. HBAT control connect to first element:
-si_nbat_ctl = [signal_index * 3 + x for signal_index in RCUs.RCU2Hctl for x in range(3)]
-#
-rcu_indices = range(32)
-hba_rcu_mask_pwr = [rcui in RCUs.RCU2Hpwr for rcui in rcu_indices]
-hba_rcu_mask_ctrl = [rcui in RCUs.RCU2Hctl for rcui in rcu_indices]
-hba_PwrMask = [
- [True] * 3 if rcui in RCUs.RCU2Hpwr else [False] * 3 for rcui in rcu_indices
-]
-hba_CtlMask = [
- [True] * 3 if rcui in RCUs.RCU2Hctl else [False] * 3 for rcui in rcu_indices
-]
-hba_mask = [[True] * 3 if rcui in RCUs.RCU2H else [False] * 3 for rcui in rcu_indices]
-
-folders = [PLOT_DIR]
-for folder in folders:
- if not os.path.exists(folder):
- os.makedirs(folder)
-
-
-def init_and_set_to_default_config(devices, skip_boot=False, silent=False):
- """
- Restart system and set to default configuration
-
- Input arguments:
- devices = struct of software devices of m&c of the station
- name: object
-
- Output arguments:
- found_error = True if an error ocurred, False otherwise
- """
- found_error = False
- # initialisation
- if not skip_boot:
- if not silent:
- plib.log("Start initialisation")
- found_error = found_error | initialise(devices)
- else:
- plib.log("SKIPPING INITIALISATION (skip_init was set to True)")
- if not silent:
- plib.log("Check initialisation")
- found_error = found_error | check_initialise(devices)
- # configuring
- if not silent:
- plib.log("Start setting to default configuration")
- found_error = found_error | set_to_default_configuration(devices, silent=silent)
- if not silent:
- plib.log("Check setting to default configuration")
- found_error = found_error | check_set_to_default_configuration(
- devices, readonly=False
- ) # readonly=False, only at first time after init
- # plot statistics data
- if not silent:
- plib.log("Plot statistics data")
- plot_statistics(devices)
- return found_error
-
-
-def initialise(devices, timeout=90, debug_flag=False):
- """
- Initialise the system
-
- Input arguments:
- timeout = timeout to flag an error in seconds
-
- Output arguments:
- found_error
- """
- devices = devices_list_2_dict(devices)
- boot = devices["boot"]
- recv = devices["recv"]
- sst = devices["sst"]
- unb2 = devices["unb2"]
- # initialise boot device
- boot.put_property({"DeviceProxy_Time_Out": 60})
- boot.off()
- boot.initialise()
- boot.on()
- # reboot system
- # increase timeouts for that
- if debug_flag:
- print("Time out settings prior to increase for reboot:")
- print(recv.get_timeout_millis())
- print(unb2.get_timeout_millis())
- print(sst.get_timeout_millis())
- recv.set_timeout_millis(30000)
- unb2.set_timeout_millis(60000)
- sst.set_timeout_millis(20000)
- unb2.put_property({"UNB2_On_Off_timeout": 20})
- recv.put_property({"RCU_On_Off_timeout": 60})
- # and start reboot process
- boot.reboot()
- print("Rebooting now")
- time_start = time.time()
- time_dt = time.time() - time_start
- # indicate progress
- while boot.booting_R and (time_dt < timeout):
- if time_dt % 5 < 1: # print every 5 seconds
- print(f"Initialisation at {boot.progress_R}%: {boot.status_R}")
- time.sleep(1)
- time_dt = time.time() - time_start
- print(f"Initialisation took {time_dt} seconds")
- # check for errors
- found_error = False
- if boot.booting_R:
- found_error = True
- print(f"Warning! Initialisation still ongoing after timeout ({timeout} sec)")
- if boot.uninitialised_devices_R:
- found_error = True
- print(f"Warning! Did not initialise {boot.uninitialised_devices_R}.")
- return found_error
-
-
-def check_initialise(devices):
- """
- Check the initialisation of the system by an independent monitoring
- point.
-
- Note that this method should be used after calling initialise()
-
- All checks that are directly related to the boot device are already
- in the method initialise()
- """
- found_error = False
- found_error = found_error | check_firmware_loaded(devices)
- found_error = found_error | check_clock_locked(devices)
- return found_error
-
-
-def check_firmware_loaded(devices, debug_flag=True):
- """
- Verify that the firmware has loaded in the fpgas
- Returns True if not loaded, False otherwise
- """
- devices = devices_list_2_dict(devices)
- sdp = devices["sdp"]
- res = sdp.FPGA_boot_image_R
- if debug_flag:
- print("")
- print("Checking sdp.FPGA_boot_image_R:")
- print(f"Reply: {res}")
- if not np.array_equal(res[0:N_FPGA], np.ones(N_FPGA)):
- print("Firmware is not loaded!")
- return True
- if debug_flag:
- print("Ok - Firmware loaded")
- return False
-
-
-def check_clock_locked(devices, debug_flag=True):
- """
- Verify that the APSCT clock is locked
- Returns True if not loaded, False otherwise
- """
- devices = devices_list_2_dict(devices)
- apsct = devices["apsct"]
- res = apsct.APSCT_PLL_200MHz_error_R
- if debug_flag:
- print("")
- print("Checking apsct.APSCT_PLL_200MHz_error_R:")
- print(f"Reply: {res}")
- if res:
- print("APSCT clock not locked!")
- return True
- if debug_flag:
- print("Ok - APSCT clock locked")
- return False
-
-
-def set_to_default_configuration(devices, silent=True):
- """
- Set the station to its default configuration
- Returns True if error found, False otherwise
- """
- found_error = False
- if not silent:
- plib.log("Start configuring Low Receivers")
- found_error = found_error | set_rcul_to_default_config(devices)
- if not silent:
- plib.log("Start configuring High Receivers")
- found_error = found_error | set_rcuh_to_default_config(devices)
- if not silent:
- plib.log("Start configuring Station Digital Processor")
- found_error = found_error | set_sdp_to_default_config(devices)
- if not silent:
- plib.log("Start configuring Subband Statistics")
- found_error = found_error | set_sdp_sst_to_default_config(devices)
- if not silent:
- plib.log("Start configuring Crosslet Statistics")
- found_error = found_error | set_sdp_xst_to_default_config(devices)
- if not silent:
- plib.log("Start configuring Beamlet Statistics")
- found_error = found_error | set_sdp_bst_to_default_config(devices)
- return found_error
-
-
-def set_rcul_to_default_config(devices):
- """
- Set RCU Low to its default configuration
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- #
- # Set RCUs Lows in default modes
- #
-
- recv.set_defaults()
-
- # Set attenuator, antenna power etc.
- recv.RCU_band_select_RW = set_ant_masked(
- recv, LBA_MASK, recv.RCU_band_select_R, [[1] * 3] * 32
- ) # 1 = 30-80 MHz
- recv.RCU_PWR_ANT_on_RW = set_ant_masked(
- recv, LBA_MASK, recv.RCU_PWR_ANT_on_R, [[True] * 3] * 32
- ) # Off
- recv.RCU_attenuator_dB_RW = set_ant_masked(
- recv, LBA_MASK, recv.RCU_attenuator_dB_R, [[0] * 3] * 32
- ) # 0dB attenuator
- wait_receiver_busy(recv)
-
- #
- found_error = False
- return found_error
-
-
-def set_rcuh_to_default_config(devices):
- """
- Set RCU High to its default configuration
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- #
- # Setup HBA RCUs and HBA Tiles
- #
-
- # Set RCU in correct mode
- rcu_modes = recv.RCU_band_select_R
- rcu_modes[8] = [2, 2, 2] # 2 = 110-180 MHz, 1 = 170-230 MHz, 4 = 210-270 MHz ??
- rcu_modes[9] = [2, 2, 2]
- recv.RCU_band_select_RW = set_ant_masked(
- recv, hba_mask, recv.RCU_band_select_R, rcu_modes
- )
- wait_receiver_busy(recv)
-
- # Switch on the Antenna
- recv.RCU_PWR_ANT_on_RW = set_ant_masked(
- recv, hba_PwrMask, recv.RCU_PWR_ANT_on_R, [[False] * 3] * 32
- ) # Switch off
- wait_receiver_busy(recv)
- recv.HBAT_PWR_LNA_on_RW = set_hba_mask(
- recv, hba_CtlMask, recv.HBAT_PWR_LNA_on_R, [[True] * 32] * 96
- ) # LNA default on
- recv.HBAT_BF_delay_steps_RW = set_hba_mask(
- recv, hba_CtlMask, recv.HBAT_BF_delay_steps_R, [[0] * 32] * 96
- ) # Default
- recv.HBAT_PWR_on_RW = set_hba_mask(
- recv, hba_CtlMask, recv.HBAT_PWR_on_R, [[True] * 32] * 96
- ) # Default
- wait_receiver_busy(recv)
- recv.RCU_PWR_ANT_on_RW = set_ant_masked(
- recv, hba_PwrMask, recv.RCU_PWR_ANT_on_R, [[True] * 3] * 32
- ) # Switch on
- wait_receiver_busy(recv)
- # default in the tile: after power-on, zero delay
- # delays should be set by the tile beam device
- # recv.HBAT_BF_delay_steps_RW=[[1]*32]*32
- # wait_receiver_busy(recv)
- # by default: equal delay settings for all elements --> Pointing to zenith
-
- # TODO: read values and only update the ones that need to be changed.
- # Could be a function that manages the masks.
- print(recv.RCU_PWR_ANT_on_RW)
- print()
- print("done")
-
- #
- found_error = False
- return found_error
-
-
-####
-# RCU specific functions and variables for DTS-Outside
-####
-
-# General RCU functions
-
-
-def wait_receiver_busy(recv):
- """
- Wait for the Receiver Translators busy monitoring point to return False
- """
- time.sleep(0.5)
- while recv.RECVTR_translator_busy_R:
- time.sleep(0.1)
-
-
-def set_ant_masked(recv, mask, old_value, new_value):
- """
- Set the antenna mask for the Receiver Translator
- """
- recv.ANT_mask_RW = mask
-
- for imask, maski in enumerate(mask):
- for rcu_input_index in range(3):
- if maski[rcu_input_index]:
- old_value[imask][rcu_input_index] = new_value[imask][rcu_input_index]
- # this used to be the following one liner, but that line was too long.
- # Here is it, but cut in pieces:
- # old_value[imask] =
- # [new_value[imask][j] if maski[j] else old_value[imask][j] for j in range(3)]
- return old_value
-
-
-def set_hba_mask(recv, mask, old_value, new_value):
- """
- Set the hba mask for the Receiver Translator
- """
- # TODO: Is this function equal to set_ant_masked()? Can this definition be removed?
- recv.ANT_mask_RW = mask
- for imask, maski in enumerate(mask):
- for i in range(3):
- if maski[i]:
- old_value[imask * 3 + i] = new_value[imask * 3 + i]
- else:
- old_value[imask * 3 + i] = old_value[imask * 3 + i]
- return old_value
-
-
-####
-# Till here RCU specific functions and variables for DTS-Outside
-####
-
-
-def set_sdp_to_default_config(devices):
- """
- Set RCU High to its default configuration
- """
- devices = devices_list_2_dict(devices)
- sdp = devices["sdp"]
- #
- # Set SDP in default modes
- #
-
- plib.log("Start configuring SDP to default")
- sdp.set_defaults()
- # should be part of sdp.set_defaults:
- next_ring = [False] * 16
- next_ring[3] = True
- sdp.FPGA_ring_use_cable_to_previous_rn_RW = [True] + [False] * 15
- sdp.FPGA_ring_use_cable_to_next_rn_RW = next_ring
- sdp.FPGA_ring_nof_nodes_RW = [N_FPGA] * 16
- sdp.FPGA_ring_node_offset_RW = [0] * 16
-
- #
- found_error = False
- return found_error
-
-
-def set_sdp_sst_to_default_config(devices):
- """
- Set SSTs to default
- """
- devices = devices_list_2_dict(devices)
- sst = devices["sst"]
-
- sst.set_defaults()
- # prepare for subband stati
- sst.FPGA_sst_offload_weighted_subbands_RW = [
- True
- ] * 16 # should be in set_defaults()
-
- #
- found_error = False
- return found_error
-
-
-def set_sdp_xst_to_default_config(devices):
- """
- Set XSTs to default
- """
- devices = devices_list_2_dict(devices)
- xst = devices["xst"]
-
- # prepare for correlations
- int_time = 1 # used for 'medium' measurement using statistics writer
- subband_step_size = 7 # 0 is no stepping
- n_xst_subbands = 7
- subband_select = [subband_step_size, 0, 1, 2, 3, 4, 5, 6]
-
- xst.set_defaults()
- # should be part of set_defaults()
- xst.FPGA_xst_processing_enable_RW = [False] * 16
- # this (line above) should be first, then configure and enable
- xst.fpga_xst_ring_nof_transport_hops_RW = [3] * 16 # [((n_fgpa/2)+1)]*16
-
- crosslets = [0] * 16
- for fpga_nr in range(N_FPGA):
- crosslets[fpga_nr] = n_xst_subbands
- xst.FPGA_xst_offload_nof_crosslets_RW = crosslets
-
- xst.FPGA_xst_subband_select_RW = [subband_select] * 16
- xst.FPGA_xst_integration_interval_RW = [int_time] * 16
-
- xst.FPGA_xst_processing_enable_RW = [True] * 16
- #
- found_error = False
- return found_error
-
-
-def set_sdp_bst_to_default_config(devices):
- """
- Set XSTs to default
- """
- devices = devices_list_2_dict(devices)
- beamlet = devices["beamlet"]
-
- # prepare for beamformer
- beamlet.set_defaults()
-
- beamlet.FPGA_bf_ring_nof_transport_hops_RW = [[1]] * 3 + [[0]] * 13
- # line above should be part of set_defaults(), specifically for DTS-Outside
-
- beamletoutput_mask = [False] * 16
- beamletoutput_mask[3] = True
- # beamlet.FPGA_beamlet_output_enable_RW=beamletoutput_mask
- beamlet.FPGA_beamlet_output_hdr_eth_destination_mac_RW = ["40:a6:b7:2d:4f:68"] * 16
- beamlet.FPGA_beamlet_output_hdr_ip_destination_address_RW = ["192.168.0.249"] * 16
-
- # define weights
- # dim: FPGA, input, subband
- weights_xx = beamlet.FPGA_bf_weights_xx_R.reshape([16, 6, 488])
- weights_yy = beamlet.FPGA_bf_weights_yy_R.reshape([16, 6, 488])
- # make a beam for the HBA
- weights_xx[:][:] = 0
- weights_yy[:][:] = 0
- for signal_index in [0, 1]: # range(8*3,9*3+3):
- unb2i = signal_index // 12
- signal_index_2 = signal_index // 2
- signal_index_2 = signal_index_2 % 6
- if signal_index % 2 == 0:
- weights_xx[unb2i, signal_index_2] = 16384
- else:
- weights_yy[unb2i, signal_index_2] = 16384
-
- beamlet.FPGA_bf_weights_xx_RW = weights_xx.reshape([16, 6 * 488])
- beamlet.FPGA_bf_weights_yy_RW = weights_yy.reshape([16, 6 * 488])
-
- blt = beamlet.FPGA_beamlet_subband_select_R.reshape([16, 12, 488])
- blt[:] = np.array(range(10, 10 + 488))[np.newaxis, np.newaxis, :]
- beamlet.FPGA_beamlet_subband_select_RW = blt.reshape([16, 12 * 488])
-
- #
- found_error = False
- return found_error
-
-
-def enable_outputs(
- devices, enable_sst=True, enable_xst=True, enable_bst=True, enable_beam_output=True
-):
- """
- Enable the station outputs after everything is set.
- """
- devices = devices_list_2_dict(devices)
- if enable_beam_output:
- sdp = devices["sdp"]
- sdp.FPGA_processing_enable_RW = [True] * 16
- if enable_sst:
- sst = devices["sst"]
- sst.FPGA_sst_offload_enable_RW = [True] * 16
- if enable_xst:
- xst = devices["xst"]
- xst.FPGA_xst_offload_enable_R = [True] * 16
- if enable_bst:
- beamlet = devices["beamlet"]
- beamlet.FPGA_beamlet_output_enable_RW = [i == 3 for i in range(16)]
-
- #
- found_error = False
- return found_error
-
-
-def check_set_to_default_configuration(devices, readonly=True):
- """
- Check the default configuration of the system to be set correctly.
- """
- found_error = False
- found_error = found_error | check_default_configuration_low_receivers(devices)
- found_error = found_error | check_default_configuration_hbat(
- devices, readonly=readonly
- )
- found_error = found_error | check_rcu_fpga_interface(devices, si_to_check=range(18))
- found_error = found_error | check_set_sdp_xst_to_default_config(
- devices, lane_mask=range(0, 6)
- )
-
- print_fpga_input_statistics(devices)
-
- return found_error
-
-
-def check_default_configuration_low_receivers(devices, mask=None, debug_flag=True):
- """
- Check that the LNAs are on
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- found_error = False
-
- if mask is None:
- mask = LBA_MASK
-
- res = recv.RCU_PWR_ANT_on_R
- if len(mask) > 0:
- res = res[mask]
- if debug_flag:
- print("")
- print("Checking recv.RCU_PWR_ANT_on_R:")
- print(f"Reply{' (masked)' if len(mask) > 0 else ''}: {res}")
- if not (res).all():
- print("One or more LBAs are powered off!")
- found_error = True
- else:
- if debug_flag:
- print("Ok - Low Receivers operational")
- return found_error
-
-
-def check_default_configuration_hbat(devices, readonly=True):
- """
- Check that the HBA Tiles are on
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- HBATsi = [rcu * 3 + r_input for rcu in RCUs.RCU2Hpwr for r_input in range(3)]
- if not readonly:
- recv.RECVTR_monitor_rate_RW = 1 # for this checking
- time.sleep(3)
-
- found_error = False
- found_error = found_error | check_rcu_vin(devices, si_mask=HBATsi)
- found_error = found_error | check_hbat_vout(devices, si_mask=HBATsi)
- found_error = found_error | check_hbat_iout(devices, si_mask=HBATsi)
-
- if not readonly:
- recv.RECVTR_monitor_rate_RW = 10
-
- # TODO - what is checked for here?
- print(recv.HBAT_PWR_LNA_on_R[8:10])
- print(recv.ANT_mask_RW[8:10])
-
- return found_error
-
-
-def check_rcu_vin(devices, si_mask=None, valid_range=None, debug_flag=True):
- """
- Verify that the HBA Tile is powered on by comparing the V_in monitoring
- point on the RCU against the thresholds for the given signal inputs.
-
- If no si_mask is given, all returned values are compared.
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- if si_mask is None:
- si_mask = []
- if valid_range is None:
- valid_range = [30, 50]
-
- res = recv.RCU_PWR_ANT_VIN_R
- if len(si_mask) > 0:
- res = [res[si // 3][si % 3] for si in si_mask]
- if debug_flag:
- print("")
- print("Checking recv.RCU_PWR_ANT_VIN_R:")
- print(f"Reply{' (masked)' if len(si_mask) > 0 else ''}: {res}")
- if not (
- np.all(valid_range[0] < np.array(res))
- and np.all(np.array(res) < valid_range[1])
- ):
- print("No power at input RCU2!")
- return True
- if debug_flag:
- print("Ok - Power at input RCU2")
- return False
-
-
-def check_hbat_vout(devices, si_mask=None, valid_range=None, debug_flag=True):
- """
- Verify that the HBA Tile is powered on by comparing the V_out monitoring
- point on the RCU against the thresholds for the given signal inputs.
-
- If no si_mask is given, all returned values are compared.
- """
- # Vout should be 48 +- 1 V
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- if si_mask is None:
- si_mask = []
- if valid_range is None:
- valid_range = [30, 50]
-
- res = recv.RCU_PWR_ANT_VOUT_R
- if len(si_mask) > 0:
- res = [res[si // 3][si % 3] for si in si_mask]
- if debug_flag:
- print("")
- print("Checking recv.RCU_PWR_ANT_VOUT_R:")
- print(f"Reply{' (masked)' if len(si_mask) > 0 else ''}: {res}")
- if not (
- np.all(valid_range[0] < np.array(res))
- and np.all(np.array(res) < valid_range[1])
- ):
- print("Not all HBA Tiles powered on!")
- return True
- if debug_flag:
- print("Ok - HBA Tiles powered on")
- return False
-
-
-def check_hbat_iout(devices, si_mask=None, valid_range=None, debug_flag=True):
- """
- Verify that the HBA Tile is powered on by comparing the I_out monitoring
- point on the RCU against the thresholds for the given signal inputs.
-
- If no si_mask is given, all returned values are compared.
- """
- # Iout = 0.7 +- 0.1 Amp
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- if si_mask is None:
- si_mask = []
- if valid_range is None:
- valid_range = [0.6, 0.8]
-
- res = recv.RCU_PWR_ANT_IOUT_R
- if len(si_mask) > 0:
- res = [res[si // 3][si % 3] for si in si_mask]
- if debug_flag:
- print("")
- print("Checking recv.RCU_PWR_ANT_IOUT_R:")
- print(f"Reply{' (masked)' if len(si_mask) > 0 else ''}: {res}")
- if not (
- np.all(valid_range[0] < np.array(res))
- and np.all(np.array(res) < valid_range[1])
- ):
- print("Not all HBA Tiles powered on (draw current)!")
- return True
- if debug_flag:
- print("Ok - HBA Tiles powered on (draw current)")
- return False
-
-
-def check_rcu_fpga_interface(devices, si_to_check=range(18)):
- """
- Function to check the RCU - FPGA interface
-
- # if error occurs, try to reboot by resetting Uniboard (switch)
- # if only one works than the APSCT board is not making contact with the backplane.
- # (Can take up to 3 times to get it right..)
- # 23/6/2022T15:42 errorcode 0x4h: xxxxx on all of them.
- # Error descriptions can be found here:
- # https://support.astron.nl/jira/browse/L2SDP-774
- """
- devices = devices_list_2_dict(devices)
- sdp = devices["sdp"]
- found_error = False
- # TODO: the number of calls can be reduced
- for signal_index in si_to_check:
- lock = sdp.FPGA_jesd204b_csr_dev_syncn_R[signal_index // 12][signal_index % 12]
- if lock != 1:
- found_error = True
- print(f"Transceiver SI: {signal_index} is not locked {lock}")
- err0 = sdp.FPGA_jesd204b_rx_err0_R[signal_index // 12][signal_index % 12]
- if err0 != 0:
- found_error = True
- print(f"SI {signal_index} has JESD err0: 0x{err0:x}h")
- err1 = sdp.FPGA_jesd204b_rx_err1_R[signal_index // 12][signal_index % 12]
- if (err1 & 0x02FF) != 0:
- found_error = True
- print(f"SI {signal_index} has JESD err1: 0x{err1:x}h")
- return found_error
-
-
-def check_set_sdp_xst_to_default_config(
- devices, lane_mask=None, time_1=None, time_dt=2.5, debug_flag=True
-):
- """
- Check sdp/xst configuration by checking the returned timestamps for:
- 1. all being the same
- 2. not epoch zero (at 1 Jan 1970 00:00:00)
- 3. within 5 seconds from the local clock timestamp (now +/- 2.5 sec)
-
- Input arguments:
- time_1 = number of seconds since the epoch to compare against
- Default: None --> now()
- time_dt = delta in seconds around time_1, in which the xst timestamps should fall.
- Default: 2.5 --> p/m 2.5 sec
- """
- devices = devices_list_2_dict(devices)
- xst = devices["xst"]
- if lane_mask is None:
- lane_mask = []
- if time_1 is None:
- time_1 = datetime.datetime.now().timestamp()
- res = xst.xst_timestamp_R
- if len(lane_mask) > 0: # mask lanes
- res = [res[lane] for lane in lane_mask]
- if debug_flag:
- print("")
- print("Checking xst.xst_timestamp_R:")
- print(f"Reply{' (masked)' if len(lane_mask) > 0 else ''}: {res}")
- print("Converted:")
- print("\n".join([f"{time.asctime(time.gmtime(xst_time))}" for xst_time in res]))
- if not (res == res[0]).all():
- print("Not all xst timestamps are equal!")
- return True
- if res[0] == 0:
- print("One or more xst timestamps are zero!")
- return True
- if abs(res[0] - time_1) > time_dt:
- print("One or more xst timestamps are outside the specified time window!")
- return True
- if debug_flag:
- print("Ok - xst configured correctly")
- return False
-
-
-def print_fpga_input_statistics(devices):
- """
- Print FPGA input statistics
- """
- devices = devices_list_2_dict(devices)
- sdp = devices["sdp"]
-
- # get data from device
- dc_offset = sdp.FPGA_signal_input_mean_R
- signal_input_rms = sdp.FPGA_signal_input_rms_R
- lock_to_adc = sdp.FPGA_jesd204b_csr_dev_syncn_R
- err0 = sdp.FPGA_jesd204b_rx_err0_R
- err1 = sdp.FPGA_jesd204b_rx_err1_R
- count = sdp.FPGA_jesd204b_csr_rbd_count_R
- signal_index = np.reshape(np.arange(count.shape[0] * count.shape[1]), count.shape)
- # get some info on the hardware connections
- pn_index = plib.signal_index_2_processing_node_index(signal_index)
- pn_input_index = plib.signal_index_2_processing_node_input_index(signal_index)
- rcu_index = plib.signal_index_2_rcu_index(signal_index)
- rcu_input_index = plib.signal_index_2_rcu_input_index(signal_index)
- # print
- print("+------------+-------------+----------------------------------------------+")
- print("| Input | Processed by| Statistics of input |")
- print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
- print("|RCU|RCU| SI | Node | Node | DC | RMS | Lock| Err0 | Err1 | Count|")
- print("|idx|inp| | Index| Input| Offset | (LSB) | | (0x) | (0x) | (0x) |")
- print("| |idx| | | Index| (LSB) | | | | | |")
- print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
- pfmt = "| %02d| %1d |%3d | %02d | %2d | %7.2f | %7.2f | %3s | %04x | %04x | %04x |"
- for rcui, rcuii, signali, pni, pnii, dco, rms, lock, err_0, err_1, cnt in zip(
- rcu_index.flatten(),
- rcu_input_index.flatten(),
- signal_index.flatten(),
- pn_index.flatten(),
- pn_input_index.flatten(),
- dc_offset.flatten(),
- signal_input_rms.flatten(),
- lock_to_adc.flatten(),
- err0.flatten(),
- err1.flatten(),
- count.flatten(),
- ):
- print(
- pfmt % (rcui, rcuii, signali, pni, pnii, dco, rms, lock, err_0, err_1, cnt)
- )
- print("+---+---+----+------+------+---------+---------+-----+------+------+------+")
-
-
-def report_setup_configuration(devices, save_fig=True, debug_flag=False):
- """
- Read name and version data from the setup and print that as a report
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- apsct = devices["apsct"]
- apspu = devices["apspu"]
- unb2 = devices["unb2"]
- sdp = devices["sdp"]
- # Receiver / High Band Antenna Tile
-
- # APS Hardware
- # APS / Receiver Unit2 side
- rcu2_pcb_ids = recv.RCU_PCB_ID_R.tolist()
- rcu2_pcb_nrs = list(recv.RCU_PCB_number_R)
- rcu2_pcb_vrs = list(recv.RCU_PCB_version_R)
-
- # APS / UniBoard2 side
- # TODO, FIXME - shouldn't this be a list with multiple entries?
- # See also LOFAR2-10774 (APSCT)
- apsct_pcb_ids = [apsct.APSCT_PCB_ID_R]
- apsct_pcb_nrs = [apsct.APSCT_PCB_number_R]
- apsct_pcb_vrs = [apsct.APSCT_PCB_version_R]
- # TODO, FIXME - shoudln't this be a list with multiple entries?
- # See also LOFAR2-11434 (APSPU)
- apspu_pcb_ids = [apspu.APSPU_PCB_ID_R]
- apspu_pcb_nrs = [apspu.APSPU_PCB_number_R]
- apspu_pcb_vrs = [apspu.APSPU_PCB_version_R]
- unb2_pcb_ids = unb2.UNB2_PCB_ID_R.tolist()
- unb2_pcb_nrs = list(unb2.UNB2_PCB_number_R)
- unb2_pcb_vrs = list(sdp.FPGA_hardware_version_R)
- # TODO, FIXME:
- # FPGA_hardware_version_R returns hardware version info per FPGA. Hardware is shared
- # per 4 fpgas, so why repeating values here?
- unb2_pcb_vrs = [unb2_pcb_vrs[idx] for idx in [0, 4, 8, 12]]
-
- # TODO, FIXME:
- # fixing list lengths for a large station with multiple APS
- # this makes sure that the list indexing below works
- n_aps = 4
- apsct_pcb_ids = apsct_pcb_ids + [0] * (n_aps - len(apsct_pcb_ids))
- apsct_pcb_nrs = apsct_pcb_nrs + [""] * (n_aps - len(apsct_pcb_nrs))
- apsct_pcb_vrs = apsct_pcb_vrs + [""] * (n_aps - len(apsct_pcb_vrs))
- apspu_pcb_ids = apspu_pcb_ids + [0] * (n_aps * 2 - len(apspu_pcb_ids))
- apspu_pcb_nrs = apspu_pcb_nrs + [""] * (n_aps * 2 - len(apspu_pcb_nrs))
- apspu_pcb_vrs = apspu_pcb_vrs + [""] * (n_aps * 2 - len(apspu_pcb_vrs))
- unb2_pcb_ids = unb2_pcb_ids + [0] * (n_aps * 2 - len(unb2_pcb_ids))
- unb2_pcb_nrs = unb2_pcb_nrs + [""] * (n_aps * 2 - len(unb2_pcb_nrs))
- unb2_pcb_vrs = unb2_pcb_vrs + [""] * (n_aps * 2 - len(unb2_pcb_vrs))
-
- if debug_flag:
- print("APSPU:")
- print(f"apsct_pcb_ids: {apsct_pcb_ids}")
- print(f"apsct_pcb_nrs: {apsct_pcb_nrs}")
- print(f"apsct_pcb_vrs: {apsct_pcb_vrs}")
- print("APSPU:")
- print(f"apspu_pcb_ids: {apspu_pcb_ids}")
- print(f"apspu_pcb_nrs: {apspu_pcb_nrs}")
- print(f"apspu_pcb_vrs: {apspu_pcb_vrs}")
- print("UNB2:")
- print(f"unb2_pcb_ids: {unb2_pcb_ids}")
- print(f"unb2_pcb_nrs: {unb2_pcb_nrs}")
- print(f"unb2_pcb_vrs: {unb2_pcb_vrs}")
-
- aps_pcb_ids = [
- apspu_pcb_ids[1],
- unb2_pcb_ids[0],
- apsct_pcb_ids[0],
- apspu_pcb_ids[0],
- unb2_pcb_ids[1],
- ]
- aps_pcb_nrs = [
- apspu_pcb_nrs[1],
- unb2_pcb_nrs[0],
- apsct_pcb_nrs[0],
- apspu_pcb_nrs[0],
- unb2_pcb_nrs[1],
- ]
- aps_pcb_vers = [
- apsct_pcb_vrs[1],
- unb2_pcb_vrs[0],
- apsct_pcb_vrs[0],
- apsct_pcb_vrs[0],
- unb2_pcb_vrs[1],
- ]
-
- if not save_fig:
- fig_filenames = []
- else:
- fig_filenames = [
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_aps_config.png',
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_aps_config.pdf',
- ]
- plot_aps_configuration(
- rcu2_pcb_ids,
- rcu2_pcb_nrs,
- rcu2_pcb_vrs,
- aps_pcb_ids,
- aps_pcb_nrs,
- aps_pcb_vers,
- filenames=fig_filenames,
- )
-
- # Services
- # Firmware images
- firmware_versions = list(sdp.FPGA_firmware_version_R)
- # make sure there are versions for at least n_aps subracks
- firmware_versions = firmware_versions + [""] * (n_aps * 8 - len(firmware_versions))
- aps_services = {}
- for apsi, fpgai in zip(sorted([i for i in range(n_aps)] * 8), range(n_aps * 8)):
- aps_services["aps%i-fpga%02d" % (apsi, fpgai)] = firmware_versions[fpgai]
- aps_services["aps%i-unb2tr" % apsi] = "" # TODO
- aps_services["aps%i-recvtr" % apsi] = "" # TODO
- aps_services["aps%i-apscttr" % apsi] = "" # TODO
- aps_services["aps%i-apsputr" % apsi] = "" # TODO
- # Translators
- sdptr_software_versions = [sdp.TR_software_version_R]
- sdptr_software_versions = sdptr_software_versions + [""] * (
- 2 - len(sdptr_software_versions)
- )
- station_services = {}
- station_services["sdptr0"] = sdptr_software_versions[0]
- station_services["sdptr1"] = sdptr_software_versions[1]
- station_services["ccdtr"] = "" # TODO, currently missing
- # station control tango devices
- for dev in devices:
- station_services[devices[dev].name().lower()] = devices[dev].version_R
- if not save_fig:
- figs = []
- else:
- figs = [
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_station_services.png',
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_station_services.pdf',
- ]
- plot_services_configuration(aps_services, station_services, filenames=figs)
-
-
-def plot_aps_configuration(
- rcu_pcb_identifiers,
- rcu_pcb_numbers,
- rcu_pcb_versions,
- aps_pcb_ids,
- aps_pcb_nrs,
- aps_pcb_vers,
- filenames=[f"{PLOT_DIR}/aps_config.png", f"{PLOT_DIR}/aps_config.pdf"],
-):
- """
- Plot the configuration of the Antenna Processing Subrack, both sides.
- """
-
- fig, axs = plt.subplots(1, 2, figsize=(20, 8))
-
- plot_aps_rcu2_configuration(
- axs[0], rcu_pcb_identifiers, rcu_pcb_numbers, rcu_pcb_versions
- )
- plot_aps_unb2_configuration(axs[1], aps_pcb_ids, aps_pcb_nrs, aps_pcb_vers)
- for filename in filenames:
- fig.savefig(filename)
- fig.show()
-
-
-def plot_aps_rcu2_configuration(
- plot_ax,
- rcu_pcb_identifiers,
- rcu_pcb_numbers,
- rcu_pcb_versions,
- rcu_locations=APS_LOCATION_LABELS,
- rcu_pos_x=None,
- rcu_pos_y=None,
-):
- """
- Plot the configuration of the Antenna Processing Subrack, RCU2 side.
- """
- if rcu_pos_x is None:
- rcu_pos_x = APS_RCU_POS_X
- if rcu_pos_y is None:
- rcu_pos_y = APS_RCU_POS_Y
- plot_ax.plot(0, 0)
- plot_dx = plot_dy = 0.9
- plot_ax.set_xlim(-1 + plot_dx, np.max(rcu_pos_x) + 1)
- plot_ax.set_ylim(-1 + plot_dy, np.max(rcu_pos_y) + 1)
- for plot_x, plot_y, loc, pcb_id, pcb_nr, pcb_v in zip(
- rcu_pos_x,
- rcu_pos_y,
- rcu_locations,
- rcu_pcb_identifiers,
- rcu_pcb_numbers,
- rcu_pcb_versions,
- ):
- plot_ax.plot(
- [plot_x, plot_x + plot_dx, plot_x + plot_dx, plot_x, plot_x],
- [plot_y, plot_y, plot_y + plot_dy, plot_y + plot_dy, plot_y],
- color="black",
- alpha=0.2,
- )
- plot_ax.text(plot_x + plot_dx / 2, plot_y + plot_dy, loc, va="top", ha="center")
- plot_ax.text(
- plot_x + plot_dx / 2,
- plot_y + plot_dy / 2,
- "%s\n%s\n%s" % (pcb_v, pcb_id, pcb_nr),
- va="center",
- ha="center",
- rotation=90,
- )
- if pcb_id == 0 and pcb_v == "" and pcb_nr == "":
- plot_ax.plot(
- [plot_x, plot_x + plot_dx],
- [plot_y, plot_y + plot_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.plot(
- [plot_x + plot_dx, plot_x],
- [plot_y, plot_y + plot_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.set_title("Configuration of RCU2s")
- plot_ax.set_xticks([])
- plot_ax.set_yticks([])
-
-
-def plot_aps_unb2_configuration(
- plot_ax,
- aps_pcb_identifiers,
- aps_pcb_numbers,
- aps_pcb_versions,
- mod_locations=APS_LOCATION_LABELS,
- mod_pos_x=None,
- mod_pos_y=None,
-):
- """
- Plot the configuration of the Antenna Processing Subrack, UNB2 side.
- """
- if mod_pos_x is None:
- mod_pos_x = APS_MOD_POS_X
- if mod_pos_y is None:
- mod_pos_y = APS_MOD_POS_Y
- plot_ax.plot(0, 0)
- plot_dx = 0.9
- plot_dy = 3.9
- plot_ax.set_xlim(-1 + plot_dx, np.max(mod_pos_x) + 2)
- plot_ax.set_ylim(-1 + plot_dx, np.max(mod_pos_y) + 4)
- for plot_x, plot_y, loc, pcb_id, pcb_nr, pcb_v in zip(
- mod_pos_x,
- mod_pos_y,
- mod_locations,
- aps_pcb_identifiers,
- aps_pcb_numbers,
- aps_pcb_versions,
- ):
- plot_ax.plot(
- [plot_x, plot_x + plot_dx, plot_x + plot_dx, plot_x, plot_x],
- [plot_y, plot_y, plot_y + plot_dy, plot_y + plot_dy, plot_y],
- color="black",
- alpha=0.2,
- )
- plot_ax.text(plot_x + plot_dx / 2, plot_y + plot_dy, loc, va="top", ha="center")
- plot_ax.text(
- plot_x + plot_dx / 2,
- plot_y + plot_dy / 2,
- "%s\n%s\n%s" % (pcb_v, pcb_id, pcb_nr),
- va="center",
- ha="center",
- rotation=90,
- )
- if pcb_id == 0 and pcb_v == "" and pcb_nr == "":
- plot_ax.plot(
- [plot_x, plot_x + plot_dx],
- [plot_y, plot_y + plot_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.plot(
- [plot_x + plot_dx, plot_x],
- [plot_y, plot_y + plot_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.set_title("Configuration of UniBoard2 side")
- plot_ax.set_xticks([])
- plot_ax.set_yticks([])
-
-
-def plot_services_configuration(
- aps_services,
- station_services,
- filenames=[f"{PLOT_DIR}/station_services.png", f"{PLOT_DIR}/station_services.pdf"],
-):
- """
- Plot the service version information in the setup.
- """
-
- fig, plot_ax = plt.subplots(1, 1, figsize=(16, 15))
-
- aps_service_dx = 0.9
- aps_service_dy = 0.9
- first_aps_service = sorted(aps_services)[0][5:]
- plot_y = plot_y0 = len(aps_services) / 2 + 2
- for aps_service in sorted(aps_services):
- plot_x = 0 if (aps_service[3] in "01") else 1
- plot_y = (
- plot_y0
- if (aps_service[5:] == first_aps_service and aps_service[3] in "02")
- else plot_y - 1
- )
- plot_y = (
- plot_y - 1
- if (aps_service[5:] == first_aps_service and aps_service[3] in "13")
- else plot_y
- )
- plot_ax.plot(
- [plot_x, plot_x + aps_service_dx, plot_x + aps_service_dx, plot_x, plot_x],
- [plot_y, plot_y, plot_y + aps_service_dy, plot_y + aps_service_dy, plot_y],
- color="black",
- alpha=0.2,
- )
- plot_ax.text(
- plot_x,
- plot_y + aps_service_dy / 2,
- aps_service,
- va="center",
- ha="left",
- color="black",
- )
- plot_ax.text(
- plot_x + aps_service_dx,
- plot_y + aps_service_dy / 2,
- aps_services[aps_service],
- va="center",
- ha="right",
- color="blue",
- )
- if aps_services[aps_service] == "":
- plot_ax.plot(
- [plot_x, plot_x + aps_service_dx],
- [plot_y, plot_y + aps_service_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.plot(
- [plot_x + aps_service_dx, plot_x],
- [plot_y, plot_y + aps_service_dy],
- color="black",
- alpha=0.2,
- )
-
- stat_service_dx = 1.9
- stat_service_dy = 0.9
- plot_x = 0
- plot_y = 0
- for stat_service in sorted(station_services):
- plot_ax.plot(
- [
- plot_x,
- plot_x + stat_service_dx,
- plot_x + stat_service_dx,
- plot_x,
- plot_x,
- ],
- [
- plot_y,
- plot_y,
- plot_y + stat_service_dy,
- plot_y + stat_service_dy,
- plot_y,
- ],
- color="black",
- alpha=0.2,
- )
- plot_ax.text(
- plot_x,
- plot_y + stat_service_dy / 2,
- stat_service,
- va="center",
- ha="left",
- color="black",
- )
- plot_ax.text(
- plot_x + stat_service_dx,
- plot_y + stat_service_dy / 2,
- station_services[stat_service],
- va="center",
- ha="right",
- color="blue",
- )
- if station_services[stat_service] == "":
- plot_ax.plot(
- [plot_x, plot_x + stat_service_dx],
- [plot_y, plot_y + stat_service_dy],
- color="black",
- alpha=0.2,
- )
- plot_ax.plot(
- [plot_x + stat_service_dx, plot_x],
- [plot_y, plot_y + stat_service_dy],
- color="black",
- alpha=0.2,
- )
- plot_y -= 1
- plot_ax.set_title("Services in setup")
- plot_ax.set_xticks([])
- plot_ax.set_yticks([])
-
- for filename in filenames:
- fig.savefig(filename)
- fig.show()
-
-
-def plot_statistics(devices, save_fig=True, silent=False):
- """
- Plot the statistics: SST, XST and BST
- """
- if not silent:
- plib.log("Plot subband statistics")
- if not save_fig:
- figs = []
- else:
- figs = [
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_subband_statistics.png',
- f'{PLOT_DIR}/{plib.get_timestamp("filename")}_subband_statistics.pdf',
- ]
- plot_subband_statistics(devices, filenames=figs)
- # TODO:
- # if not silent:
- # plib.log("Plot crosslet statistics")
- # TODO:
- # if not silent:
- # plib.log("Plot beamlet statistics")
-
-
-def plot_subband_statistics(
- devices,
- xlim=None,
- ylim=[-105, -30],
- filenames=[
- f"{PLOT_DIR}/subband_statistics.png",
- f"{PLOT_DIR}/subband_statistics.pdf",
- ],
-):
- """
- Make the subband statistics plot
- """
- if xlim is None:
- xlim = [-5, 305]
- if ylim is None:
- ylim = [-105, -30]
- sst_data, band_names, frequency_axes = get_subband_statistics(devices)
-
- fig, axs = plt.subplots(1, 1, figsize=(18, 6))
- for signal_index in range(len(sst_data)):
- if band_names[signal_index] is None:
- continue
- freq = frequency_axes[band_names[signal_index]]
- # plot data in dB full scale
- plot_data = 10 * np.log10(sst_data[signal_index, :] + 1) - 128 - 6 * 4
- # since we're lacking antenna names,
- # show the RCU input info as label
- rcu_index, rcu_input_index = plib.signal_input_2_rcu_index_and_rcu_input_index(
- signal_index
- )
- label = "RCU%02d, Inp%d" % (rcu_index, rcu_input_index)
- plt.plot(freq / 1e6, plot_data, label=label)
- plt.grid()
- plt.legend(bbox_to_anchor=(1, 1), loc="upper left")
- plt.xlim(xlim)
- plt.ylim(ylim)
- plt.xlabel("Frequency (MHz)")
- plt.ylabel("Power (dB full scale)")
- plt.suptitle("Subband Statistics for all fully-connected receivers")
- plt.title(f"{STATION_NAME}; {plib.get_timestamp()}")
-
- for filename in filenames:
- fig.savefig(filename)
- fig.show()
-
-
-def get_subband_statistics(devices):
- """
- Get subband statistics data from the setup, including proper axes and labels
- """
- devices = devices_list_2_dict(devices)
- recv = devices["recv"]
- sst = devices["sst"]
-
- # get band names for the current configuration
- cur_band_names = plib.get_band_names(
- recv.RCU_band_select_R,
- # TODO - when correct LCNs are provided
- # in "RCU_PCB_number_R", uncomment the
- # following line:
- # recv.RCU_PCB_number_R,
- # and remove the following line:
- recv.RCU_PCB_ID_R,
- lb_tags=RCU2L_TAGS,
- hb_tags=RCU2H_TAGS,
- )
-
- # replace the aliases of LB1 and LB2 into LB: (same frequency axis)
- for alias in plib.LB_ALIASES:
- while alias in cur_band_names:
- cur_band_names[cur_band_names.index(alias)] = "LB"
- # get frequency axes
-
- # TODO - get frequency axes correct (indexing is incorrect/inconsequent)
- key1 = "HB2"
- key2 = "HB1"
- tmp_key = "magic_band"
- while key1 in cur_band_names:
- cur_band_names[cur_band_names.index(key1)] = tmp_key
- while key2 in cur_band_names:
- cur_band_names[cur_band_names.index(key1)] = key1
- while tmp_key in cur_band_names:
- cur_band_names[cur_band_names.index(tmp_key)] = key2
-
- frequency_axes = {}
- for band_name in plib.get_valid_band_names():
- if band_name in cur_band_names:
- frequency_axes[band_name] = plib.get_frequency_for_band_name(band_name)
- # get sst data
- sst_data = sst.sst_r
- # make sure header data is of correct size
- cur_band_names = cur_band_names + [None] * (len(sst_data) - len(cur_band_names))
- return sst_data, cur_band_names, frequency_axes
-
-
-def devices_list_2_dict(devices_list, device_keys=None):
- """
- Convert a list of devices to a dictionary with known keys for internal
- use.
-
- Devices are selected based on substring presence in the devices name
-
- Input arguments:
- devices_list = list of Tango devices
- device_keys = list of keys, should be a substring of the device names
-
- Output arguments:
- devices_dict = dict of devices with known keys
- """
- if device_keys is None:
- device_keys = [
- "boot",
- "unb2",
- "recv",
- "sdp",
- "sst",
- "bst",
- "xst",
- "digitalbeam",
- "tilebeam",
- "beamlet",
- "apsct",
- "apspu",
- ]
- devices_dict = {}
- if isinstance(devices_list, dict):
- # if by accident devices_list is already a dict,
- # then simply return the dict
- return devices_list
- for device in devices_list:
- for device_key in device_keys:
- if device_key in device.name().lower():
- devices_dict[device_key] = device
- return devices_dict
-
-
-def main():
- """Main"""
- return False
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/lofar_station_client/processing_lib.py b/lofar_station_client/processing_lib.py
deleted file mode 100644
index 285654b..0000000
--- a/lofar_station_client/processing_lib.py
+++ /dev/null
@@ -1,467 +0,0 @@
-# processing_lib.py: Module with generic functions for processing
-# LOFAR2 station data
-#
-# -*- coding: utf-8 -*-
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-
-""" module processing_lib
-
-This module contains generic functions for processing LOFAR2 station data.
-
-"""
-
-import sys
-
-# import h5py
-# from os import listdir
-# from os.path import isfile, join
-import datetime
-import numpy as np
-
-# from mpl_toolkits.mplot3d import axes3d
-# import matplotlib.pyplot as plt
-
-
-CST_N_SUB = 512 # number of subbands as output of subband generation in firmware
-CST_FS = 100e6 # sampling frequency in Hz
-BANDLIMS = {"LB": [0, 100e6], "HB1": [100e6, 200e6], "HB2": [200e6, 300e6]}
-LB_ALIASES = ["LB1", "LB2"]
-
-
-def signal_index_2_processing_node_index(signal_index):
- """
- Convert signal index to processing node index.
- Can be used to determine on which processing node the signal is processed.
- """
- return (signal_index % 192) // 12
-
-
-def signal_index_2_processing_node_input_index(signal_index):
- """
- Convert signal index to processing node input index.
- Can be used to determine wich input to the processing node is used for the signal.
- """
- return (signal_index % 192) % 12
-
-
-def signal_index_2_uniboard_index(signal_index):
- """
- Convert signal index to UniBoard index.
- Can be used to determine on which UniBoard the signal is processed.
- """
- return signal_index // 48
-
-
-def signal_index_2_rcu_index(signal_index):
- """
- Convert signal index to RCU index.
- Can be used to determine which RCU is used to provide the signal.
- """
- return (signal_index // 6) * 2 + signal_index % 2
-
-
-def signal_index_2_rcu_input_index(signal_index):
- """
- Convert signal index to RCU input index.
- Can be used to determine which input to the RCU is used for the signal.
- """
- return (signal_index // 2) % 3
-
-
-def signal_index_2_aps_index(signal_index):
- """
- Convert signal index to APS index.
- Can be used to determine in which Antenna Processing Subrack this signal is
- processed.
- """
- return signal_index // 96
-
-
-def signal_input_2_rcu_index_and_rcu_input_index(signal_index):
- """
- Given the gsi, get the RCU index and the RCU input index in one call.
- """
- return (
- signal_index_2_rcu_index(signal_index),
- signal_index_2_rcu_input_index(signal_index),
- )
-
-
-# the inverse:
-def rcu_index_2_signal_index(rcu_index):
- """
- Convert RCU index to signal indices.
- """
- return rcu_index_and_rcu_input_index_2_signal_index(rcu_index)
-
-
-def rcu_index_and_rcu_input_index_2_signal_index(
- rcu_index, rcu_input_index=np.array([0, 1, 2])
-):
- """
- Convert RCU index and RCU input index to signal index.
- """
- return rcu_index + rcu_input_index * 2 + (rcu_index // 2) * 4
-
-
-# backward compatible methods:
-def gsi_2_rcu_input_index(gsi):
- """Deprecated. Please use signal_index_2_rcu_input_index."""
- print("Warning! Do not use gsi_2_rcu_input_index().")
- print("Use signal_index_2_rcu_input_index() instead")
- return signal_index_2_rcu_input_index(gsi)
-
-
-def gsi_2_rcu_index(gsi):
- """Deprecated. Please use signal_index_2_rcu_index."""
- print("Warning! Do not use gsi_2_rcu_index().")
- print("Use signal_index_2_rcu_index() instead")
- return signal_index_2_rcu_index(gsi)
-
-
-def gsi_2_rcu_index_and_rcu_input_index(gsi):
- """Deprecated. Please use signal_input_2_rcu_index_and_rcu_input_index."""
- print("Warning! Do not use gsi_2_rcu_index_and_rcu_input_index().")
- print("Use signal_input_2_rcu_index_and_rcu_input_index() instead")
- return signal_input_2_rcu_index_and_rcu_input_index(gsi)
-
-
-def rcu_index_2_gsi(rcu_index):
- """Deprecated. Please use rcu_index_2_signal_index."""
- print("Warning! Do not use rcu_index_2_gsi().")
- print("Use rcu_index_2_signal_index() instead")
- return rcu_index_2_signal_index(rcu_index)
-
-
-def rcu_index_and_rcu_input_index_2_gsi(rcu_index, rcu_input_index=np.array([0, 1, 2])):
- """Deprecated. Please use rcu_index_and_rcu_input_index_2_signal_index."""
- print("Warning! Do not use rcu_index_and_rcu_input_index_2_gsi().")
- print("Use rcu_index_and_rcu_input_index_2_signal_index() instead")
- return rcu_index_and_rcu_input_index_2_signal_index(rcu_index, rcu_input_index)
-
-
-def test_rcu_index_2_signal_index():
- """
- Test code for rcu_index_2_signal_index
- """
- test_input = []
- test_output = []
- # test 1
- test_input.append(np.array([0]))
- test_output.append(np.array([0, 2, 4]))
- # test 2
- test_input.append(np.array([1]))
- test_output.append(np.array([1, 3, 5]))
- for input_rcu_index, expected_output in zip(test_input, test_output):
- actual_output = rcu_index_2_signal_index(input_rcu_index)
- if not np.all(expected_output == actual_output):
- print(expected_output)
- print(actual_output)
- raise Exception("Tests fails!")
-
-
-def test_rcu_index_and_rcu_input_index_2_signal_index():
- """
- Test code for rcu_index_and_rcu_input_index_2_signal_index
- """
- test_input = []
- test_output = []
- # test 1
- test_input.append([np.array([0]), np.array([0])])
- test_output.append(np.array([0]))
- # test 2
- test_input.append([np.array([0]), np.array([0, 1, 2])])
- test_output.append(np.array([0, 2, 4]))
- for inputs, expected_output in zip(test_input, test_output):
- input_rcu_index = inputs[0]
- input_rcu_input_index = inputs[1]
- actual_output = rcu_index_and_rcu_input_index_2_signal_index(
- input_rcu_index, input_rcu_input_index
- )
- if not np.all(expected_output == actual_output):
- print(expected_output)
- print(actual_output)
- raise Exception("Tests fails!")
-
-
-def test_signal_input_2_rcu_index_and_rcu_input_index():
- """
- Test code for signal_input_2_rcu_index_and_rcu_input_index
- """
- test_input = []
- test_output = []
- # test 1
- test_input.append(np.array([0]))
- test_output.append((np.array([0]), np.array([0])))
- # test 2
- test_input.append(np.array([1]))
- test_output.append((np.array([1]), np.array([0])))
- for input_signal_index, expected_output in zip(test_input, test_output):
- actual_output = signal_input_2_rcu_index_and_rcu_input_index(input_signal_index)
- print(actual_output)
- print(expected_output)
- if not np.all(expected_output == actual_output):
- print(expected_output)
- print(actual_output)
- raise Exception("Tests fails!")
-
-
-def get_timestamp(format_specifier=None):
- """
- Get the timestamp in standard format
-
- Input argument:
- format_specifier = format specifier.
- iso format (default)
- "filename": filename without spaces and special characters,
- format: yyyymmddThhmmss
-
- Output argument:
- timestamp = timestamp in correct format
- """
- timestamp = datetime.datetime.isoformat(datetime.datetime.now())
- if format_specifier == "filename":
- timestamp = datetime.datetime.now().strftime("%Y%m%dT%H%M%S")
- return timestamp
-
-
-def log(string):
- """
- Standard printing of a log line, including a timestamp.
- """
- print(f"{get_timestamp()} : {string}")
-
-
-def get_valid_band_names():
- """
- Return the valid band names.
- """
- band_names = []
- for band_name in BANDLIMS:
- band_names.append(band_name)
- return band_names
-
-
-def get_band_name_and_subband_index_for_frequency(freqs):
- """
- Get band name and subband index for one or more frequencies.
-
- Input arguments:
- freqs = one or more frequencies in Hz
-
- Output arguments:
- band_names = list of band names (see get_valid_band_names)
- sbi = one or more indices of the subbands for which the frequencies
- should be returned
- """
- # make sure freqs is a list
- if not isinstance(freqs, list):
- freqs = [freqs]
- # get band name and subband index
- freqs = np.array(freqs)
- band_names = []
- for freq in freqs:
- band_name = get_band_name_for_frequency(freq)
- band_names.append(band_name)
- # get indices for band_name "LB"
- sbis = np.round(freqs * CST_N_SUB / CST_FS)
- # and update for the other bands:
- for idx in np.where(np.array(band_names) == "HB1")[0]:
- sbis[idx] = 2 * CST_N_SUB - sbis[idx]
- for idx in np.where(np.array(band_names) == "HB2")[0]:
- sbis[idx] = sbis[idx] - 2 * CST_N_SUB
- return band_names, sbis
-
-
-def get_band_name_for_frequency(freq, bandlims=None):
- """
- Get band name for frequency
-
- Input arguments:
- freq = one frequency in Hz, float
- bandlims = dict with band names and their limits (default: LOFAR2 bands)
-
- Output arguments:
- band_name = name of analog band (see get_valid_band_names),
- empty string ("") if unsuccessful
- """
- if bandlims is None:
- bandlims = BANDLIMS
- for band_name in bandlims:
- if np.min(bandlims[band_name]) <= freq <= np.max(bandlims[band_name]):
- return band_name
- return ""
-
-
-def get_frequency_for_band_name(band_name):
- """
- Get all frequencies for given band name
-
- Input arguments:
- band_name = name of analog band (see get_valid_band_names)
-
- Output arguments:
- freqs = frequencies in Hz for the selected band
- """
- return get_frequency_for_band_name_and_subband_index(band_name, sbi=np.arange(512))
-
-
-def get_frequency_for_band_name_and_subband_index(band_name, sbi):
- """
- Get frequency for band name and subband(s)
-
- Input arguments:
- band_name = name of analog band (see get_valid_band_names)
- sbi = one or more indices of the subbands for which the frequencies
- should be returned
-
- Output arguments:
- freqs = frequencies in Hz for the selected band and subband
- """
- # check input
- valid_band_names = get_valid_band_names()
- if band_name not in valid_band_names and band_name not in LB_ALIASES:
- raise Exception(
- f"Unknown band_name '{band_name}'. Valid band_names are: {valid_band_names}"
- )
- # generate frequencies
- freqs = sbi * CST_FS / CST_N_SUB
- if band_name == "HB1":
- return 200e6 - freqs
- if band_name == "HB2":
- return 200e6 + freqs
- # else: # LB
- return freqs
-
-
-def get_band_names(rcu_band_select_r, rcu_pcb_version_r, lb_tags=None, hb_tags=None):
- """
- Get the frequency band names per receiver, based on their band selection
- and RCU PCB name.
-
- PCB name is used to determine the band (Low Band "LB" or High Band "HB",
- None otherwise). The band selection is added as a postfix.
- RCU PCB version can also be used, but then the default tags will not be sufficient
-
- Input Arguments:
- rcu_band_select_r = as returned by recv.RCU_band_select_R
- rcu_pcb_version_r = as returned by recv.RCU_PCB_version_R
- lb_tags = substrings in RCU_PCB_version_R fields to indicate Low Band
- Default: RCU2L
- hb_tags = substrings in RCU_PCB_version_R fields to indicate High Band
- Default: RCU2H
-
- Note:
- rcu_pcb_version_r can also hold IDs as returned by recv.RCU_PCB_ID_R
- In that case, the lb_tags and hb_tags should be passed on by the user
-
- Output Arguments:
- band_name = list of strings indicating the band name per receiver.
- containts None if no band name could be determined
-
- """
- # if IDs are passed on, convert to list of strings
- if isinstance(rcu_pcb_version_r, np.ndarray):
- rcu_pcb_version_r = [str(x) for x in rcu_pcb_version_r]
- #
- if lb_tags is None:
- lb_tags = ["RCU2L"]
- if hb_tags is None:
- hb_tags = ["RCU2H"]
-
- n_signal_indices = rcu_band_select_r.shape[0] * rcu_band_select_r.shape[1]
- band_name_per_signal_index = [None] * n_signal_indices
-
- for rcu_index in range(len(rcu_band_select_r)):
- for rcu_input_index in range(len(rcu_band_select_r[rcu_index])):
- signal_index = rcu_index_and_rcu_input_index_2_signal_index(
- rcu_index, rcu_input_index
- )
- this_band = None
- for tag in lb_tags:
- if tag in rcu_pcb_version_r[rcu_index]:
- this_band = f"LB{rcu_band_select_r[rcu_index][rcu_input_index]:1}"
- break
- if this_band is None: # continue with high band
- for tag in hb_tags:
- if tag in rcu_pcb_version_r[rcu_index]:
- this_band = (
- f"HB{rcu_band_select_r[rcu_index][rcu_input_index]:1}"
- )
- break
- band_name_per_signal_index[signal_index] = this_band
- return band_name_per_signal_index
-
-
-# some basic test methods, for internal use:
-def test_get_band_name_and_subband_index_for_frequency():
- """
- Test code for get_band_name_and_subband_index_for_frequency
- """
- input_frequencies = [138.1e6, 137.9e6, 0.1, 0, 195312.5, 234e6]
- expected_band_names = ["HB1", "HB1", "LB", "LB", "LB", "HB2"]
- expected_subband_index = np.array([317.0, 318.0, 0.0, 0.0, 1.0, 174.0])
- (
- actual_band_names,
- actual_subband_index,
- ) = get_band_name_and_subband_index_for_frequency(input_frequencies)
- if expected_band_names != actual_band_names:
- print(expected_band_names)
- print(actual_band_names)
- raise Exception("Tests fails!")
- if not (expected_subband_index == actual_subband_index).all():
- print(expected_subband_index)
- print(actual_subband_index)
- raise Exception("Tests fails!")
-
-
-def test_get_frequency_for_band_name_and_subband_index():
- """
- Test code for get_frequency_for_band_name_and_subband_index
- """
- for input_band_name, input_subband_index, expected_frequencies in zip(
- ["HB1", "HB1", "LB", "LB", "LB", "HB2", "LB1"],
- [195.0, 194.0, 0.0, 0.0, 1.0, 174.0, 1.0],
- [161914062.5, 162109375.0, 0.0, 0.0, 195312.5, 233984375, 195312.5],
- ):
- actual_frequencies = get_frequency_for_band_name_and_subband_index(
- input_band_name, input_subband_index
- )
- if not expected_frequencies == actual_frequencies:
- print(expected_frequencies)
- print(actual_frequencies)
- raise Exception("Tests fails!")
-
-
-def test():
- """
- Method to call the defined test methods.
- """
- test_rcu_index_2_signal_index()
- test_rcu_index_and_rcu_input_index_2_signal_index()
- test_signal_input_2_rcu_index_and_rcu_input_index()
- test_get_band_name_and_subband_index_for_frequency()
- test_get_frequency_for_band_name_and_subband_index()
-
-
-def main():
- """
- Main function
- """
- return False
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/requirements.txt b/requirements.txt
index 2c7f926..5a3e704 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
-# TODO(Corne): Uncomment below when L2SS-832 is fixed
-# tangostationcontrol@git+https://git.astron.nl/lofar2.0/tango.git#egg=tangostationcontrol&subdirectory=tangostationcontrol
-
+# Order does matter
requests>=2.0 # Apache 2
-numpy>=1.21.0 # BSD
\ No newline at end of file
+numpy>=1.21.0 # BSD
+matplotlib>=3.5.0 # PSF
+pyDeprecate>=0.3.0 # MIT
diff --git a/tests/dts/__init__.py b/tests/dts/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/dts/test_bands.py b/tests/dts/test_bands.py
new file mode 100644
index 0000000..6e1635c
--- /dev/null
+++ b/tests/dts/test_bands.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import numpy as np
+
+from lofar_station_client.dts import bands
+
+from tests import base
+
+
+class DTSBandsTest(base.TestCase):
+ def test_get_band_name_and_subband_index_for_frequency(self):
+ """Test code for get_band_name_and_subband_index_for_frequency"""
+
+ input_frequencies = [138.1e6, 137.9e6, 0.1, 0, 195312.5, 234e6]
+ expected_band_names = ["HB1", "HB1", "LB", "LB", "LB", "HB2"]
+ expected_subband_index = np.array([317.0, 318.0, 0.0, 0.0, 1.0, 174.0])
+ (
+ actual_band_names,
+ actual_subband_index,
+ ) = bands.get_band_name_and_subband_index_for_frequency(input_frequencies)
+
+ self.assertEqual(expected_band_names, actual_band_names)
+ np.testing.assert_array_equal(expected_subband_index, actual_subband_index)
+
+ def test_get_frequency_for_band_name_and_subband_index(self):
+ """Test code for get_frequency_for_band_name_and_subband_index"""
+
+ for input_band_name, input_subband_index, expected_frequencies in zip(
+ ["HB1", "HB1", "LB", "LB", "LB", "HB2", "LB1"],
+ [195.0, 194.0, 0.0, 0.0, 1.0, 174.0, 1.0],
+ [161914062.5, 162109375.0, 0.0, 0.0, 195312.5, 233984375, 195312.5],
+ ):
+ actual_frequencies = bands.get_frequency_for_band_name_and_subband_index(
+ input_band_name, input_subband_index
+ )
+
+ self.assertEqual(expected_frequencies, actual_frequencies)
diff --git a/tests/dts/test_index.py b/tests/dts/test_index.py
new file mode 100644
index 0000000..3c07caf
--- /dev/null
+++ b/tests/dts/test_index.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import numpy as np
+
+from lofar_station_client.dts import index
+
+from tests import base
+
+
+class DTSIndexTest(base.TestCase):
+ @staticmethod
+ def test_rcu_index_2_signal_index():
+ """Test code for rcu_index_2_signal_index"""
+
+ test_input = []
+ test_output = []
+
+ # test 1
+ test_input.append(np.array([0]))
+ test_output.append(np.array([0, 2, 4]))
+
+ # test 2
+ test_input.append(np.array([1]))
+ test_output.append(np.array([1, 3, 5]))
+
+ for input_rcu_index, expected_output in zip(test_input, test_output):
+ actual_output = index.rcu_index_2_signal_index(input_rcu_index)
+ np.testing.assert_array_equal(expected_output, actual_output)
+
+ @staticmethod
+ def test_rcu_index_and_rcu_input_index_2_signal_index():
+ """Test code for rcu_index_and_rcu_input_index_2_signal_index"""
+
+ test_input = []
+ test_output = []
+
+ # test 1
+ test_input.append([np.array([0]), np.array([0])])
+ test_output.append(np.array([0]))
+
+ # test 2
+ test_input.append([np.array([0]), np.array([0, 1, 2])])
+ test_output.append(np.array([0, 2, 4]))
+
+ for inputs, expected_output in zip(test_input, test_output):
+ input_rcu_index = inputs[0]
+ input_rcu_input_index = inputs[1]
+ actual_output = index.rcu_index_and_rcu_input_index_2_signal_index(
+ input_rcu_index, input_rcu_input_index
+ )
+ np.testing.assert_array_equal(expected_output, actual_output)
+
+ @staticmethod
+ def test_signal_input_2_rcu_index_and_rcu_input_index():
+ """Test code for signal_input_2_rcu_index_and_rcu_input_index"""
+
+ test_input = []
+ test_output = []
+
+ # test 1
+ test_input.append(np.array([0]))
+ test_output.append((np.array([0]), np.array([0])))
+
+ # test 2
+ test_input.append(np.array([1]))
+ test_output.append((np.array([1]), np.array([0])))
+
+ for input_signal_index, expected_output in zip(test_input, test_output):
+ actual_output = index.signal_input_2_rcu_index_and_rcu_input_index(
+ input_signal_index
+ )
+ np.testing.assert_array_equal(expected_output, actual_output)
--
GitLab