From e37c99cf7b21ab7a5a6f80af005e580f5e3b015b Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 14:44:46 +0200
Subject: [PATCH 01/11] L2SS-1470: Expose settings for both polarisations in
 RCU_band_select_R(W)

---
 .../tangostationcontrol/devices/antennafield.py | 10 ++++++----
 .../devices/base_device_classes/mapper.py       | 17 ++++++++++++++---
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index b83f34e6b..546e4315c 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -510,13 +510,15 @@ class AntennaField(LOFARDevice):
     )
     RCU_band_select_R = MappedAttribute(
         "RCU_band_select_R",
-        dtype=(numpy.int64,),
-        max_dim_x=MAX_ANTENNA,
+        dtype=((numpy.int64,),),
+        max_dim_x=N_pol,
+        max_dim_y=MAX_ANTENNA,
     )
     RCU_band_select_RW = MappedAttribute(
         "RCU_band_select_RW",
-        dtype=(numpy.int64,),
-        max_dim_x=MAX_ANTENNA,
+        dtype=((numpy.int64,),),
+        max_dim_x=N_pol,
+        max_dim_y=MAX_ANTENNA,
         access=AttrWriteType.READ_WRITE,
     )
     RCU_attenuator_dB_R = MappedAttribute(
diff --git a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
index efddb1bd2..064bba36a 100644
--- a/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
+++ b/tangostationcontrol/tangostationcontrol/devices/base_device_classes/mapper.py
@@ -328,8 +328,12 @@ class AntennaToRecvMapper(AntennaMapper):
             "HBAT_PWR_on_RW": value_map_ant_32_bool,
             "RCU_PWR_ANT_on_R": value_map_ant_bool,
             "RCU_PWR_ANT_on_RW": value_map_ant_bool,
-            "RCU_band_select_R": numpy.zeros(number_of_antennas, dtype=numpy.int64),
-            "RCU_band_select_RW": numpy.zeros(number_of_antennas, dtype=numpy.int64),
+            "RCU_band_select_R": numpy.zeros(
+                (number_of_antennas, N_pol), dtype=numpy.int64
+            ),
+            "RCU_band_select_RW": numpy.zeros(
+                (number_of_antennas, N_pol), dtype=numpy.int64
+            ),
             "RCU_attenuator_dB_R": numpy.zeros(
                 (number_of_antennas, N_pol), dtype=numpy.int64
             ),
@@ -354,6 +358,8 @@ class AntennaToRecvMapper(AntennaMapper):
         self._reshape_attributes_in = mapped_dimensions
         self._reshape_attributes_in["RCU_attenuator_dB_R"] = ((MAX_ANTENNA * N_pol),)
         self._reshape_attributes_in["RCU_attenuator_dB_RW"] = ((MAX_ANTENNA * N_pol),)
+        self._reshape_attributes_in["RCU_band_select_R"] = ((MAX_ANTENNA * N_pol),)
+        self._reshape_attributes_in["RCU_band_select_RW"] = ((MAX_ANTENNA * N_pol),)
         self._reshape_attributes_in["RCU_PCB_ID_R"] = (MAX_ANTENNA,)
         self._reshape_attributes_in["RCU_PCB_version_R"] = (MAX_ANTENNA,)
 
@@ -366,6 +372,8 @@ class AntennaToRecvMapper(AntennaMapper):
         # Attributes which need to be reshaped with a copy of their values,
         # because RECV original dimension < AntennaField mapped dimension
         self._fill_attributes_in = {
+            "RCU_band_select_R": N_pol,
+            "RCU_band_select_RW": N_pol,
             "RCU_attenuator_dB_R": N_pol,
             "RCU_attenuator_dB_RW": N_pol,
             "RCU_PCB_ID_R": N_rcu_inp,
@@ -375,7 +383,8 @@ class AntennaToRecvMapper(AntennaMapper):
         # Attributes which need to be reshaped with removing a part of their duplicated
         # values because RECV original dimension < Antennafield mapped dimension
         self._empty_attributes_out = {
-            "RCU_attenuator_dB_RW": (N_rcu * N_rcu_inp, N_pol)
+            "RCU_band_select_RW": (N_rcu * N_rcu_inp, N_pol),
+            "RCU_attenuator_dB_RW": (N_rcu * N_rcu_inp, N_pol),
         }
 
     def _init_masked_value_write(self, mapped_dimensions: dict):
@@ -398,6 +407,8 @@ class AntennaToRecvMapper(AntennaMapper):
         double_mapping = [
             "RCU_attenuator_dB_R",
             "RCU_attenuator_dB_RW",
+            "RCU_band_select_R",
+            "RCU_band_select_RW",
             "RCU_PCB_ID_R",
             "RCU_PCB_version_R",
         ]
-- 
GitLab


From b8fbe893779fa32f9e6b924ddb5a057320bae5c4 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 15:10:31 +0200
Subject: [PATCH 02/11] L2SS-1470: Handle different RCU bands per polarisation

---
 .../tangostationcontrol/common/calibration.py | 13 +++-------
 .../devices/antennafield.py                   | 25 +++++++++++++------
 2 files changed, 21 insertions(+), 17 deletions(-)

diff --git a/tangostationcontrol/tangostationcontrol/common/calibration.py b/tangostationcontrol/tangostationcontrol/common/calibration.py
index 1c98b7dbc..c0147b3a8 100644
--- a/tangostationcontrol/tangostationcontrol/common/calibration.py
+++ b/tangostationcontrol/tangostationcontrol/common/calibration.py
@@ -256,7 +256,7 @@ def calibrate_RCU_attenuator_dB(antenna_field: DeviceProxy):
     # -----------------------------------------------------------
 
     # Correct for signal loss in the cables
-    signal_delay_loss = antenna_field.Antenna_Cables_Loss_R
+    signal_delay_loss = antenna_field.Antenna_Cables_Loss_R.flatten()
 
     # return coarse attenuation to apply (weakest signal
     # gets 0 attenuation).
@@ -266,14 +266,9 @@ def calibrate_RCU_attenuator_dB(antenna_field: DeviceProxy):
     rcu_attenuator_db += antenna_field.Field_Attenuation_R
 
     # apply on antenna field
-    antenna_field.RCU_attenuator_dB_RW = (
-        numpy.tile(
-            numpy.array(rcu_attenuator_db, dtype=numpy.int64).flatten(),
-            N_pol,
-        )
-        .reshape(N_pol, antenna_field.nr_antennas_R)
-        .T
-    )
+    antenna_field.RCU_attenuator_dB_RW = rcu_attenuator_db.reshape(
+        antenna_field.nr_antennas_R, N_pol
+    ).astype(numpy.int64)
 
 
 def loss_compensation(losses_dB: numpy.ndarray):
diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index 546e4315c..c040dfdf2 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -393,9 +393,10 @@ class AntennaField(LOFARDevice):
         unit="s",
     )
     Antenna_Cables_Loss_R = attribute(
-        doc="Loss caused by the cable between antenna and RCU, in dB.",
+        doc="Loss caused by the cable between antenna and RCU, in dB, for either polarisation.",
         dtype=(numpy.float64,),
-        max_dim_x=MAX_ANTENNA,
+        max_dim_x=N_pol,
+        max_dim_y=MAX_ANTENNA,
         unit="dB",
     )
 
@@ -701,8 +702,8 @@ class AntennaField(LOFARDevice):
     def read_Frequency_Band_RW(self):
         antenna_type = self.Antenna_Type
 
-        # fetch settings from RECV
-        rcu_band_select = self.read_attribute("RCU_band_select_RW")
+        # fetch settings from RECV, use X polarisation for reference
+        rcu_band_select = self.read_attribute("RCU_band_select_RW")[:, 0]
 
         # fetch settings from SDP
         clock = self.sdpfirmware_proxy.clock_RW
@@ -742,11 +743,16 @@ class AntennaField(LOFARDevice):
                         These do not: {val} and {value[0, 0]}."
                 )
 
