diff --git a/.gitattributes b/.gitattributes index 929b6bb4f5a33f37a97181698c5a0626021018f0..fd70048c7ad908b94cd835cee34f0f16eadd84a4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,6 @@ *.h5 binary *.jpg binary *.bin binary + +# casacore measures tables +table.* binary diff --git a/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.dat b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..1f7869a8f1af1f6d4089128eeed130c78d5c6ae2 Binary files /dev/null and b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.dat differ diff --git a/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0 b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..5c14b2d3c72812c815005ec6fbd10110c7ceab70 Binary files /dev/null and b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0 differ diff --git a/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0i b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..d50b1bdd28ae76847df63f335bcbdd1bd1e7cc9f Binary files /dev/null and b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.f0i differ diff --git a/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.info b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.info new file mode 100644 index 0000000000000000000000000000000000000000..d1fa70b4e608c03fc4f6da5a213c36d568f9fbc4 Binary files /dev/null and b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.info differ diff --git a/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.lock b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..dab1a78a5dec98a99cad64cd140e1308abf761a8 Binary files /dev/null and b/docker-compose/lofar-device-base/WSRT_Measures_stub/ephemerides/DE200/table.lock differ diff --git a/tangostationcontrol/tangostationcontrol/beam/delays.py b/tangostationcontrol/tangostationcontrol/beam/delays.py index 771fad6c90a59ebe002867fedfc55446ead51000..ff81bd485fb66d8a043922a9a65443b3c080ff81 100644 --- a/tangostationcontrol/tangostationcontrol/beam/delays.py +++ b/tangostationcontrol/tangostationcontrol/beam/delays.py @@ -50,9 +50,21 @@ class delay_calculator: return numpy.dot(reference_direction_vector, relative_itrf) / speed_of_light + def is_valid_direction(self, direction): + try: + _ = self.measure.direction(*direction) + except RuntimeError as e: + return False + + return True + def convert(self, direction, antenna_itrf: list([float])): - # obtain the direction vector for a specific pointing - pointing = self.measure.direction(*direction) + try: + # obtain the direction vector for a specific pointing + pointing = self.measure.direction(*direction) + except RuntimeError as e: + raise ValueError(f"Could not convert direction {direction} into a pointing") from e + reference_dir_vector = self.get_direction_vector(pointing) # # compute the delays for an antennas w.r.t. the reference position diff --git a/tangostationcontrol/tangostationcontrol/beam/test_delays.py b/tangostationcontrol/tangostationcontrol/beam/test_delays.py deleted file mode 100644 index 1668345163c4f3bff244ede6ff63cf2c2786f9b1..0000000000000000000000000000000000000000 --- a/tangostationcontrol/tangostationcontrol/beam/test_delays.py +++ /dev/null @@ -1,65 +0,0 @@ -from delays import * - -if __name__ == '__main__': - # # create a frame tied to the reference position - reference_itrf = [3826577.066, 461022.948, 5064892.786] # CS002LBA, in ITRF2005 epoch 2012.5 - d = delay_calculator(reference_itrf) - - # # set the timestamp to solve for - timestamp = datetime.datetime(2021,1,1,0,0,5) - d.set_measure_time(timestamp) - - # compute the delays for an antennas w.r.t. the reference position - antenna_itrf = [[3826923.546, 460915.441, 5064643.489]] # CS001LBA, in ITRF2005 epoch 2012.5 - - # # obtain the direction vector for a specific pointing - direction = "J2000","0deg","0deg" - - # calculate the delays based on the set reference position, the set time and now the set direction and antenna positions. - delays = d.convert(direction, antenna_itrf) - - # print the delays - # pprint.pprint(delays) - - - #test changing the time - - print(f"Changing timestamp test\nBase parametres: Direction: {direction}, position: {antenna_itrf}") - for i in range(10): - # # set the timestamp to solve for - timestamp = datetime.datetime(2021,1,1,0,i,5) - d.set_measure_time(timestamp) - - delays = d.convert(direction, antenna_itrf) - - # print the delays - print(f"Timestamp: {timestamp}: {delays}") - - - # reset time - timestamp = datetime.datetime(2021, 1, 1, 0, 0, 5) - d.set_measure_time(timestamp) - - - #test changing the antenna position - print(f"Changing Antenna position test.\nBase parametres: Time: {timestamp} Direction: {direction}") - for i in range(10): - antenna_itrf = [[3826577.066 + i, 461022.948, 5064892.786]] # CS002LBA, in ITRF2005 epoch 2012.5 - - delays = d.convert(direction, antenna_itrf) - - # print the delays - print(f"Antenna position: {antenna_itrf}: {delays}") - - # test changing the direction - - antenna_itrf = [[3826923.546, 460915.441, 5064643.489]] # CS001LBA, in ITRF2005 epoch 2012.5 - print(f"Changing direction test.\nBase parametres: Time: {timestamp} , position: {antenna_itrf}") - - for i in range(10): - direction = "J2000", f"{i}deg", "0deg" - - delays = d.convert(direction, antenna_itrf) - - # print the delays - print(f"Direction: {direction}: {delays}") diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index 052440551ae1870d1fa7440a54c3c7f839c3a533..9540560a9537eca57d16e208defd44dffa8d9814 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -8,12 +8,12 @@ """ import numpy -import datetime, time +import datetime from json import loads from tango.server import attribute, command, device_property from tango import AttrWriteType, DebugIt, DevState, DeviceProxy, DevVarStringArray, DevVarDoubleArray, DevString -from threading import Thread +from threading import Thread, Lock, Condition # Additional import from tangostationcontrol.common.entrypoint import entry @@ -83,8 +83,8 @@ class Beam(lofar_device): # Initialise pointing array data and attribute self._hbat_pointing_timestamp_r = numpy.zeros(96, dtype=numpy.double) - self._hbat_pointing_direction_r = numpy.zeros((96,3), dtype=numpy.str) - self._hbat_pointing_direction_rw = numpy.zeros((96,3), dtype=numpy.str) + self._hbat_pointing_direction_r = numpy.zeros((96,3), dtype="<U32") + self._hbat_pointing_direction_rw = numpy.array([["AZELGEO","0deg","90deg"]] * 96, dtype="<U32") # Set a reference of RECV device self.recv_proxy = DeviceProxy("STAT/RECV/1") @@ -121,9 +121,17 @@ class Beam(lofar_device): # internal functions # -------- - def write_HBAT_pointing_direction_RW(self): + def write_HBAT_pointing_direction_RW(self, value): """ Setter method for attribute HBAT_pointing_direction_RW """ - self.HBAT_pointing_direction_RW = self._hbat_pointing_direction_rw + # verify whether values are valid + for tile in range(96): + if not self.HBAT_delay_calculators[tile].is_valid_direction(value[tile]): + raise ValueError(f"Invalid direction: {value[tile]}") + + self._hbat_pointing_direction_rw = value + + # force update across tiles if pointing changes + self.HBAT_beam_tracker.force_update() def _HBAT_delays(self, pointing_direction: numpy.array, timestamp: datetime.datetime = datetime.datetime.now()): """ @@ -266,42 +274,64 @@ class BeamTracker(): DISCONNECT_TIMEOUT = 3.0 """ Object that encapsulates a Thread, resposible for beam tracking operations """ - def __init__(self, device:lofar_device): + def __init__(self, device: lofar_device): self.thread = Thread(target=self._update_HBAT_pointing_direction) self.device = device + + # Condition to trigger a forced update or early abort + self.update_lock = Lock() + self.update_condition = Condition(self.update_lock) def start(self): """ Starts the Beam Tracking thread """ - self.done = False # beam tracking loop flag + self.done = False 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() + + def force_update(self): + """ Force the pointing to be updated. """ + + # inform the thread to stop waiting + with self.update_lock: + self.update_condition.notify() def stop(self): """ Stops the Beam Tracking loop """ - self.done = True # beam tracking loop flag + + self.done = True + self.force_update() + + # wait for thread to finish self.thread.join(self.DISCONNECT_TIMEOUT) - if (self.thread.is_alive()): + + if self.is_alive(): logger.error("BeamTracking Thread did not properly terminate") def _get_sleep_time(self): """ Computes the sleep time (in seconds) that needs to be waited for the next beam tracking update """ now = datetime.datetime.now().timestamp() + # Computes the left seconds before the next update next_update_in = self.device.HBAT_beam_tracking_interval - (now % self.device.HBAT_beam_tracking_interval) + # Computes the needed sleep time before the next update sleep_time = next_update_in - self.device.HBAT_beam_tracking_preparation_period # If sleep time is negative, add the tracking interval for the next update - if (sleep_time<0): + if sleep_time < 0: return sleep_time + self.device.HBAT_beam_tracking_interval else: return sleep_time def _update_HBAT_pointing_direction(self): """ Updates the beam weights using a fixed interval of time """ + # Check if flag beamtracking is true - while (not(self.done)): - time.sleep(self._get_sleep_time()) - self.device.HBAT_set_pointing(numpy.array(self.device.proxy.HBAT_pointing_direction_RW).flatten()) + with self.update_lock: + while not self.done: + 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) + self.update_condition.wait(self._get_sleep_time()) diff --git a/tangostationcontrol/tangostationcontrol/test/beam/__init__.py b/tangostationcontrol/tangostationcontrol/test/beam/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py b/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py new file mode 100644 index 0000000000000000000000000000000000000000..624491c454f6d1b6795295b66eee072f45774928 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/beam/test_delays.py @@ -0,0 +1,103 @@ +import datetime + +from tangostationcontrol.beam.delays import * +from tangostationcontrol.test import base + + + + + +class TestDelays(base.TestCase): + def test_init(self): + """ + Fail condition is simply the object creation failing + """ + + reference_itrf = [3826577.066, 461022.948, 5064892.786] # CS002LBA, in ITRF2005 epoch 2012.5 + d = delay_calculator(reference_itrf) + + self.assertIsNotNone(d) + + def test_sun(self): + # # create a frame tied to the reference position + reference_itrf = [3826577.066, 461022.948, 5064892.786] + d = delay_calculator(reference_itrf) + + for i in range(24): + + # set the time to the day of the winter solstice 2021 (21 december 16:58) as this is the time with the least change in sunlight + timestamp = datetime.datetime(2021, 12, 21, i, 58, 0) + d.set_measure_time(timestamp) + + + # point to the sun + direction = "SUN", "0deg", "0deg" + + # calculate the delays based on the set reference position, the set time and now the set direction and antenna positions. + pointing = d.measure.direction(*direction) + direction = d.get_direction_vector(pointing) + + """ + direction[2] is the z-coordinate of ITRF, which points to the north pole. + This direction is constant when pointing to the sun, as the earth rotates around its axis, + but changes slowly due to the earths rotation around the sun. + The summer and winter solstices are when these values are at their peaks and the changes are the smallest. + This test takes the value at the winter solstice and checks whether the measured values are near enough to that. + """ + + # Measured manually at the winter solstice. Using datetime.datetime(2021, 12, 21, 16, 58, 0) + z_at_solstice = -0.3977784695213487 + z_direction = direction[2] + + self.assertAlmostEqual(z_at_solstice, z_direction, 4) + + def test_identical_location(self): + # # create a frame tied to the reference position + reference_itrf = [3826577.066, 461022.948, 5064892.786] # CS002LBA, in ITRF2005 epoch 2012.5 + d = delay_calculator(reference_itrf) + + # set the antenna position identical to the reference position + antenna_itrf = [[reference_itrf[0], reference_itrf[1], reference_itrf[2]]] # CS001LBA, in ITRF2005 epoch 2012.5 + + # # set the timestamp to solve for + timestamp = datetime.datetime(2000, 1, 1, 0, 0, 0) + d.set_measure_time(timestamp) + + # compute the delays for an antennas w.r.t. the reference position + + # # obtain the direction vector for a specific pointing + direction = "J2000", "0deg", "0deg" + + # calculate the delays based on the set reference position, the set time and now the set direction and antenna positions. + delays = d.convert(direction, antenna_itrf) + + self.assertListEqual(delays, [0.0], msg=f"delays = {delays}") + + def test_light_second_delay(self): + """ + This test measures the delay between 2 positions 1 light second apart. + """ + + # # create a frame tied to the reference position + reference_itrf = [3826577.066, 461022.948, 5064892.786] # CS002LBA, in ITRF2005 epoch 2012.5 + d = delay_calculator(reference_itrf) + + # set the antenna position identical to the reference position + speed_of_light = 299792458.0 + antenna_itrf = [[reference_itrf[0], reference_itrf[1] - speed_of_light, reference_itrf[2]]] # CS001LBA, in ITRF2005 epoch 2012.5 + + # # set the timestamp to solve for + timestamp = datetime.datetime(2000, 1, 1, 0, 0, 0) + d.set_measure_time(timestamp) + + # compute the delays for an antennas w.r.t. the reference position + + # # obtain the direction vector for a specific pointing + direction = "J2000", "0deg", "0deg" + + # calculate the delays based on the set reference position, the set time and now the set direction and antenna positions. + delays = d.convert(direction, antenna_itrf) + + + self.assertTrue(0.98 <= delays[0] <= 1.02, f"delays[0] = {delays[0]}") +