예제 #1
0
 def __init__(self, n_chans, n_accs, n_bls, bls_ordering, bandwidth,
              sync_time, int_time, scale_factor_timestamp, ref_ant,
              **kwargs):
     self.dbe_mode = 'c8n856M32k'
     self.ref_ant = Antenna(ref_ant)
     self.req_target('Zenith, azel, 0, 90')
     self.auto_delay = True
예제 #2
0
    def test_dryrun(self):
        nbeams = 32
        antennas = ["m%03d" % ii for ii in range(16)]
        feng_antenna_map = {antenna: ii for ii, antenna in enumerate(antennas)}
        coherent_beam_antennas = antennas
        incoherent_beam_antennas = antennas
        nantennas = len(antennas)
        beam_manager = BeamManager(
            nbeams, [Antenna(ANTENNAS[i]) for i in coherent_beam_antennas])
        delay_config_server = DelayConfigurationServer("127.0.0.1", 0,
                                                       beam_manager)
        delay_config_server.start()
        dc_ip, dc_port = delay_config_server.bind_address
        for _ in range(nbeams):
            beam_manager.add_beam(Target("source0, radec, 123.1, -30.3"))

        coherent_beams_csv = ",".join(
            [beam.idx for beam in beam_manager.get_beams()])
        tot_nchans = 4096
        feng_groups = "spead://239.11.1.150+3:7147"
        nchans_per_group = tot_nchans / nantennas / 4
        chan0_idx = 0
        chan0_freq = 1240e6
        chan_bw = 856e6 / tot_nchans
        mcast_to_beam_map = {
            "spead://239.11.2.150:7147": coherent_beams_csv,
            "spead://239.11.2.151:7147": "ifbf00001"
        }
        feng_config = {
            "bandwidth": 856e6,
            "centre-frequency": 1200e6,
            "sideband": "upper",
            "feng-antenna-map": feng_antenna_map,
            "sync-epoch": 12353524243.0,
            "nchans": 4096
        }
        coherent_beam_config = {
            "tscrunch": 16,
            "fscrunch": 1,
            "antennas": ",".join(coherent_beam_antennas)
        }
        incoherent_beam_config = {
            "tscrunch": 16,
            "fscrunch": 1,
            "antennas": ",".join(incoherent_beam_antennas)
        }
        yield self._send_request_expect_ok('prepare', feng_groups,
                                           nchans_per_group, chan0_idx,
                                           chan0_freq, chan_bw, nbeams,
                                           json.dumps(mcast_to_beam_map),
                                           json.dumps(feng_config),
                                           json.dumps(coherent_beam_config),
                                           json.dumps(incoherent_beam_config),
                                           dc_ip, dc_port)
        yield self._check_sensor_value('device-status', 'ok')
        yield self._send_request_expect_ok('capture-start')
        yield sleep(10)
        yield self._send_request_expect_ok('capture-stop')
        self.server._delay_buf_ctrl.stop()
예제 #3
0
 def fetch_config_info(self):
     """
     @brief   Retrieve configuration information from the delay configuration server
     """
     log.debug("Fetching configuration information from the delay configuration server")
     yield self._delay_client.until_synced()
     antennas_json = yield self._delay_client.sensor.antennas.get_value()
     try:
         antennas = json.loads(antennas_json)
     except Exception as error:
         log.exception("Failed to parse antennas")
         raise error
     self._antennas = [Antenna(antennas[antenna]) for antenna in self._ordered_antennas]
     log.debug("Ordered the antenna capture list to:\n {}".format("\n".join([i.format_katcp() for i in self._antennas])))
     reference_antenna = yield self._delay_client.sensor.reference_antenna.get_value()
     self._reference_antenna = Antenna(reference_antenna)
     log.debug("Reference antenna: {}".format(self._reference_antenna.format_katcp()))
    def setup_sensors(self):
        """
        @brief    Set up monitoring sensors.
        """
        self._target_sensors = []
        for beam in self._beam_manager.get_beams():
            sensor = Sensor.string("{}-target".format(beam.idx),
                                   description="Target for beam {}".format(
                                       beam.idx),
                                   default=beam.target.format_katcp(),
                                   initial_status=Sensor.UNKNOWN)
            self.add_sensor(sensor)
            beam.register_observer(lambda beam, sensor=sensor: sensor.
                                   set_value(beam.target.format_katcp()))

        antenna_map = {
            a.name: a.format_katcp()
            for a in self._beam_manager.antennas
        }
        self._antennas_sensor = Sensor.string(
            "antennas",
            description=
            "JSON breakdown of the antennas (in KATPOINT format) associated with this delay engine",
            default=json.dumps(antenna_map),
            initial_status=Sensor.NOMINAL)
        self.add_sensor(self._antennas_sensor)

        self._phase_reference_sensor = Sensor.string(
            "phase-reference",
            description=
            "A KATPOINT target string denoting the F-engine phasing centre",
            default="unset,radec,0,0",
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._phase_reference_sensor)

        reference_antenna = Antenna(
            "reference,{ref.lat},{ref.lon},{ref.elev}".format(
                ref=self._beam_manager.antennas[0].ref_observer))
        self._reference_antenna_sensor = Sensor.string(
            "reference-antenna",
            description=
            "A KATPOINT antenna string denoting the reference antenna",
            default=reference_antenna.format_katcp(),
            initial_status=Sensor.NOMINAL)
        self.add_sensor(self._reference_antenna_sensor)
