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
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()
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)
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!'
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]
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))
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)
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'))
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)
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))
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