diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index 9540560a9537eca57d16e208defd44dffa8d9814..69f792101b10b7a83374451928315f0d25f379f3 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -67,6 +67,14 @@ class Beam(lofar_device): dtype=(numpy.double,), max_dim_x=96, fget=lambda self: self._hbat_pointing_timestamp_r) + HBAT_tracking_enabled_R = attribute(access=AttrWriteType.READ, + dtype=numpy.bool, + fget=lambda self: self.HBAT_beam_tracker.is_alive()) + + HBAT_tracking_enabled_RW = attribute(access=AttrWriteType.READ_WRITE, + dtype=numpy.bool, + fget=lambda self: self._hbat_tracking_enabled_rw) + # Directory where the casacore measures that we use, reside. We configure ~/.casarc to # use the symlink /opt/IERS/current, which we switch to the actual set of files to use. measures_directory_R = attribute(dtype=str, access=AttrWriteType.READ, fget = lambda self: get_measures_directory()) @@ -86,6 +94,9 @@ class Beam(lofar_device): self._hbat_pointing_direction_r = numpy.zeros((96,3), dtype="<U32") self._hbat_pointing_direction_rw = numpy.array([["AZELGEO","0deg","90deg"]] * 96, dtype="<U32") + # Initialise tracking control + self._hbat_tracking_enabled_rw = True + # Set a reference of RECV device self.recv_proxy = DeviceProxy("STAT/RECV/1") @@ -107,15 +118,15 @@ class Beam(lofar_device): super().configure_for_on() # Start beam tracking thread - self.HBAT_beam_tracker.start() + if self._hbat_tracking_enabled_rw: + self.HBAT_beam_tracker.start() @log_exceptions def configure_for_off(self): - super().configure_for_off() - # Stop thread object self.HBAT_beam_tracker.stop() - self.HBAT_beam_tracker = None + + super().configure_for_off() # -------- # internal functions @@ -132,6 +143,15 @@ class Beam(lofar_device): # force update across tiles if pointing changes self.HBAT_beam_tracker.force_update() + logger.info("Pointing direction update requested") + + def write_HBAT_tracking_enabled_RW(self, value): + self._hbat_tracking_enabled_rw = value + + if value: + self.HBAT_beam_tracker.start() + else: + self.HBAT_beam_tracker.stop() def _HBAT_delays(self, pointing_direction: numpy.array, timestamp: datetime.datetime = datetime.datetime.now()): """ @@ -225,6 +245,7 @@ class Beam(lofar_device): @command(dtype_in=DevVarStringArray) @DebugIt() + @log_exceptions() @only_in_states([DevState.ON]) def HBAT_set_pointing(self, pointing_direction: list, timestamp: datetime.datetime = datetime.datetime.now()): """ @@ -275,7 +296,7 @@ class BeamTracker(): """ Object that encapsulates a Thread, resposible for beam tracking operations """ def __init__(self, device: lofar_device): - self.thread = Thread(target=self._update_HBAT_pointing_direction) + self.thread = None self.device = device # Condition to trigger a forced update or early abort @@ -284,12 +305,17 @@ class BeamTracker(): def start(self): """ Starts the Beam Tracking thread """ + if self.thread: + # already started + return + self.done = False + self.thread = Thread(target=self._update_HBAT_pointing_direction, name=f"BeamTracker of {self.device.get_name()}") self.thread.start() def is_alive(self): """ Returns True just before the Thread run() method starts until just after the Thread run() method terminates. """ - return self.thread.is_alive() + return self.thread and self.thread.is_alive() def force_update(self): """ Force the pointing to be updated. """ @@ -301,6 +327,9 @@ class BeamTracker(): def stop(self): """ Stops the Beam Tracking loop """ + if not self.thread: + return + self.done = True self.force_update() @@ -309,6 +338,8 @@ class BeamTracker(): if self.is_alive(): logger.error("BeamTracking Thread did not properly terminate") + + self.thread = None def _get_sleep_time(self): """ Computes the sleep time (in seconds) that needs to be waited for the next beam tracking update """ @@ -324,13 +355,15 @@ class BeamTracker(): return sleep_time + self.device.HBAT_beam_tracking_interval else: return sleep_time - + + @log_exceptions() def _update_HBAT_pointing_direction(self): """ Updates the beam weights using a fixed interval of time """ # Check if flag beamtracking is true with self.update_lock: while not self.done: + # TODO: Occasionally this still gets called when the beam device is in OFF? self.device.HBAT_set_pointing(numpy.array(self.device.proxy.HBAT_pointing_direction_RW).flatten()) # sleep until the next update, or when interrupted (this releases the lock, allowing for notification) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_beam.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_beam.py index 21c93eab4f434350fa091523a421837513aadf9c..9af017c12df46b3719c367f94a5452cdddebdbed 100644 --- a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_beam.py +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_device_beam.py @@ -68,6 +68,7 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase): self.proxy.Initialise() self.assertEqual(DevState.STANDBY, self.proxy.state()) self.proxy.set_defaults() + self.proxy.HBAT_tracking_enabled_RW = False self.proxy.on() self.assertEqual(DevState.ON, self.proxy.state()) @@ -90,6 +91,7 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase): recv_proxy = self.setup_recv_proxy() self.proxy.initialise() + self.proxy.HBAT_tracking_enabled_RW = False self.proxy.on() # Point to Zenith @@ -101,11 +103,40 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase): numpy.testing.assert_equal(calculated_HBAT_delay_steps, expected_HBAT_delay_steps) + def test_pointing_across_horizon(self): + # setup RECV as well + recv_proxy = self.setup_recv_proxy() + + self.proxy.initialise() + self.proxy.HBAT_tracking_enabled_RW = False + self.proxy.on() + + # point at north on the horizon + self.proxy.HBAT_set_pointing(["AZELGEO","0deg","0deg"] * 96) + + # obtain delays of the X polarisation of all the elements of the first tile + north_beam_delay_steps = recv_proxy.HBAT_BF_delay_steps_RW[0].reshape(2,4,4)[0] + + # delays must differ under rotation, or our test will give a false positive + self.assertNotEqual(north_beam_delay_steps.tolist(), numpy.rot90(north_beam_delay_steps).tolist()) + + for angle in (90,180,270): + # point at angle degrees (90=E, 180=S, 270=W) + self.proxy.HBAT_set_pointing(["AZELGEO",f"{angle}deg","0deg"] * 96) + + # obtain delays of the X polarisation of all the elements of the first tile + angled_beam_delay_steps = recv_proxy.HBAT_BF_delay_steps_RW[0].reshape(2,4,4)[0] + + expected_delay_steps = numpy.rot90(north_beam_delay_steps, k=-(angle/90)) + + self.assertListEqual(expected_delay_steps.tolist(), angled_beam_delay_steps.tolist(), msg=f"angle={angle}") + def test_delays_same_as_LOFAR_ref_pointing(self): # setup RECV as well recv_proxy = self.setup_recv_proxy() self.proxy.initialise() + self.proxy.HBAT_tracking_enabled_RW = False self.proxy.on() # Point to LOFAR 1 ref pointing (0.929342, 0.952579, J2000) @@ -131,3 +162,24 @@ class TestDeviceBeam(AbstractTestBases.TestDeviceBase): expected_HBAT_delay_steps = numpy.array([24, 25, 27, 29, 17, 18, 20, 21, 10, 11, 13, 14, 3, 4, 5, 7] * 2, dtype=numpy.int64) numpy.testing.assert_equal(calculated_HBAT_delay_steps[0], expected_HBAT_delay_steps) numpy.testing.assert_equal(calculated_HBAT_delay_steps[48], expected_HBAT_delay_steps) + + def test_beam_tracking(self): + # setup RECV as well + recv_proxy = self.setup_recv_proxy() + + self.proxy.initialise() + self.proxy.on() + + # check if we're really tracking + self.assertTrue(self.proxy.HBAT_tracking_enabled_R) + + # point somewhere + new_pointings = [("J2000",f"{tile}deg","0deg") for tile in range(96)] + self.proxy.HBAT_pointing_direction_RW = new_pointings + + # wait for tracking thread to pick up and set. should be almost instant + time.sleep(0.5) + + # check pointing + self.assertListEqual(new_pointings, list(self.proxy.HBAT_pointing_direction_R)) +