예제 #5
0
 def test_get_observer_string(self):
     try:
         value = yield self.kpc.get_observer_string('m001')
     except:
         raise unittest.SkipTest(
             "m001 not available on current portal instance")
     try:
         Antenna(value)
     except Exception as error:
         self.fail(
             "Could not convert antenna string to katpoint Antenna instance,"
             " failed with error {}".format(str(error)))
    def __init__(self):
        Queue.__init__(self)
        self.put(['Kochab', 40, None, '14:30:00', 20, 'Pong'])
        self.put(['Sun', 10, None, '16:10:00', 20, 'Raster'])
        self.put(['Capella', 20, None, '11:10:00', 20, 'Lissajous'])
        self.put(['Kochab', 40, None, '14:32:00', 20, 'Pong'])
        self.put(['Sun', 10, None, '16:12:00', 20, 'Raster'])
        self.put(['Capella', 20, None, '11:12:00', 20, 'Lissajous'])
        self.put(['Kochab', 40, None, '14:35:00', 20, 'Pong'])
        self.put(['Sun', 10, None, '16:15:00', 20, 'Raster'])
        self.put(['Capella', 20, None, '11:15:00', 20, 'Lissajous'])
        RTT = Antenna("Roof Top Telescope (Shed Side)", '51:45:34.5', '-1:15:34.8', 85)
        RTT2 = Antenna("Roof Top Telescope 2 (Stairs Side)", '51:45:34.6', '-1:15:34.9', 85)
        self.antennaList = [RTT, RTT2]
        self.active_antennas = [RTT, RTT2]
        self.halt = False
        self.catalogue = Catalogue(add_specials=True, add_stars=True)
        print 'Initialising Catalogues...'

        filedir = '/Users/eisenbarth/Desktop/telescope_control_repo/telescope_control_program/telescopecontrol'
        #
        # # Hipparcos Catalogue
        # filename = os.path.join(filedir, 'Catalogues/hipparcos.edb')
        # self.catalogue.add_edb(file(filename))
        #
        # CBASS Catalogue
        filename = os.path.join(filedir, 'Catalogues/CBASS_Catalogue.csv')
        self.catalogue.add(file(filename))
        #
        # # Messier Catalogue
        filename = os.path.join(filedir, 'Catalogues/MESSIER.edb')
        self.catalogue.add_edb(file(filename))
        self.update_time = None
        self.in_range = None

        # Checks which targets are in range of the telescopes. Depending on the size of the Catalogue, this can take
        # very long. That's why it is threaded. If the first loop was completed, the list will be shown on the website
        threading.Thread(target=self.check_in_range).start()

        print 'Initialization Done!'
예제 #7
0
 def setup(self):
     self.target = Target('PKS1934-638, radec, 19:39, -63:42')
     self.antennas = [
         Antenna('m000, -30:42:39.8, 21:26:38.0, 1086.6, 13.5, '
                 '-8.264 -207.29 8.5965'),
         Antenna('m063, -30:42:39.8, 21:26:38.0, 1086.6, 13.5, '
                 '-3419.5845 -1840.48 16.3825')
     ]
     corrprods = [('m000h', 'm000h'), ('m000v', 'm000v'),
                  ('m063h', 'm063h'), ('m063v', 'm063v'),
                  ('m000h', 'm063h'), ('m000v', 'm063v')]
     subarray = Subarray(self.antennas, corrprods)
     spw = SpectralWindow(centre_freq=1284e6,
                          channel_width=0,
                          num_chans=16,
                          sideband=1,
                          bandwidth=856e6)
     # Pick a time when the source is up as that seems more realistic
     self.timestamps = 1234667890.0 + 1.0 * np.arange(10)
     self.dataset = MinimalDataSet(self.target, subarray, spw,
                                   self.timestamps)
     self.array_ant = self.dataset.sensor.get('Antennas/array/antenna')[0]
예제 #8
0
        def configure():
            kpc = self._katportal_wrapper_type(streams['cam.http']['camdata'])
            # Get all antenna observer strings
            futures, observers = [],[]
            for antenna in antennas:
                log.debug("Fetching katpoint string for antenna {}".format(antenna))
                futures.append(kpc.get_observer_string(antenna))
            for ii,future in enumerate(futures):
                try:
                    observer = yield future
                except Exception as error:
                    log.error("Error on katportalclient call: {}".format(str(error)))
                    req.reply("fail", "Error retrieving katpoint string for antenna {}".format(antennas[ii]))
                    return
                else:
                    log.debug("Fetched katpoint antenna: {}".format(observer))
                    observers.append(Antenna(observer))

            # Get bandwidth, cfreq, sideband, f-eng mapping

            #TODO: Also get sync-epoch

            log.debug("Fetching F-engine and subarray configuration information")
            bandwidth_future = kpc.get_bandwidth(feng_stream_name)
            cfreq_future = kpc.get_cfreq(feng_stream_name)
            sideband_future = kpc.get_sideband(feng_stream_name)
            feng_antenna_map_future = kpc.get_antenna_feng_id_map(instrument_name, antennas)
            sync_epoch_future = kpc.get_sync_epoch()
            bandwidth = yield bandwidth_future
            cfreq = yield cfreq_future
            sideband = yield sideband_future
            feng_antenna_map = yield feng_antenna_map_future
            sync_epoch = yield sync_epoch_future
            feng_config = {
                'bandwidth': bandwidth,
                'centre-frequency': cfreq,
                'sideband': sideband,
                'feng-antenna-map': feng_antenna_map,
                'sync-epoch': sync_epoch,
                'nchans': n_channels
            }
            for key, value in feng_config.items():
                log.debug("{}: {}".format(key, value))
            product = FbfProductController(self, product_id, observers, n_channels,
                feng_groups, proxy_name, feng_config)
            self._products[product_id] = product
            self._update_products_sensor()
            req.reply("ok",)
            log.debug("Configured FBFUSE instance with ID: {}".format(product_id))
예제 #9
0
    def __init__(self, target, subarray, spectral_window, timestamps):
        super(MinimalDataSet, self).__init__(name='test', ref_ant='array')
        num_dumps = len(timestamps)
        num_chans = spectral_window.num_chans
        num_corrprods = len(subarray.corr_products)
        dump_period = timestamps[1] - timestamps[0]

        def constant_sensor(value):
            return CategoricalData([value], [0, num_dumps])

        self.subarrays = [subarray]
        self.spectral_windows = [spectral_window]
        sensors = {}
        for ant in subarray.ants:
            sensors['Antennas/%s/antenna' %
                    (ant.name, )] = constant_sensor(ant)
            az, el = target.azel(timestamps, ant)
            sensors['Antennas/%s/az' % (ant.name, )] = az
            sensors['Antennas/%s/el' % (ant.name, )] = el
        # Extract array reference position as 'array_ant' from last antenna
        array_ant_fields = ['array'] + ant.description.split(',')[1:5]
        array_ant = Antenna(','.join(array_ant_fields))
        sensors['Antennas/array/antenna'] = constant_sensor(array_ant)

        sensors['Observation/target'] = constant_sensor(target)
        for name in ('spw', 'subarray', 'scan', 'compscan', 'target'):
            sensors['Observation/%s_index' % (name, )] = constant_sensor(0)
        sensors['Observation/scan_state'] = constant_sensor('track')
        sensors['Observation/label'] = constant_sensor('track')

        self._timestamps = timestamps
        self._time_keep = np.full(num_dumps, True, dtype=np.bool_)
        self._freq_keep = np.full(num_chans, True, dtype=np.bool_)
        self._corrprod_keep = np.full(num_corrprods, True, dtype=np.bool_)
        self.dump_period = dump_period
        self.start_time = Timestamp(timestamps[0] - 0.5 * dump_period)
        self.end_time = Timestamp(timestamps[-1] + 0.5 * dump_period)
        self.sensor = SensorCache(sensors,
                                  timestamps,
                                  dump_period,
                                  keep=self._time_keep,
                                  virtual=DEFAULT_VIRTUAL_SENSORS)
        self.catalogue.add(target)
        self.catalogue.antenna = array_ant
        self.select(spw=0, subarray=0)
