Source code for ConfigData

######################################################################
#
#  ConfigData.py -- configuration data classes for DIBAS. Loads mode and
#  bank configuration data from specified configuration sections of a
#  config file and stores them in these classes.
#
#  Copyright (C) 2013 Associated Universities, Inc. Washington DC, USA.
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#  Correspondence concerning GBT software should be addressed as follows:
#  GBT Operations
#  National Radio Astronomy Observatory
#  P. O. Box 2
#  Green Bank, WV 24944-0002 USA
#
######################################################################

import ConfigParser
from datetime import datetime, timedelta

class AutoVivification(dict):
    """
    Implementation of perl's autovivification feature. This allows a
    blank dictionary (one with value '{}') to be initialized using
    multiple keys at once:

    d = {}
    d['foo']['bar'] = 'baz'

    (see http://stackoverflow.com/questions/651794/whats-the-best-way-to-initialize-a-dict-of-dicts-in-python)
    """
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

[docs]class ConfigData(object): """ A common base class for data read out of a config file using ConfigParser. It's main purpose is to serve as a common area for helper functions. """
[docs] def read_kv_pairs(self, config, section, kvkey): """ read_kv_pairs(self, config, section, kvkey) *config:* an open ConfigParser object *section:* the name of a section in the config file *kvkey:* the key of keys returns: A dictionary of kv pairs, empty if *kvkey* is not there, or if it doesn't have any value, or if any of the values are not themselves keys in the section. Looks in the ConfigParser object for a key-of-keys *kvkey* which list a set of arbitrary keys to be read that the program does not know about. The value associated with this key is a comma delimited list of keys. The function parses this, then iterates over the keys to obtain the values, returning a dictionary of key/value pairs. Given an entry in a configuration file's section [MODE1] as follows:: shmkeys = foo,bar,baz foo = frog bar = cat baz = dog then:: cf.read_kv_pairs(config, 'MODE1', 'shmkeys') -> {'bar': 'cat', 'baz': 'dog', 'foo': 'frog'} These may then be used in any way by the Player code. One use, as implied in this example, is to store these values in shared status memory. Another use is to read register/value pairs to be directly written to the FPGA. """ try: # get the keys, stripping out any spaces keys = [key.lstrip().rstrip() for key in config.get(section, kvkey).split(',')] except ConfigParser.NoOptionError: return {} # now iterate over the keys, obtaining the values. Store in dictionary. kvpairs = {} for key in keys: try: val=config.get(section, key) kvpairs[key]=val except: print 'Warning no such key %s found, but was specified in the keys list of section %s' % (key,section) return kvpairs
[docs]class BankData(ConfigData): """ Container for all Bank specific data. """ def __init__(self): self.name = None """The bank name""" self.datahost = None """The 10Gbs IP address for the roach""" self.dataport = None """The 10Gbs port for the roach""" self.dest_ip = None """The 10Gbs HPC IP address""" self.dest_port = None """The KATCP host, on the 1Gbs network""" self.katcp_ip = None """The KATCP host, on the 1Gbs network""" self.katcp_port = None """The KATCP port on the 1Gbs network""" self.synth = None """The location of the Valon serial port: 'katcp' (on roach) or 'local' (on hpc machine)""" self.synth_port = None """The Valon serial port device (i.e. /dev/ttyS1)""" self.synth_ref = None """The reference frequency: 'internal' or 'external'""" self.synth_ref_freq = None """The frequency of the Valon's external reference""" self.synth_vco_range = None """Valon VCO range""" self.synth_rf_level = None """The Valon RF level, in dBm. Legal values are -4, -1, 2, and 5""" self.synth_options = None """ List of Valon options. With the exception of the reference frequency multiplier, all of these are flags which either are clear (0) or set (1): doubler, halver, multiplier, low-spur """ self.mac_base = (2 << 40) + (2 << 32) """ The base mac address used to compute individual mac addresses for the roaches. """ self.shmkvpairs = None """ Arbitrary shared memory key/value pairs. Read from the config file and placed in shared status memory. """ self.roach_kvpairs = None """ Arbitrary FPGA register key/value pairs. Read from the config file and written directly to the FPGA. """ self.i_am_master = None """ Switching Signals master flag. *True* if this bank is the master. """ def __repr__(self): return "BankData (name=%s, datahost=%s, dataport=%i, dest_ip=%s, dest_port=%i, " \ "katcp_ip=%s, katcp_port=%i, synth=%s, synth_port=%s, synth_ref=%i, " \ "synth_ref_freq=%i, synth_vco_range=%s, synth_rf_level=%i, " \ "synth_options=%s, mac_base=%i, shmkvpairs=%s, roach_kvpairs=%s, " \ "i_am_master=%s)" \ % (self.name, self.datahost, self.dataport, self.dest_ip, self.dest_port, self.katcp_ip, self.katcp_port, self.synth, self.synth_port, self.synth_ref, self.synth_ref_freq, str(self.synth_vco_range), self.synth_rf_level, str(self.synth_options), self.mac_base, str(self.shmkvpairs), str(self.roach_kvpairs), str(self.i_am_master))
[docs] def load_config(self, config, bank): """ Given the open *ConfigFile* object *config*, loads data for *bank*. *config* normally is opened with the config file at ``$DIBAS_DIR/etc/config/dibas.conf`` """ self.name = bank self.datahost = config.get(bank, 'datahost').lstrip('"').rstrip('"') self.dataport = config.getint(bank, 'dataport') self.dest_ip = int(config.get(bank, 'dest_ip'), 0) self.dest_port = config.getint(bank, 'dest_port') self.katcp_ip = config.get(bank, 'katcp_ip').lstrip('"').rstrip('"') self.katcp_port = config.getint(bank, 'katcp_port') self.synth = config.get(bank, 'synth') self.synth_port = config.get(bank, 'synth_port').lstrip('"').rstrip('"') self.synth_ref = 1 if config.get(bank, 'synth_ref') == 'external' else 0 self.synth_ref_freq = config.getint(bank, 'synth_ref_freq') self.synth_vco_range = [int(i) for i in config.get(bank, 'synth_vco_range').split(',')] self.synth_rf_level = config.getint(bank, "synth_rf_level") self.synth_options = [int(i) for i in config.get(bank, 'synth_options').split(',')] self.shmkvpairs = self.read_kv_pairs(config, bank, 'shmkeys') self.roach_kvpairs = self.read_kv_pairs(config, bank, 'roach_reg_keys') self.i_am_master = True if config.get('DEFAULTS', 'who_is_master') == bank else False
[docs]class ModeData(ConfigData): """ Container for all Mode specific data. """ def __init__(self): self.mode = None """Mode name""" self.acc_len = None """BOF file specific value""" self.filter_bw = None """Filter bandwidth""" self.frequency = None """Valon frequency""" self.nchan = None """Number of channels, BOF specific value""" self.bof = None """BOF file for this mode""" self.sg_period = None """BOF specific value""" self.reset_phase = [] """Sequence of FPGA register writes necessary to reset the ROACH.""" self.arm_phase = [] """Sequence of FPGA register writes necessary to arm the ROACH.""" self.postarm_phase = [] """Sequence of FPGA register writes required post-arm.""" self.master_slave_sels = AutoVivification() """ This dictionary contains the master/slave select values. The value chosen depends on whether the backend is master, what the switching signal source is (internal/external), and what the blanking source is (internal/external). Typical config file entry:: 0x00,0x00,0x00,0x0E,0x00,0x00 The order of the elements is as follows, where 'm' is master, 's' is slave, 'int' is internal, and 'ext' is external: m/int/int, m/int/ext, m/ext/ext, s/int/int, s/int/ext, s/ext/ext A typical use would be: ``ssg_ms_sel = self.mode.master_slave_sels[master][ss_source][bl_source]`` where *master* is the master flag (0=slave, 1=master), *ss_source* is the switching signal source (0=internal or 1=external), and *bl_source* is the blanking source (0=internal or 1=external) """ self.needed_arm_delay = 0 """ The time needed by the backend to arm in this mode. """ self.cdd_mode = None """ Flag, whether this mode is a coherent de-dispersion mode. """ self.cdd_roach = None """ The roach that takes data for the coherent de-dispersion modes. """ self.cdd_roach_ips = [] """ The IP addresses of the onboard network adapter for the CoDD roach. The CoDD roach has 8 of these. """ self.cdd_hpcs = [] """ The IP addresses of the HPC computers that are the end-points for each the CoDD ROACH's network adapters. There are 8 of these. """ self.cdd_master_hpc = None """ In CoDD mode, the HPC that will control the ROACH. Since theCoDD modes only use 1 ROACH but 8 HPC machines, one must be designated to be the controller. """ self.shmkvpairs = None """ Arbitrary shared memory keys to be placed into status shared memory for this mode. """ self.roach_kvpairs = None """ Arbitrary FPGA register/value pairs that are to be written to the FPGA for this mode. """ def __repr__(self): return "ModeData (mode=%s, acc_len=%s, filter_bw=%s, frequency=%s, nchan=%s, bof=%s, " \ "sg_period=%s, reset_phase=%s, arm_phase=%s, " \ "postarm_phase=%s, needed_arm_delay=%s, master_slave_sels=%s, " \ "shmkvpairs=%s, roach_kvpairs=%s)" % \ (str(self.mode), str(self.acc_len), str(self.filter_bw), str(self.frequency), str(self.nchan), str(self.bof), str(self.sg_period), str(self.reset_phase), str(self.arm_phase), str(self.postarm_phase), str(self.needed_arm_delay), str(self.master_slave_sels), str(self.shmkvpairs), str(self.roach_kvpairs))
[docs] def load_config(self, config, mode): """ Given the open *ConfigFile* object *config*, loads data for *mode*. *config* normally is opened with the config file at ``$DIBAS_DIR/etc/config/dibas.conf`` """ try: self.acc_len = config.getint(mode, 'acc_len') except: self.acc_len = None self.mode = mode self.filter_bw = config.getint(mode, 'filter_bw') self.frequency = config.getfloat(mode, 'frequency') self.nchan = config.getint(mode, 'nchan') self.bof = config.get(mode, 'bof_file') # Get config info for subprocess self.hpc_program = config.get(mode, 'hpc_program').lstrip('"').rstrip('"') self.hpc_fifo_name = config.get(mode, 'hpc_fifo_name').lstrip('"').rstrip('"') self.backend_type = config.get(mode, 'BACKEND') try: self.sg_period = config.getint(mode, 'sg_period') except: self.sg_period=None mssel = config.get(mode, 'master_slave_sel').split(',') self.master_slave_sels[1][0][0] = int(mssel[0], 0) self.master_slave_sels[1][0][1] = int(mssel[1], 0) self.master_slave_sels[1][1][1] = int(mssel[2], 0) self.master_slave_sels[0][0][0] = int(mssel[3], 0) self.master_slave_sels[0][0][1] = int(mssel[4], 0) self.master_slave_sels[0][1][1] = int(mssel[5], 0) # reset, arm and postarm phases; for ease of use, they # should be read, then the commands should be paired # with their parameters, eg ["sg_sync","0x12", # "wait","0.5", ...] should become [("sg_sync", "0x12"), # ("wait", "0.5"), ...] reset_phase = config.get(mode, 'reset_phase').split(',') arm_phase = config.get(mode, 'arm_phase').split(',') postarm_phase = config.get(mode, 'postarm_phase').split(',') self.reset_phase = zip(reset_phase[0::2], reset_phase[1::2]) self.arm_phase = zip(arm_phase[0::2], arm_phase[1::2]) self.postarm_phase = zip(postarm_phase[0::2], postarm_phase[1::2]) self.shmkvpairs = self.read_kv_pairs(config, mode, 'shmkeys') self.roach_kvpairs = self.read_kv_pairs(config, mode, 'roach_reg_keys') arm_delay = config.getint(mode, 'needed_arm_delay') self.needed_arm_delay = timedelta(seconds = arm_delay) # These are optional, for the Coherent Dedispersion Modes self.cdd_mode = True; try: self.cdd_roach = config.get(mode, 'cdd_roach') self.cdd_roach_ips = config.get(mode, 'cdd_roach_ips').split(',') self.cdd_hpcs = config.get(mode, 'cdd_hpcs').split(',') self.cdd_master_hpc = config.get(mode, 'cdd_master_hpc') except ConfigParser.NoOptionError: self.cdd_mode = False try: self.gigabit_interface_name = config.get(mode, 'gigabit_interface_name') self.dest_ip_register_name = config.get(mode, 'dest_ip_register_name') self.dest_port_register_name = config.get(mode, 'dest_port_register_name') except: raise Exception("""One or more of gigabit_interface_name, dest_ip_register_name, or dest_port_register_name is not defined in the mode section""") try: self.shmkvpairs = self.read_kv_pairs(config, mode, 'shmkeys') except: pass try: self.roach_kvpairs = self.read_kv_pairs(config, mode, 'roach_reg_keys') except: pass # If this mode has a list of HPC machines for CODD operation, fetch # the dest_ip and dest_port entries from the corresponding Bank sections. # The resulting list is in the order of ports, i.e the first entry in the # cdd_hpcs list specifies the IP_0 and PT_0 registers of the CODD bof. if self.cdd_hpcs is not None: self.cdd_hpc_ip_info = [] for i in self.cdd_hpcs: try: d_ip = int(config.get(i, 'dest_ip'),0) dprt = int(config.get(i, 'dest_port'),0) self.cdd_hpc_ip_info.append( (d_ip, dprt) ) except: print "No dest_ip/dest_port information for cdd Bank %s" % i pass #raise Exception("No dest_ip/dest_port information for cdd Bank %s" % i)