-        # apply settings on RECV
-        self.proxy.RCU_band_select_RW = numpy.vectorize(
+        # convert into settings for RECV
+        RCU_band_select_per_polarisation = numpy.vectorize(
             lambda band: bands[band].rcu_band
         )(value)
 
+        # apply same setting for both polarisations on RECV
+        self.proxy.RCU_band_select_RW = numpy.dstack(
+            (RCU_band_select_per_polarisation, RCU_band_select_per_polarisation)
+        )[0]
+
         # apply settings on SDP
         self.sdpfirmware_proxy.clock_RW = bands[value[0]].clock
 
@@ -792,9 +798,12 @@ class AntennaField(LOFARDevice):
         # Return 0 loss for them instead.
         return numpy.array(
             [
-                cable_types[cable].get_loss(self.Antenna_Type, rcu_band)
+                [
+                    cable_types[cable].get_loss(self.Antenna_Type, rcu_band[0]),
+                    cable_types[cable].get_loss(self.Antenna_Type, rcu_band[1]),
+                ]
                 if recv > 0
-                else 0
+                else [0, 0]
                 for recv, cable, rcu_band in zip(recvs, self.Antenna_Cables, rcu_bands)
             ]
         )
-- 
GitLab


From 8c3a022411e5e8d27e7b94fac505638ae3fd1c4a Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 15:14:05 +0200
Subject: [PATCH 03/11] L2SS-1470: Support different RCU bands per polarisation

---
 .../tangostationcontrol/devices/antennafield.py            | 7 ++++---
 .../tangostationcontrol/devices/observation.py             | 2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index c040dfdf2..3bfddc9fd 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -371,9 +371,10 @@ class AntennaField(LOFARDevice):
         return numpy.array(self.RECV_Devices)
 
     Frequency_Band_RW = attribute(
-        doc="The selected frequency band of each antenna.",
+        doc="The selected frequency band of each polarisation of each antenna.",
         dtype=(str,),
-        max_dim_x=MAX_ANTENNA,
+        max_dim_x=N_pol,
+        max_dim_y=MAX_ANTENNA,
         access=AttrWriteType.READ_WRITE,
     )
 
@@ -703,7 +704,7 @@ class AntennaField(LOFARDevice):
         antenna_type = self.Antenna_Type
 
         # fetch settings from RECV, use X polarisation for reference
-        rcu_band_select = self.read_attribute("RCU_band_select_RW")[:, 0]
+        rcu_band_select = self.read_attribute("RCU_band_select_RW")
 
         # fetch settings from SDP
         clock = self.sdpfirmware_proxy.clock_RW
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
index 1af565875..2bb92b80d 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -315,7 +315,7 @@ class Observation(LOFARDevice):
 
         # convert numpy.array(dtype=str) to list[str] to avoid crash in DeviceProxy
         # see https://gitlab.com/tango-controls/pytango/-/issues/492
-        return (numpy.array([filter_name] * MAX_ANTENNA).tolist(),)
+        return (numpy.array([[filter_name, filter_name]] * MAX_ANTENNA).tolist(),)
 
     def _apply_saps_subbands(self, sap_subbands: list):
         """Convert an array of subbands into the correct format for Beamlet device"""
-- 
GitLab


From 4beb753d0666c0ad871f27932afc16ef74b8e348 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 15:15:37 +0200
Subject: [PATCH 04/11] L2SS-1470: Only upload settings for the correct number
 of antennas

---
 .../tangostationcontrol/devices/observation.py               | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
index 2bb92b80d..341302d50 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -16,6 +16,7 @@ from tangostationcontrol.common.constants import (
     MAX_ANTENNA,
     N_beamlets_ctrl,
     N_point_prop,
+    N_pol,
 )
 from tangostationcontrol.common.entrypoint import entry
 from tangostationcontrol.common.lofar_logging import device_logging_to_python
@@ -313,9 +314,11 @@ class Observation(LOFARDevice):
         AntennaField device
         """
 
+        nr_antennas = self.antennafield_proxy.nr_antennas_R
+
         # convert numpy.array(dtype=str) to list[str] to avoid crash in DeviceProxy
         # see https://gitlab.com/tango-controls/pytango/-/issues/492
-        return (numpy.array([[filter_name, filter_name]] * MAX_ANTENNA).tolist(),)
+        return (numpy.array([[filter_name] * N_pol] * nr_antennas).tolist(),)
 
     def _apply_saps_subbands(self, sap_subbands: list):
         """Convert an array of subbands into the correct format for Beamlet device"""
-- 
GitLab


From a0e5fa213ed1dcd7e87e8c5f47c2a63c7296afd0 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 16:48:02 +0200
Subject: [PATCH 05/11] L2SS-1470: Fixed tests

---
 .../test/devices/test_antennafield_device.py               | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/test/devices/test_antennafield_device.py
index da91e7d26..840b47c6b 100644
--- a/tangostationcontrol/test/devices/test_antennafield_device.py
+++ b/tangostationcontrol/test/devices/test_antennafield_device.py
@@ -208,7 +208,7 @@ class TestAntennaToRecvMapper(base.TestCase):
         }
         mapper = AntennaToRecvMapper(recv_mapping, RECV_MAPPED_ATTRS, 3)
         receiver_values = [[0] * MAX_ANTENNA, [0] * MAX_ANTENNA, [0] * MAX_ANTENNA]
-        expected = [0] * DEFAULT_N_HBA_TILES
+        expected = [[0, 0]] * DEFAULT_N_HBA_TILES
         actual = mapper.map_read("RCU_band_select_RW", receiver_values)
         numpy.testing.assert_equal(expected, actual)
 
@@ -712,7 +712,10 @@ class TestAntennaToRecvMapper(base.TestCase):
         mapper = AntennaToRecvMapper(recv_mapping, RECV_MAPPED_ATTRS, 1)
 
         set_values = [1, 0] + [None] * (DEFAULT_N_HBA_TILES - 2)
-        expected = [[[0, 1, None]] + [[None, None, None]] * (N_rcu - 1)]
+        expected = [
+            [[[None, 0], [None, 1], [None, None]]]
+            + [[[None, None], [None, None], [None, None]]] * (N_rcu - 1)
+        ]
         actual = mapper.map_write("RCU_band_select_RW", set_values)
         numpy.testing.assert_equal(expected, actual)
 
-- 
GitLab


From e384dc62258103553dd18ef58d6c9fd63de14d4b Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 16:49:45 +0200
Subject: [PATCH 06/11] Bump version

---
 README.md                                                    | 1 +
 tangostationcontrol/VERSION                                  | 2 +-
 tangostationcontrol/test/devices/test_antennafield_device.py | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 5cfefc5ca..bf330a2d0 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,7 @@ Next change the version in the following places:
 
 # Release Notes
 
+* 0.20.5 Manage both polarisations in RCU_band_select_R(W), Antenna_Loss_R, and Frequency_Band_RW
 * 0.20.4 Collapse AbstractHierarchyDevice and AbstractHierarchy into one class
 * 0.20.3 Fix application of Field_Attenuation_R
 * 0.20.2 Support only one parent in hierarchies
diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION
index 6dd46024a..1b619f348 100644
--- a/tangostationcontrol/VERSION
+++ b/tangostationcontrol/VERSION
@@ -1 +1 @@
-0.20.4
+0.20.5
diff --git a/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/test/devices/test_antennafield_device.py
index 840b47c6b..ea72b4091 100644
--- a/tangostationcontrol/test/devices/test_antennafield_device.py
+++ b/tangostationcontrol/test/devices/test_antennafield_device.py
@@ -711,7 +711,7 @@ class TestAntennaToRecvMapper(base.TestCase):
         }
         mapper = AntennaToRecvMapper(recv_mapping, RECV_MAPPED_ATTRS, 1)
 
-        set_values = [1, 0] + [None] * (DEFAULT_N_HBA_TILES - 2)
+        set_values = [[1, 1], [0, 0]] + [[None, None]] * (DEFAULT_N_HBA_TILES - 2)
         expected = [
             [[[None, 0], [None, 1], [None, None]]]
             + [[[None, None], [None, None], [None, None]]] * (N_rcu - 1)
-- 
GitLab


From 93070f38b5095de21159b7c3aff551bc0e89059c Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 17:07:32 +0200
Subject: [PATCH 07/11] L2SS-1470: Removed unnecessary flatten+reshape

---
 .../tangostationcontrol/common/calibration.py             | 8 +++-----
 .../test/devices/test_antennafield_device.py              | 5 +----
 2 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/tangostationcontrol/tangostationcontrol/common/calibration.py b/tangostationcontrol/tangostationcontrol/common/calibration.py
index c0147b3a8..2bf56dd30 100644
--- a/tangostationcontrol/tangostationcontrol/common/calibration.py
+++ b/tangostationcontrol/tangostationcontrol/common/calibration.py
@@ -256,7 +256,7 @@ def calibrate_RCU_attenuator_dB(antenna_field: DeviceProxy):
     # -----------------------------------------------------------
 
     # Correct for signal loss in the cables
-    signal_delay_loss = antenna_field.Antenna_Cables_Loss_R.flatten()
+    signal_delay_loss = antenna_field.Antenna_Cables_Loss_R
 
     # return coarse attenuation to apply (weakest signal
     # gets 0 attenuation).
@@ -266,9 +266,7 @@ def calibrate_RCU_attenuator_dB(antenna_field: DeviceProxy):
     rcu_attenuator_db += antenna_field.Field_Attenuation_R
 
     # apply on antenna field
-    antenna_field.RCU_attenuator_dB_RW = rcu_attenuator_db.reshape(
-        antenna_field.nr_antennas_R, N_pol
-    ).astype(numpy.int64)
+    antenna_field.RCU_attenuator_dB_RW = rcu_attenuator_db.astype(numpy.int64)
 
 
 def loss_compensation(losses_dB: numpy.ndarray):
@@ -295,7 +293,7 @@ def loss_compensation(losses_dB: numpy.ndarray):
 
     # correct for the coarse loss by dampening the signals to line up.
     input_attenuation_integer_dB = (
-        max(signal_attenuation_integer_dB) - signal_attenuation_integer_dB
+        numpy.max(signal_attenuation_integer_dB) - signal_attenuation_integer_dB
     )
 
     # compute the remainder, as a scaling factor
diff --git a/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/test/devices/test_antennafield_device.py
index ea72b4091..12edfafef 100644
--- a/tangostationcontrol/test/devices/test_antennafield_device.py
+++ b/tangostationcontrol/test/devices/test_antennafield_device.py
@@ -712,10 +712,7 @@ class TestAntennaToRecvMapper(base.TestCase):
         mapper = AntennaToRecvMapper(recv_mapping, RECV_MAPPED_ATTRS, 1)
 
         set_values = [[1, 1], [0, 0]] + [[None, None]] * (DEFAULT_N_HBA_TILES - 2)
-        expected = [
-            [[[None, 0], [None, 1], [None, None]]]
-            + [[[None, None], [None, None], [None, None]]] * (N_rcu - 1)
-        ]
+        expected = [[[0, 1, None]] + [[None, None, None]] * (N_rcu - 1)]
         actual = mapper.map_write("RCU_band_select_RW", set_values)
         numpy.testing.assert_equal(expected, actual)
 
-- 
GitLab


From aebce44f9407b96d48a0f754ab49fe0c2af45067 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 18:33:10 +0200
Subject: [PATCH 08/11] Try to fix setting the frequency bands

---
 .../tangostationcontrol/devices/observation.py               | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/tangostationcontrol/tangostationcontrol/devices/observation.py b/tangostationcontrol/tangostationcontrol/devices/observation.py
index 341302d50..af767e7cc 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation.py
@@ -315,10 +315,7 @@ class Observation(LOFARDevice):
         """
 
         nr_antennas = self.antennafield_proxy.nr_antennas_R
-
-        # convert numpy.array(dtype=str) to list[str] to avoid crash in DeviceProxy
-        # see https://gitlab.com/tango-controls/pytango/-/issues/492
-        return (numpy.array([[filter_name] * N_pol] * nr_antennas).tolist(),)
+        return (numpy.array([[filter_name] * N_pol] * nr_antennas),)
 
     def _apply_saps_subbands(self, sap_subbands: list):
         """Convert an array of subbands into the correct format for Beamlet device"""
-- 
GitLab


From cd68ea600a1cac1bee79b469ad99521f940382d8 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Fri, 4 Aug 2023 19:37:52 +0200
Subject: [PATCH 09/11] Fix dimensions

---
 .../devices/test_device_antennafield.py       | 13 ++++++++-----
 .../devices/antennafield.py                   | 19 +++++++------------
 2 files changed, 15 insertions(+), 17 deletions(-)

diff --git a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
index 6411150bb..43f75791b 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
@@ -317,7 +317,7 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
 
         try:
             antennafield_proxy.write_attribute(
-                "RCU_band_select_RW", [True] * MAX_ANTENNA
+                "RCU_band_select_RW", [[True, True]] * MAX_ANTENNA
             )
             numpy.testing.assert_equal(
                 numpy.array([[True] * N_rcu_inp] * N_rcu),
@@ -339,8 +339,11 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         for antenna_type in ["HBA"]:
             properties = {
                 "Antenna_Type": [antenna_type],
-                "Control_to_RECV_mapping": [1, 1, 1, 0] + [-1, -1] * (CS001_TILES - 2),
-                "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + [-1, -1] * (CS001_TILES - 2),
+                "Control_to_RECV_mapping": [1, 1]
+                + [-1, -1] * (CS001_TILES - 1),
+                "Power_to_RECV_mapping": [1, 0] + [-1, -1] * (CS001_TILES - 1),
+                "Antenna_to_SDP_Mapping": [0, 1, 0, 0]
+                + [-1, -1] * (CS001_TILES - 2),
             }
 
             antennafield_proxy = self.proxy
@@ -356,7 +359,7 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
                 self.sdp_proxy.write_attribute("nyquist_zone_RW", [[0] * S_pn] * N_pn)
 
                 antennafield_proxy.write_attribute(
-                    "Frequency_Band_RW", [band.name] * CS001_TILES
+                    "Frequency_Band_RW", [[band.name, band.name]] * CS001_TILES
                 )
                 # Wait for Tango attributes updating
                 time.sleep(1)
@@ -390,6 +393,6 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
                 # test whether reading back results in the same band for our inputs
                 numpy.testing.assert_equal(
                     numpy.array([band.name, band.name]),
-                    antennafield_proxy.read_attribute("Frequency_Band_RW").value[:2],
+                    antennafield_proxy.read_attribute("Frequency_Band_RW").value[0],
                     err_msg=f"{band.name}",
                 )
diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index 3bfddc9fd..8d7396811 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -372,7 +372,7 @@ class AntennaField(LOFARDevice):
 
     Frequency_Band_RW = attribute(
         doc="The selected frequency band of each polarisation of each antenna.",
-        dtype=(str,),
+        dtype=((str,),),
         max_dim_x=N_pol,
         max_dim_y=MAX_ANTENNA,
         access=AttrWriteType.READ_WRITE,
@@ -737,25 +737,20 @@ class AntennaField(LOFARDevice):
                 )
 
             if (
-                bands[val].clock != bands[value[0]].clock
-            ):  # NB: "value[0] in bands" holds at this point
+                bands[val].clock != bands[value[0, 0]].clock
+            ):  # NB: "value[0,0] in bands" holds at this point
                 raise ValueError(
                     f"All frequency bands must use the same clock. \
                         These do not: {val} and {value[0, 0]}."
                 )
 
         # convert into settings for RECV
-        RCU_band_select_per_polarisation = numpy.vectorize(
+        self.proxy.RCU_band_select_RW = numpy.vectorize(
             lambda band: bands[band].rcu_band
         )(value)
 
-        # apply same setting for both polarisations on RECV
-        self.proxy.RCU_band_select_RW = numpy.dstack(
-            (RCU_band_select_per_polarisation, RCU_band_select_per_polarisation)
-        )[0]
-
         # apply settings on SDP
-        self.sdpfirmware_proxy.clock_RW = bands[value[0]].clock
+        self.sdpfirmware_proxy.clock_RW = bands[value[0, 0]].clock
 
         # read-modify-write on [fpga][(input, polarisation)]
         sdp_nyquist_zone = numpy.full((N_pn, A_pn * N_pol), None)
@@ -768,11 +763,11 @@ class AntennaField(LOFARDevice):
 
             # set for x polarisation
             sdp_nyquist_zone[fpga_nr, input_nr * 2 + 0] = bands[
-                value[antenna_nr]
+                value[antenna_nr, 0]
             ].nyquist_zone
             # set for y polarisation
             sdp_nyquist_zone[fpga_nr, input_nr * 2 + 1] = bands[
-                value[antenna_nr]
+                value[antenna_nr, 1]
             ].nyquist_zone
 
         self.atomic_read_modify_write_attribute(
-- 
GitLab


From d364d3bff7d377d103a3e4bda918a48183a4f689 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Sun, 6 Aug 2023 14:33:17 +0200
Subject: [PATCH 10/11] Fixes

---
 .../devices/test_device_antennafield.py       | 32 ++++++++++-------
 .../devices/test_device_observation.py        | 11 +++---
 .../tangostationcontrol/common/calibration.py | 35 +++++++++++--------
 .../devices/antennafield.py                   |  2 +-
 .../test/common/test_calibration.py           |  2 +-
 5 files changed, 49 insertions(+), 33 deletions(-)

diff --git a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
index 43f75791b..bc4f4dada 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
@@ -29,7 +29,12 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         super().setUp("STAT/AntennaField/HBA0")
         self.proxy.put_property(
             {
-                "Power_to_RECV_mapping": [1, 1, 1, 0] + [-1] * ((CS001_TILES * 2) - 4),
+                "Power_to_RECV_mapping": numpy.array(
+                    [[1, x * 2 + 0] for x in range(MAX_ANTENNA // 2)]
+                ).flatten(),
+                "Control_to_RECV_mapping": numpy.array(
+                    [[1, x * 2 + 1] for x in range(MAX_ANTENNA // 2)]
+                ).flatten(),
             }
         )
         self.recv_proxy = self.setup_recv_proxy()
@@ -300,10 +305,12 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         """Verify small RECV device attribute changed all inputs mapped"""
 
         mapping_properties = {
-            "Power_to_RECV_mapping": [-1, -1] * CS001_TILES,
-            "Control_to_RECV_mapping":
-            # [1, 0, 1, 1, 1, 2, 1, x ... 1, 95]
-            numpy.array([[1, x] for x in range(0, MAX_ANTENNA)]).flatten(),
+            "Power_to_RECV_mapping": numpy.array(  # X maps on power
+                [[1, x * 2 + 1] for x in range(MAX_ANTENNA // 2)]
+            ).flatten(),
+            "Control_to_RECV_mapping": numpy.array(  # Y maps on control
+                [[1, x * 2 + 0] for x in range(MAX_ANTENNA // 2)]
+            ).flatten(),
         }
 
         antennafield_proxy = self.proxy
@@ -311,22 +318,23 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         antennafield_proxy.put_property(mapping_properties)
         antennafield_proxy.boot()
 
-        self.recv_proxy.write_attribute(
-            "RCU_band_select_RW", [[False] * N_rcu_inp] * N_rcu
-        )
+        self.recv_proxy.write_attribute("RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu)
 
         try:
             antennafield_proxy.write_attribute(
-                "RCU_band_select_RW", [[True, True]] * MAX_ANTENNA
+                # [X, Y]
+                "RCU_band_select_RW",
+                [[1, 2]] * MAX_ANTENNA,
             )
             numpy.testing.assert_equal(
-                numpy.array([[True] * N_rcu_inp] * N_rcu),
-                self.recv_proxy.read_attribute("RCU_band_select_RW").value,
+                # [Power, Control]
+                numpy.array([2, 1] * (MAX_ANTENNA // 2)),
+                self.recv_proxy.read_attribute("RCU_band_select_RW").value.flatten(),
             )
         finally:
             # Always restore recv again
             self.recv_proxy.write_attribute(
-                "RCU_band_select_RW", [[False] * N_rcu_inp] * N_rcu
+                "RCU_band_select_RW", [[0] * N_rcu_inp] * N_rcu
             )
 
         # Verify device did not enter FAULT state
diff --git a/tangostationcontrol/integration_test/default/devices/test_device_observation.py b/tangostationcontrol/integration_test/default/devices/test_device_observation.py
index f6b304da8..634b0f222 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_observation.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_observation.py
@@ -112,12 +112,14 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
     def setup_antennafield_proxy(self):
         # setup AntennaField
         antennafield_proxy = TestDeviceProxy("STAT/AntennaField/HBA0")
-        control_mapping = [[1, i] for i in range(CS001_TILES)]
+        power_mapping = [[1, i * 2 + 0] for i in range(CS001_TILES)]
+        control_mapping = [[1, i * 2 + 1] for i in range(CS001_TILES)]
         antenna_qualities = numpy.array([AntennaQuality.OK] * MAX_ANTENNA)
         antenna_use = numpy.array([AntennaUse.AUTO] * MAX_ANTENNA)
         antennafield_proxy.put_property(
             {
                 "Antenna_Type": ["HBA"],
+                "Power_to_RECV_mapping": numpy.array(power_mapping).flatten(),
                 "Control_to_RECV_mapping": numpy.array(control_mapping).flatten(),
                 "Antenna_to_SDP_Mapping": self.ANTENNA_TO_SDP_MAPPING,
                 "Antenna_Quality": antenna_qualities,
@@ -241,16 +243,17 @@ class TestDeviceObservation(AbstractTestBases.TestDeviceBase):
         self.setup_stationmanager_proxy()
         self.setup_recv_proxy()
         antennafield_proxy = self.setup_antennafield_proxy()
-        antennafield_proxy.RCU_band_select_RW = [0] * CS001_TILES
+        antennafield_proxy.RCU_band_select_RW = [[0, 0]] * CS001_TILES
         self.assertListEqual(
-            antennafield_proxy.RCU_band_select_RW.tolist(), [0] * CS001_TILES
+            antennafield_proxy.RCU_band_select_RW.tolist(),
+            [[0, 0]] * CS001_TILES,
         )
         self.proxy.off()
         self.proxy.observation_settings_RW = self.VALID_JSON
         self.proxy.Initialise()
         self.proxy.On()
         expected_bands = [
-            2
+            [2, 2]
         ] * CS001_TILES  # we request every antenna to be in this band, regardless of mask
         self.assertListEqual(
             antennafield_proxy.RCU_band_select_RW.tolist(), expected_bands
diff --git a/tangostationcontrol/tangostationcontrol/common/calibration.py b/tangostationcontrol/tangostationcontrol/common/calibration.py
index 2bf56dd30..9cf13f644 100644
--- a/tangostationcontrol/tangostationcontrol/common/calibration.py
+++ b/tangostationcontrol/tangostationcontrol/common/calibration.py
@@ -120,13 +120,8 @@ class CalibrationManager:
 
         is_hba = antenna_type.upper() != "LBA"
 
-        for antenna_nr, rcu_band in enumerate(rcu_bands):
-            fpga_nr, input_nr = antenna_to_sdp_mapping[antenna_nr]
-            antenna_name = antenna_names[antenna_nr]
-
-            if input_nr == -1:
-                # skip unconnected antennas
-                continue
+        def get_antenna_calibration(antenna_name: str, rcu_band: int):
+            """Return the calibration values for the given antenna and RCU band."""
 
             calibration_filename = os.path.join(
                 self._tmp_dir.name,
@@ -143,19 +138,29 @@ class CalibrationManager:
                         f"Expected calibration table for station {self._station_name}, but got {table.observation_station} in calibration file {calibration_filename}"
                     )
 
-                if antenna_name not in table.antennas:
+                try:
+                    return table.antennas[antenna_name]
+                except KeyError:
                     raise ValueError(
                         f"Could not find calibration values for field {antennafield_name} antenna {antenna_name} (index {antenna_nr}) in calibration file {calibration_filename}"
                     )
 
-                # set weights, converted from complex to packed uint32
-                fpga_subband_weights[fpga_nr, input_nr, 0] = complex_to_weights(
-                    table.antennas[antenna_name].x
-                )
+        for antenna_nr, (rcu_band_x, rcu_band_y) in enumerate(rcu_bands):
+            fpga_nr, input_nr = antenna_to_sdp_mapping[antenna_nr]
+            antenna_name = antenna_names[antenna_nr]
 
-                fpga_subband_weights[fpga_nr, input_nr, 1] = complex_to_weights(
-                    table.antennas[antenna_name].y
-                )
+            if input_nr == -1:
+                # skip unconnected antennas
+                continue
+
+            # set weights, converted from complex to packed uint32
+            fpga_subband_weights[fpga_nr, input_nr, 0] = complex_to_weights(
+                get_antenna_calibration(antenna_name, rcu_band_x).x
+            )
+
+            fpga_subband_weights[fpga_nr, input_nr, 1] = complex_to_weights(
+                get_antenna_calibration(antenna_name, rcu_band_y).y
+            )
 
         # TODO(L2SS-1312): This should use atomic_read_modify_write
         sdp.FPGA_subband_weights_RW = fpga_subband_weights.reshape(
diff --git a/tangostationcontrol/tangostationcontrol/devices/antennafield.py b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
index 8d7396811..3477e4787 100644
--- a/tangostationcontrol/tangostationcontrol/devices/antennafield.py
+++ b/tangostationcontrol/tangostationcontrol/devices/antennafield.py
@@ -395,7 +395,7 @@ class AntennaField(LOFARDevice):
     )
     Antenna_Cables_Loss_R = attribute(
         doc="Loss caused by the cable between antenna and RCU, in dB, for either polarisation.",
-        dtype=(numpy.float64,),
+        dtype=((numpy.float64,),),
         max_dim_x=N_pol,
         max_dim_y=MAX_ANTENNA,
         unit="dB",
diff --git a/tangostationcontrol/test/common/test_calibration.py b/tangostationcontrol/test/common/test_calibration.py
index 159c088f7..40cb48f5f 100644
--- a/tangostationcontrol/test/common/test_calibration.py
+++ b/tangostationcontrol/test/common/test_calibration.py
@@ -74,7 +74,7 @@ class TestCalibrationManager(base.TestCase):
             ).reshape(-1, 2),
             Antenna_Names_R=[f"T{n + 1}" for n in range(2)],
             Antenna_Type_R="HBA",
-            RCU_band_select_RW=numpy.array([1, 2]),
+            RCU_band_select_RW=numpy.array([[1, 1], [2, 2]]),
             **{"name.return_value": "Stat/AntennaField/TEST"},
         )
         subband_weights = numpy.array([[SDP_UNIT_WEIGHT] * S_pn * N_subbands] * N_pn)
-- 
GitLab


From d5087d47a0b6ed400931f4bd8f55bef4a5d14bf9 Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Mon, 7 Aug 2023 13:49:41 +0200
Subject: [PATCH 11/11] Fix black

---
 .../default/devices/test_device_antennafield.py             | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
index bc4f4dada..d79c50d30 100644
--- a/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
+++ b/tangostationcontrol/integration_test/default/devices/test_device_antennafield.py
@@ -347,11 +347,9 @@ class TestAntennaFieldDevice(AbstractTestBases.TestDeviceBase):
         for antenna_type in ["HBA"]:
             properties = {
                 "Antenna_Type": [antenna_type],
-                "Control_to_RECV_mapping": [1, 1]
-                + [-1, -1] * (CS001_TILES - 1),
+                "Control_to_RECV_mapping": [1, 1] + [-1, -1] * (CS001_TILES - 1),
                 "Power_to_RECV_mapping": [1, 0] + [-1, -1] * (CS001_TILES - 1),
-                "Antenna_to_SDP_Mapping": [0, 1, 0, 0]
-                + [-1, -1] * (CS001_TILES - 2),
+                "Antenna_to_SDP_Mapping": [0, 1, 0, 0] + [-1, -1] * (CS001_TILES - 2),
             }
 
             antennafield_proxy = self.proxy
-- 
GitLab