예제 #10
0
 def __init__(self, description, real_az_min_deg, real_az_max_deg,
              real_el_min_deg, real_el_max_deg, max_slew_azim_dps,
              max_slew_elev_dps, inner_threshold_deg, **kwargs):
     self.observer = description
     self.ant = Antenna(description)
     self.mode = 'STOP'
     self.req_target('')
     self.activity = 'stop'
     self.lock_threshold = inner_threshold_deg
     self.pos_actual_scan_azim = self.pos_request_scan_azim = 0.0
     self.pos_actual_scan_elev = self.pos_request_scan_elev = 90.0
     self.real_az_min_deg = real_az_min_deg
     self.real_az_max_deg = real_az_max_deg
     self.real_el_min_deg = real_el_min_deg
     self.real_el_max_deg = real_el_max_deg
     self.max_slew_azim_dps = max_slew_azim_dps
     self.max_slew_elev_dps = max_slew_elev_dps
     self._last_update = 0.0
import logging
import os
import unittest
from tornado.testing import AsyncTestCase, gen_test
from katpoint import Antenna, Target
from mpikat import DelayConfigurationServer, BeamManager

root_logger = logging.getLogger('')
root_logger.setLevel(logging.CRITICAL)

DEFAULT_ANTENNAS_FILE = os.path.join(os.path.dirname(__file__), 'data',
                                     'default_antenna.csv')
with open(DEFAULT_ANTENNAS_FILE, "r") as f:
    DEFAULT_ANTENNAS = f.read().strip().splitlines()
KATPOINT_ANTENNAS = [Antenna(i) for i in DEFAULT_ANTENNAS]


class TestDelayConfigurationServer(AsyncTestCase):
    def setUp(self):
        super(TestDelayConfigurationServer, self).setUp()

    def tearDown(self):
        super(TestDelayConfigurationServer, self).tearDown()

    @gen_test
    def test_startup(self):
        bm = BeamManager(4, KATPOINT_ANTENNAS)
        de = DelayConfigurationServer("127.0.0.1", 0, bm)
        de.start()
        bm.add_beam(Target('test_target0,radec,12:00:00,01:00:00'))
예제 #12
0
class DelayBufferController(object):
    def __init__(self, delay_client, ordered_beams, ordered_antennas, nreaders):
        """
        @brief    Controls shared memory delay buffers that are accessed by one or more
                  beamformer instances.

        @params   delay_client       A KATCPResourceClient connected to an FBFUSE delay engine server
        @params   ordered_beams      A list of beam IDs in the order that they should be generated by the beamformer
        @params   orderded_antennas  A list of antenna IDs in the order which they should be captured by the beamformer
        @params   nreaders           The number of posix shared memory readers that will access the memory
                                     buffers that are managed by this instance.
        """
        self._nreaders = nreaders
        self._delay_client = delay_client
        self._ordered_antennas = ordered_antennas
        self._ordered_beams = ordered_beams
        self.shared_buffer_key = "delay_buffer"
        self.mutex_semaphore_key = "delay_buffer_mutex"
        self.counting_semaphore_key = "delay_buffer_count"
        self._nbeams = len(self._ordered_beams)
        self._nantennas = len(self._ordered_antennas)
        self._delays_array = self._delays = np.rec.recarray((self._nbeams, self._nantennas),
            dtype=[("delay_rate","float32"),("delay_offset","float32")])
        self._targets = OrderedDict()
        for beam in self._ordered_beams:
            self._targets[beam] = Target(DEFAULT_TARGET)
        self._phase_reference = Target(DEFAULT_TARGET)
        self._update_rate = DEFAULT_UPDATE_RATE
        self._delay_span = DEFAULT_DELAY_SPAN
        self._update_callback = None
        self._beam_callbacks = {}

    def unlink_all(self):
        """
        @brief   Unlink (remove) all posix shared memory sections and semaphores.
        """
        log.debug("Unlinking all relevant posix shared memory segments and semaphores")
        try:
            posix_ipc.unlink_semaphore(self.counting_semaphore_key)
        except posix_ipc.ExistentialError:
            pass
        try:
            posix_ipc.unlink_semaphore(self.mutex_semaphore_key)
        except posix_ipc.ExistentialError:
            pass
        try:
            posix_ipc.unlink_shared_memory(self.shared_buffer_key)
        except posix_ipc.ExistentialError:
            pass

    @coroutine
    def fetch_config_info(self):
        """
        @brief   Retrieve configuration information from the delay configuration server
        """
        log.debug("Fetching configuration information from the delay configuration server")
        yield self._delay_client.until_synced()
        antennas_json = yield self._delay_client.sensor.antennas.get_value()
        try:
            antennas = json.loads(antennas_json)
        except Exception as error:
            log.exception("Failed to parse antennas")
            raise error
        self._antennas = [Antenna(antennas[antenna]) for antenna in self._ordered_antennas]
        log.debug("Ordered the antenna capture list to:\n {}".format("\n".join([i.format_katcp() for i in self._antennas])))
        reference_antenna = yield self._delay_client.sensor.reference_antenna.get_value()
        self._reference_antenna = Antenna(reference_antenna)
        log.debug("Reference antenna: {}".format(self._reference_antenna.format_katcp()))

    @coroutine
    def start(self):
        """
        @brief   Start the delay buffer controller

        @detail  This method will create all necessary posix shared memory segments
                 and semaphores, retreive necessary information from the delay
                 configuration server and start the delay update callback loop.
        """
        yield self.fetch_config_info()
        self.register_callbacks()
        self.unlink_all()
        # This semaphore is required to protect access to the shared_buffer
        # so that it is not read and written simultaneously
        # The value is set to two such that two processes can read simultaneously
        log.info("Creating mutex semaphore, key='{}'".format(self.mutex_semaphore_key))
        self._mutex_semaphore = posix_ipc.Semaphore(
            self.mutex_semaphore_key,
            flags=posix_ipc.O_CREX,
            initial_value=self._nreaders)

        # This semaphore is used to notify beamformer instances of a change to the
        # delay models. Upon any change its value is simply incremented by one.
        # Note: There sem_getvalue does not work on Mac OS X so the value of this
        # semaphore cannot be tested on OS X (this is only a problem for local testing).
        log.info("Creating counting semaphore, key='{}'".format(self.counting_semaphore_key))
        self._counting_semaphore = posix_ipc.Semaphore(self.counting_semaphore_key,
            flags=posix_ipc.O_CREX,
            initial_value=0)

        # This is the share memory buffer that contains the delay models for the
        log.info("Creating shared memory, key='{}'".format(self.shared_buffer_key))
        self._shared_buffer = posix_ipc.SharedMemory(
            self.shared_buffer_key,
            flags=posix_ipc.O_CREX,
            size=len(self._delays_array.tobytes()))

        # For reference one can access this memory from another python process using:
        # shm = posix_ipc.SharedMemory("delay_buffer")
        # data_map = mmap.mmap(shm.fd, shm.size)
        # data = np.frombuffer(data_map, dtype=[("delay_rate","float32"),("delay_offset","float32")])
        # data = data.reshape(nbeams, nantennas)

        self._shared_buffer_mmap = mmap(self._shared_buffer.fd, self._shared_buffer.size)
        self._update_callback = PeriodicCallback(self._safe_update_delays, self._update_rate*1000)
        self._update_callback.start()

    def stop(self):
        """
        @brief   Stop the delay buffer controller

        @detail  This method will stop the delay update callback loop, deregister
                 any sensor callbacks and trigger the closing and unlinking of
                 posix IPC objects.
        """
        self._update_callback.stop()
        self.deregister_callbacks()
        log.debug("Closing shared memory mmap and file descriptor")
        self._shared_buffer_mmap.close()
        self._shared_buffer.close_fd()
        self.unlink_all()

    def _update_phase_reference(self, rt, t, status, value):
        if status != "nominal":
            return
        log.debug("Received update to phase-reference: {}, {}, {}, {}".format(rt, t, status, value))
        self._phase_reference = Target(value)

    def register_callbacks(self):
        """
        @brief   Register callbacks on the phase-reference and target positions for each beam

        @detail  The delay configuration server provides information about antennas, reference
                 antennas, phase centres and beam targets. It is currently assumed that the
                 antennas and reference antenna will not (can not) change during an observation
                 as such we here only register callbacks on the phase-reference (a KATPOINT target
                 string specifying the bore sight pointing position) and the individial beam targets.
        """
        log.debug("Registering phase-reference update callback")
        self._delay_client.sensor.phase_reference.set_sampling_strategy('event')
        self._delay_client.sensor.phase_reference.register_listener(self._update_phase_reference)
        for beam in self._ordered_beams:
            sensor_name = "{}_target".format(beam)
            def callback(rt, t, status, value, beam):
                log.debug("Received target update for beam {}: {}".format(beam, value))
                if status == 'nominal':
                    try:
                        self._targets[beam] = Target(value)
                    except Exception as error:
                        log.exception("Error when updating target for beam {}".format(beam))
            self._delay_client.sensor[sensor_name].set_sampling_strategy('event')
            self._delay_client.sensor[sensor_name].register_listener(
                lambda rt, r, status, value, beam=beam: callback(rt, r, status, value, beam))
            self._beam_callbacks[beam] = callback

    def deregister_callbacks(self):
        """
        @brief    Deregister any callbacks started with the register callbacks method
        """
        log.debug("Deregistering phase-reference update callback")
        self._delay_client.sensor.phase_reference.set_sampling_strategy('none')
        self._delay_client.sensor.phase_reference.unregister_listener(self._update_phase_reference)
        log.debug("Deregistering targets update callbacks")
        for beam in self._ordered_beams:
            sensor_name = "{}_target".format(beam)
            self._delay_client.sensor[sensor_name].set_sampling_strategy('none')
            self._delay_client.sensor[sensor_name].unregister_listener(self._beam_callbacks[beam])
        self._beam_callbacks = {}

    def _safe_update_delays(self):
        # This is just a wrapper around update delays that
        # stops it throwing an exception
        try:
            self.update_delays()
        except Exception as error:
            log.exception("Failure while updating delays")

    def update_delays(self):
        """
        @brief    Calculate updated delays based on the currently set targets and
                  phase reference.

        @detail   The delays will be calculated in the order specified in the constructor
                  of the class and the delays will be written to the shared memory segment.

                  Two semaphores are used here:
                    - mutex: This is required to stop clients reading the shared
                             memory segment while it is being written to.
                    - counting: This semaphore is incremented after a succesful write
                                to inform clients that there is fresh data to read
                                from the shared memory segment. It is the responsibility
                                of client applications to track the value of this semaphore.
        """
        log.info("Updating delays")
        timer = Timer()
        delay_calc = DelayPolynomial(self._antennas, self._phase_reference,
            self._targets.values(), self._reference_antenna)
        poly = delay_calc.get_delay_polynomials(time.time(), duration=self._delay_span)
        poly_calc_time = timer.elapsed()
        log.debug("Poly calculation took {} seconds".format(poly_calc_time))
        if poly_calc_time >= self._update_rate:
            log.warning("The time required for polynomial calculation >= delay update rate, "
                "this may result in degredation of beamforming quality")
        timer.reset()
        # Acquire the semaphore for each possible reader
        log.debug("Acquiring semaphore for each reader")
        for ii in range(self._nreaders):
            self._mutex_semaphore.acquire()
        self._shared_buffer_mmap.seek(0)
        self._shared_buffer_mmap.write(poly.astype('float32').tobytes())
        # Increment the counting semaphore to notify the readers
        # that a new model is available
        log.debug("Incrementing counting semaphore")
        self._counting_semaphore.release()
        # Release the semaphore for each reader
        log.debug("Releasing semaphore for each reader")
        for ii in range(self._nreaders):
            self._mutex_semaphore.release()
        log.debug("Delay model writing took {} seconds on worker side".format(timer.elapsed()))
    def setup_sensors(self):
        """
        @brief    Setup the default KATCP sensors.

        @note     As this call is made only upon an FBFUSE configure call a mass inform
                  is required to let connected clients know that the proxy interface has
                  changed.
        """
        self._state_sensor = LoggingSensor.discrete(
            "state",
            description = "Denotes the state of this FBF instance",
            params = self.STATES,
            default = self.IDLE,
            initial_status = Sensor.NOMINAL)
        self._state_sensor.set_logger(self.log)
        self.add_sensor(self._state_sensor)

        self._ca_address_sensor = Sensor.string(
            "configuration-authority",
            description = "The address of the server that will be deferred to for configurations",
            default = "",
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ca_address_sensor)

        self._available_antennas_sensor = Sensor.string(
            "available-antennas",
            description = "The antennas that are currently available for beamforming",
            default = json.dumps({antenna.name:antenna.format_katcp() for antenna in self._katpoint_antennas}),
            initial_status = Sensor.NOMINAL)
        self.add_sensor(self._available_antennas_sensor)

        self._phase_reference_sensor = Sensor.string(
            "phase-reference",
            description="A KATPOINT target string denoting the F-engine phasing centre",
            default="unset,radec,0,0",
            initial_status=Sensor.UNKNOWN)
        self.add_sensor(self._phase_reference_sensor)

        reference_antenna = Antenna("reference,{ref.lat},{ref.lon},{ref.elev}".format(
            ref=self._katpoint_antennas[0].ref_observer))
        self._reference_antenna_sensor = Sensor.string(
            "reference-antenna",
            description="A KATPOINT antenna string denoting the reference antenna",
            default=reference_antenna.format_katcp(),
            initial_status=Sensor.NOMINAL)
        self.add_sensor(self._reference_antenna_sensor)

        self._bandwidth_sensor = Sensor.float(
            "bandwidth",
            description = "The bandwidth this product is configured to process",
            default = self._default_sb_config['bandwidth'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._bandwidth_sensor)

        self._nchans_sensor = Sensor.integer(
            "nchannels",
            description = "The number of channels to be processesed",
            default = self._n_channels,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._nchans_sensor)

        self._cfreq_sensor = Sensor.float(
            "centre-frequency",
            description = "The centre frequency of the band this product configured to process",
            default = self._default_sb_config['centre-frequency'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cfreq_sensor)

        self._cbc_nbeams_sensor = Sensor.integer(
            "coherent-beam-count",
            description = "The number of coherent beams that this FBF instance can currently produce",
            default = self._default_sb_config['coherent-beams-nbeams'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_nbeams_sensor)

        self._cbc_nbeams_per_group = Sensor.integer(
            "coherent-beam-count-per-group",
            description = "The number of coherent beams packed into a multicast group",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_nbeams_per_group)

        self._cbc_ngroups = Sensor.integer(
            "coherent-beam-ngroups",
            description = "The number of multicast groups used for coherent beam transmission",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_ngroups)

        self._cbc_nbeams_per_server_set = Sensor.integer(
            "coherent-beam-nbeams-per-server-set",
            description = "The number of beams produced by each server set",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_nbeams_per_server_set)

        self._cbc_tscrunch_sensor = Sensor.integer(
            "coherent-beam-tscrunch",
            description = "The number time samples that will be integrated when producing coherent beams",
            default = self._default_sb_config['coherent-beams-tscrunch'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_tscrunch_sensor)

        self._cbc_fscrunch_sensor = Sensor.integer(
            "coherent-beam-fscrunch",
            description = "The number frequency channels that will be integrated when producing coherent beams",
            default = self._default_sb_config['coherent-beams-fscrunch'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_fscrunch_sensor)

        self._cbc_antennas_sensor = Sensor.string(
            "coherent-beam-antennas",
            description = "The antennas that will be used when producing coherent beams",
            default = self._default_sb_config['coherent-beams-antennas'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_antennas_sensor)

        self._cbc_mcast_groups_sensor = Sensor.string(
            "coherent-beam-multicast-groups",
            description = "Multicast groups used by this instance for sending coherent beam data",
            default = "",
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_mcast_groups_sensor)

        self._cbc_mcast_groups_mapping_sensor = Sensor.string(
            "coherent-beam-multicast-group-mapping",
            description = "Mapping of mutlicast group address to the coherent beams in that group",
            default= "",
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._cbc_mcast_groups_mapping_sensor)

        self._ibc_nbeams_sensor = Sensor.integer(
            "incoherent-beam-count",
            description = "The number of incoherent beams that this FBF instance can currently produce",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ibc_nbeams_sensor)

        self._ibc_tscrunch_sensor = Sensor.integer(
            "incoherent-beam-tscrunch",
            description = "The number time samples that will be integrated when producing incoherent beams",
            default = self._default_sb_config['incoherent-beam-tscrunch'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ibc_tscrunch_sensor)

        self._ibc_fscrunch_sensor = Sensor.integer(
            "incoherent-beam-fscrunch",
            description = "The number frequency channels that will be integrated when producing incoherent beams",
            default = self._default_sb_config['incoherent-beam-fscrunch'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ibc_fscrunch_sensor)

        self._ibc_antennas_sensor = Sensor.string(
            "incoherent-beam-antennas",
            description = "The antennas that will be used when producing incoherent beams",
            default = self._default_sb_config['incoherent-beam-antennas'],
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ibc_antennas_sensor)

        self._ibc_mcast_group_sensor = Sensor.string(
            "incoherent-beam-multicast-group",
            description = "Multicast group used by this instance for sending incoherent beam data",
            default = "",
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._ibc_mcast_group_sensor)

        self._servers_sensor = Sensor.string(
            "servers",
            description = "The worker server instances currently allocated to this product",
            default = ",".join(["{s.hostname}:{s.port}".format(s=server) for server in self._servers]),
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._servers_sensor)

        self._nserver_sets_sensor = Sensor.integer(
            "nserver-sets",
            description = "The number of server sets (independent subscriptions to the F-engines)",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._nserver_sets_sensor)

        self._nservers_per_set_sensor = Sensor.integer(
            "nservers-per-set",
            description = "The number of servers per server set",
            default = 1,
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._nservers_per_set_sensor)

        self._delay_config_server_sensor = Sensor.string(
            "delay-config-server",
            description = "The address of the delay configuration server for this product",
            default = "",
            initial_status = Sensor.UNKNOWN)
        self.add_sensor(self._delay_config_server_sensor)
예제 #14
0
    def configure(self, product_id, antennas_csv, n_channels, streams_json,
                  proxy_name):
        """
        @brief     Internal implementation of configure.

        @detail    This function is used to break the configure logic from only being
                   accessible through a KATCP request. For details for the parameters
                   see the "request_configure" method implemented above
        """
        with open(CONFIG_PICKLE_FILE, "w") as f:
            cPickle.dump((product_id, antennas_csv, n_channels, streams_json,
                          proxy_name), f)
        self._last_configure_arguments = (product_id, antennas_csv, n_channels,
                                          streams_json, proxy_name)
        msg = ("Configuring new FBFUSE product",
               "Product ID: {}".format(product_id),
               "Antennas: {}".format(antennas_csv),
               "Nchannels: {}".format(n_channels),
               "Streams: {}".format(streams_json),
               "Proxy name: {}".format(proxy_name))
        log.info("\n".join(msg))
        # Test if product_id already exists
        if product_id in self._products:
            raise ProductExistsError(
                "FBF already has a configured product with ID: {}".format(
                    product_id))
        # Determine number of nodes required based on number of antennas in subarray
        # Note this is a poor way of handling this that may be updated later. In theory
        # there is a throughput measure as a function of bandwidth, polarisations and number
        # of antennas that allows one to determine the number of nodes to run. Currently we
        # just assume one antennas worth of data per NIC on our servers, so two antennas per
        # node.
        antennas = parse_csv_antennas(antennas_csv)
        if n_channels not in VALID_NCHANS:
            raise Exception(
                ("The provided number of channels ({}) is not "
                 "valid. Valid options are {}").format(n_channels,
                                                       VALID_NCHANS))
        streams = json.loads(streams_json)
        try:
            streams['cam.http']['camdata']
            # Need to check for endswith('.antenna-channelised-voltage') as the i0 is not
            # guaranteed to stay the same.
            # i0 = instrument name
            # Need to keep this for future sensor lookups
            streams['cbf.antenna_channelised_voltage']
        except KeyError as error:
            raise KeyError(("JSON streams object does not contain "
                            "required key: {}").format(str(error)))

        for key, value in streams['cbf.antenna_channelised_voltage'].items():
            if key.endswith('.antenna-channelised-voltage'):
                instrument_name, _ = key.split('.')
                feng_stream_name = key
                feng_groups = value
                log.debug("Parsed instrument name from streams: {}".format(
                    instrument_name))
                break
        else:
            raise Exception("Could not determine instrument name "
                            "(e.g. 'i0') from streams")
        kpc = self._katportal_wrapper_type(streams['cam.http']['camdata'])
        # Get all antenna observer strings
        log.debug("Fetching katpoint string for antennas: {}".format(antennas))
        try:
            response = yield kpc.get_observer_strings(antennas)
        except Exception as error:
            log.exception("Error on katportalclient call: {}".format(
                str(error)))
            response = {}
        observers = []
        for antenna in antennas:
            if antenna in response:
                observer = response[antenna]
            else:
                log.warning("Falling back on default pointing model for "
                            "antenna '{}'".format(antenna))
                observer = DEFAULT_ANTENNA_MODELS[antenna]
            log.debug("Fetched katpoint antenna: {}".format(observer))
            observers.append(Antenna(observer))
        # Get bandwidth, cfreq, sideband, f-eng mapping
        # TODO: Also get sync-epoch
        log.debug("Fetching F-engine and subarray configuration information")
        bandwidth_future = kpc.get_bandwidth(feng_stream_name)
        cfreq_future = kpc.get_cfreq(feng_stream_name)
        sideband_future = kpc.get_sideband(feng_stream_name)
        feng_antenna_map_future = kpc.get_antenna_feng_id_map(
            instrument_name, antennas)
        sync_epoch_future = kpc.get_sync_epoch()
        bandwidth = yield bandwidth_future
        cfreq = yield cfreq_future
        sideband = yield sideband_future
        feng_antenna_map = yield feng_antenna_map_future
        sync_epoch = yield sync_epoch_future
        feng_config = {
            'bandwidth': bandwidth,
            'centre-frequency': cfreq,
            'sideband': sideband,
            'feng-antenna-map': feng_antenna_map,
            'sync-epoch': sync_epoch,
            'nchans': n_channels
        }
        for key, value in feng_config.items():
            log.debug("{}: {}".format(key, value))

        log.info("Starting subarray activity tracker")
        product = FbfProductController(self, product_id, observers, n_channels,
                                       feng_groups, proxy_name, feng_config,
                                       streams['cam.http']['camdata'],
                                       self._katportal_wrapper_type)
        self._products[product_id] = product
        self._update_products_sensor()
        log.debug("Configured FBFUSE instance with ID: {}".format(product_id))
예제 #15
0
    def setup(self, subarray_size, antennas_csv, nbeams, tot_nchans,
              feng_groups, chan0_idx, worker_idx):
        cbc_antennas_names = parse_csv_antennas(antennas_csv)
        cbc_antennas = [Antenna(ANTENNAS[name]) for name in cbc_antennas_names]
        self._beam_manager = BeamManager(nbeams, cbc_antennas)
        self._delay_config_server = DelayConfigurationServer(
            "127.0.0.1", 0, self._beam_manager)
        self._delay_config_server.start()
        antennas_json = self._delay_config_server._antennas_sensor.value()
        antennas = json.loads(antennas_json)
        coherent_beams = ["cfbf{:05d}".format(ii) for ii in range(nbeams)]
        coherent_beams_csv = ",".join(coherent_beams)
        feng_antenna_map = {antenna: ii for ii, antenna in enumerate(antennas)}
        coherent_beam_antennas = antennas
        incoherent_beam_antennas = antennas
        nantennas = len(antennas)
        nchans_per_group = tot_nchans / subarray_size / 4
        nchans = ip_range_from_stream(feng_groups).count * nchans_per_group

        chan0_freq = 1240e6
        chan_bw = 856e6 / tot_nchans

        mcast_to_beam_map = {"spead://239.11.1.150:7148": "ifbf00001"}
        for ii in range(8):
            mcast_to_beam_map["spead://239.11.1.{}:7148".format(
                ii)] = ",".join(coherent_beams[4 * ii:4 * (ii + 1)])

        feng_config = {
            "bandwidth": 856e6,
            "centre-frequency": 1200e6,
            "sideband": "upper",
            "feng-antenna-map": feng_antenna_map,
            "sync-epoch": 1554907897.0,
            "nchans": tot_nchans
        }
        coherent_beam_config = {
            "tscrunch": 16,
            "fscrunch": 1,
            "antennas": ",".join(coherent_beam_antennas)
        }
        incoherent_beam_config = {
            "tscrunch": 16,
            "fscrunch": 1,
            "antennas": ",".join(incoherent_beam_antennas)
        }

        worker_client = KATCPClientResource(
            dict(name="worker-server-client",
                 address=self._worker_server.bind_address,
                 controlled=True))
        yield worker_client.start()
        yield worker_client.until_synced()

        print "preparing"
        response = yield worker_client.req.prepare(
            feng_groups,
            nchans_per_group,
            chan0_idx,
            chan0_freq,
            chan_bw,
            nbeams,
            json.dumps(mcast_to_beam_map),
            json.dumps(feng_config),
            json.dumps(coherent_beam_config),
            json.dumps(incoherent_beam_config),
            *self._delay_config_server.bind_address,
            timeout=300.0)
        if not response.reply.reply_ok():
            raise Exception("Error on prepare: {}".format(
                response.reply.arguments))
        else:
            print "prepare done"

        yield worker_client.req.capture_start()
def init_wetton_telescope(OVST):
    def _addToSensorList(OVST, sensor):
        '''A method which appends a sensor to the :list: 'sensorUpdaterList' and makes the settings
        sothat the sensors have an 'error' status if they are not in range.
        It is important that the :list: 'sensorUpdaterList' is ordered correct. It is used for updating the window.
        For easy handling order it following: first the sensors which value should be displayed as number and string.
        Then the sensors which value is just necessary as number.


        params
        ------
        sensor: object of :class: 'Sensor'
            Sensor which is appended.
        '''
        sensor.raiseStatus = 3  # 'error'
        OVST.sensorUpdaterList.append(sensor)

    # directory in which the position data gets stored
    OVST.daq_dirpath = '/home/telescopecontrol/philippe/DAQ/antenna_positions'

    OVST.az_limit = [173, 150]
    OVST.el_limit = [25, 95]

    # Values for writing
    OVST.axisToMoveRegister = 21
    OVST.motionCommandRegister = 20
    OVST.moveIncrementalValue = 13
    OVST.moveContinuousValue = 15

    # Status Sensors
    OVST.BasicError = (Sensor(
        Sensor.INTEGER,
        'BasicError',
        'If a BASIC error is detected this VR reports the process number for which the error occurred %s',
        params=[0, 0],
        default=0))
    OVST.BasicError.refRegister = 2
    OVST.BasicError.stringDict = {
        0: "No BASIC error",
        1: "BASIC error in STARTUP",
        2: "BASIC error in MONITOR",
        3: "BASIC error in MAIN",
        4: "BASIC error in VR-UPDATE",
        5:
        "Other user process"  # other values are also possible but not handled
    }
    _addToSensorList(OVST, OVST.BasicError)

    OVST.CurrentSystemStatus = (Sensor(Sensor.INTEGER,
                                       'Current System Status',
                                       'Reports current system status',
                                       params=[0, 7],
                                       initial_status=0))
    OVST.CurrentSystemStatus.refRegister = 10
    OVST.CurrentSystemStatus.stringDict = {
        0: "System initialising",
        1: "Axes disabled",
        2: "System ready",
        3: "Homing all axes",
        4: "Jogging an axis",
        5: "Moving to Inc or Abs position",
        6: "Running main routine",
        7: "stopping",
        99: "fault"
    }
    _addToSensorList(OVST, OVST.CurrentSystemStatus)

    OVST.FaultCode = (Sensor(
        Sensor.INTEGER,
        'Fault Code',
        'In the event of a system fault this VR reports the fault code',
        params=[0, 0],
        initial_status=0))
    OVST.FaultCode.refRegister = 11
    OVST.FaultCode.stringDict = {
        0: "No fault present",
        1: "Emergency stop active",
        2: "CAN I/O Fault",
        3: "Telescope 1 Elevation forward limit switch hit",
        4: "Telescope 1 Elevation reverse limit switch hit",
        5: "Telescope 1 Azimuth forward limit switch hit",
        6: "Telescope 1 Azimuth reverse limit switch hit",
        7: "Telescope 2 Elevation (or Azimuth) forward limit switch hit",
        8: "Telescope 2 Elevation (or Azimuth) reverse limit switch hit",
        9: "Telescope 2 Azimuth (or Elevation) forward limit switch hit",
        10: "Telescope 2 Azimuth (or Elevation) reverse limit switch hit",
        11: "Telescope 1 Elevation drive comms fault",
        12: "Telescope 1 Azimuth drive comms fault",
        13: "Telescope 2 Elevation drive comms fault",
        14: "Telescope 2 Azimuth drive comms fault",
        15: "Telescope 1 Elevation drive fault",
        16: "Telescope 1 Azimuth drive fault",
        17: "Telescope 2 Elevation drive fault",
        18: "Telescope 2 Azimuth drive fault",
        19: "Telescope 1 Elevation following error fault",
        20: "Telescope 1 Azimuth following error fault",
        21: "Telescope 2 Elevation following error fault",
        22: "Telescope 2 Azimuth following error fault",
        99: "BASIC error"
    }
    _addToSensorList(OVST, OVST.FaultCode)

    OVST.AxisWarningsLimitSwitchHits = (Sensor(
        Sensor.INTEGER,
        'Axis Warnings and software limitswitch hits',
        'Reports any system warnings that do not result in program interruption. Also combinations are possible. They are not handled',
        params=[0, 0],
        initial_status=0))
    OVST.AxisWarningsLimitSwitchHits.refRegister = 14
    OVST.AxisWarningsLimitSwitchHits.stringDict = {
        0: "No fault present",
        1: "Telescope 1 Elevation forward software limit, ",
        2: "Telescope 1 Elevation reverse software limit, ",
        4: "Telescope 1 Azimuth forward software limit, ",
        8: "Telescope 1 Azimuth reverse software limit, ",
        16: "Telescope 2 Elevation forward software limit, ",
        32: "Telescope 2 Elevation reverse software limit, ",
        64: "Telescope 2 Azimuth forward software limit, ",
        128: "Telescope 2 Azimuth reverse software limit, ",
        256: "Telescope 1 Elevation warning following error, ",
        512: "Telescope 1 Azimuth warning following error, ",
        1024: "Telescope 2 Elevation warning following error, ",
        2048: "Telescope 2 Azimuth warning following error, "
    }
    _addToSensorList(OVST, OVST.AxisWarningsLimitSwitchHits)

    OVST.CurrentMotionCommand = (Sensor(
        Sensor.INTEGER,
        'Axis Warnings and software limitswitch hits',
        'Call motion command if conditions are met',
        params=[0, 15],
        initial_status=0))
    OVST.CurrentMotionCommand.refRegister = 20
    OVST.CurrentMotionCommand.stringDict = {
        0: "Stop motion",
        1: "Enable axes",
        2: "Disable axes",
        3: "Home all axes",
        4: "Telescope 1 Elevation forward jog",
        5: "Telescope 1 Elevation reverse jog",
        6: "Telescope 1 Azimuth forward jog",
        7: "Telescope 1 Azimuth reverse jog",
        8: "Telescope 2 Elevation forward jog",
        9: "Telescope 2 Elevation reverse jog",
        10: "Telescope 2 Azimuth forward jog",
        11: "Telescope 2 Azimuth reverse jog",
        12: "Move absolute on selected axis",
        13: "Move incremental on selected axis",
        14: "Start main sequence",
        15: "Move continuous on selected axis"
    }  # Start Main Sequence is not recognized as fault
    _addToSensorList(OVST, OVST.CurrentMotionCommand)

    OVST.ConnectionStatus = Sensor(
        Sensor.BOOLEAN,
        "Connection Status",
        "Describes the current status of the modbus connection",
        params=[True, True])
    OVST.ConnectionStatus.stringDict = {
        False: "Not connected",
        True: "Connected"
    }
    OVST.sensorUpdaterList.append(OVST.ConnectionStatus)

    # Create an antenna
    RTT = Antenna("Roof Top Telescope (Shed Side)", '51:45:34.5', '-1:15:34.8',
                  85)  # height is not exact
    # Define the registers
    # Absolute Position and Speed
    RTT.azimuthMoveAbsolutePositionRegister = 221
    RTT.azimuthMoveAbsoluteSpeedRegister = 225
    RTT.elevationMoveAbsolutePositionRegister = 121
    RTT.elevationMoveAbsoluteSpeedRegister = 125

    # Incremental
    RTT.azimuthMoveIncrementalDistanceRegister = 220
    RTT.elevationMoveIncrementalDistanceRegister = 120

    RTT.selectValue = 3  # Allow moving azimuth and elevation

    # Specify the antennas delaychannel
    # [channelNumber, hasPartialDelay, hardwareDelay, goemetricDelay]
    RTT.delayInfo = [0, False, 0, 0]

    OVST.antennaList.append(RTT)

    # Sensor Settings
    # Create a list with objects of :class: 'Sensors' , representing azimuth (index 0) and elevation (index 1)
    azElSensRTT = []

    # The Limits could be read out from the PLC. BUT not forget the offset
    azElSensRTT.append(
        Sensor(Sensor.FLOAT, 'AzimuthRTT',
               'Describes the Azimuth of RoofTopTTelescope', 'degree',
               [-187, 150]))
    azElSensRTT.append(
        Sensor(Sensor.FLOAT, 'ElevationRTT',
               'Describes the Elevation of RoofTopTTelescope', 'degree',
               [25, 95]))

    # Add the PLC registers where the encoder value is stored
    azElSensRTT[1].refRegister = 130  # Elevation
    azElSensRTT[0].refRegister = 230  # Azimuth
    # flag elvation registers; Not flaged Sensors work as azimuth
    azElSensRTT[1].isElevation = True
    azElSensRTT[0].isAzimuth = True
    # Hard coded offset, maybe it could be done by reading out the PLC
    azElSensRTT[1].offset = -41  # 21.04.2017
    azElSensRTT[0].offset = 266

    # If the value changes more than the 'maxDeltaWithoutRolloverDetection', it is interpret as Rollover
    azElSensRTT[0].maxDeltaWithoutRolloverDetection = 90

    # Include the property of psition without rollover reset

    # The values of antenna 1
    OVST.sensorList.append(azElSensRTT[0])  # add azimuth
    OVST.sensorList.append(azElSensRTT[1])  # add elevation

    # Concatenate the objecs of :class: 'Sensor' with a object of :class: 'antenna'
    RTT.azElPos = [OVST.sensorList[0], OVST.sensorList[1]
                   ]  # First the azimuth and then the elevation

    # Create an antenna
    RTT2 = Antenna("Roof Top Telescope 2 (Stairs Side)", '51:45:34.6',
                   '-1:15:34.9', 85)  # height is not exact

    # Define the registers
    RTT2.azimuthMoveAbsolutePositionRegister = 421
    RTT2.elevationMoveAbsolutePositionRegister = 321
    RTT2.azimuthMoveAbsoluteSpeedRegister = 425
    RTT2.elevationMoveAbsoluteSpeedRegister = 325

    # Incremental
    RTT2.azimuthMoveIncrementalDistanceRegister = 420
    RTT2.elevationMoveIncrementalDistanceRegister = 320

    # select Value
    RTT2.selecaskRolloverCountertValue = 0b1100  # Allow moving azimuth and elevation

    # Specify the antennas delaychannel
    # [channelNumber, hasPartialDelay, hardwareDelay, goemetricDelay]
    RTT2.delayInfo = [1, True, 15 * 4 + 7, 0]

    OVST.antennaList.append(RTT2)

    # Sensor Settings
    # Create a list with objects of :class: 'Sensors' , representing azimuth (index 0) and elevation (index 1)
    azElSensRTT2 = []

    # The Limits could be read out from the PLC. BUT not forget the offset
    azElSensRTT2.append(
        Sensor(Sensor.FLOAT, 'AzimuthRTT2',
               'Describes the Azimuth of RoofTopTelescope 2', 'degree',
               [-187, 150]))
    azElSensRTT2.append(
        Sensor(Sensor.FLOAT, 'ElevationRTT2',
               'Describes the Elevation of RoofTopTTelescope 2', 'degree',
               [25, 95]))

    # Add the PLC registers where the encoder value is stored
    azElSensRTT2[1].refRegister = 330  # Elevation
    azElSensRTT2[0].refRegister = 430  # Azimuth
    # flag elevation registers; Not flagged Sensors work as azimuth
    azElSensRTT2[1].isElevation = True
    azElSensRTT2[0].isAzimuth = True
    # Hard coded offset, maybe it could be done by reading out the PLC
    azElSensRTT2[1].offset = -65
    azElSensRTT2[0].offset = 208.4

    # If the value changes more than the 'maxDeltaWithoutRolloverDetection', it is interpret as Rollover
    azElSensRTT2[0].maxDeltaWithoutRolloverDetection = 90

    # Include the property of possition without rollover reset
    # The values of antenna 2
    OVST.sensorList.append(azElSensRTT2[0])  # add azimuth
    OVST.sensorList.append(azElSensRTT2[1])  # add elevation

    # Concatenate the objecs of :class: 'Sensor' with a object of :class: 'antenna'
    RTT2.azElPos = [OVST.sensorList[2], OVST.sensorList[3]
                    ]  # First the azimuth and then the elevation

    RTT2.selectValue = 0b1100

    # Add the list to the sensor list which is used for updating
    OVST.sensorUpdaterList = OVST.sensorUpdaterList + OVST.sensorList  # + self.moveIncrSensLst + self.moveAbsSensLst