Example #1
0
    def init_for_roach(self, roach_name):
        "Inits the helper classes we use to interact w/ the given roach name"

        # connect to roach
        tmsg = 'Connecting to %s' % roach_name
        logger.info(tmsg)
        if not self.test:
            self.roach = corr.katcp_wrapper.FpgaClient(roach_name)
            time.sleep(1)
            if not self.roach.is_connected():
                raise Exception("Cannot connect to %s" % roach_name)

        # we'll need this to change the frequency
        valonSerial = "/dev/ttyS1"  # this should never change
        if not self.test:
            self.valon = ValonKATCP(self.roach, valonSerial)

        # this is the object that will find the MMCM value
        self.cal = ADCCalibrate(dir=self.data_dir,
                                roach_name=roach_name,
                                gpib_addr=self.gpibaddr,
                                roach=self.roach,
                                now=self.now,
                                test=self.test)

        # read the config file and find the mmcm  through each mode
        fn = self.get_adc_config_filename(roach_name)
        self.adcConf = ADCConfFile(fn)
Example #2
0
    def init_for_roach(self, roach_name):
        "Inits the helper classes we use to interact w/ the given roach name"

        # connect to roach
        tmsg = 'Connecting to %s'%roach_name
        logger.info(tmsg)
        if not self.test:
            self.roach = corr.katcp_wrapper.FpgaClient(roach_name)
            time.sleep(1)
            if not self.roach.is_connected():
                raise Exception("Cannot connect to %s" % roach_name)
    
        # we'll need this to change the frequency
        valonSerial = "/dev/ttyS1" # this should never change
        if not self.test:
            self.valon = ValonKATCP(self.roach, valonSerial) 
    
        # this is the object that will find the MMCM value
        self.cal = ADCCalibrate(dir = self.data_dir 
                         , roach_name = roach_name
                         , gpib_addr = self.gpibaddr
                         , roach = self.roach
                         , now = self.now
                         , test = self.test)
    
        # read the config file and find the mmcm  through each mode
        fn = self.get_adc_config_filename(roach_name)
        self.adcConf = ADCConfFile(fn)
Example #3
0
class Bank(object):
    """
    A roach bank manager class.
    """
    def __init__(self, bank_name=None, simulate=False):

        print "Player::Bank()"

        # Set up a dictionary of BackEnd Types. This will be used to
        # create the correct backend based on the MODENAME key in the
        # MODE sections of the configuration file:
        #self.BET = {"h1k"           : VegasHBWBackend.VegasHBWBackend,
        #            "h16k"          : VegasHBWBackend.VegasHBWBackend,
        #            "l1/lbw1"       : VegasL1LBWBackend.VegasL1LBWBackend,
        #            "l8/lbw1"       : VegasL8LBWBackend.VegasL8LBWBackend,
        #            "l8/lbw8"       : VegasL8LBWBackend.VegasL8LBWBackend,
        #            "guppi-inco"    : GuppiBackend.GuppiBackend,
        #            "guppi-codd"    : GuppiCODDBackend.GuppiCODDBackend,
        self.BET = {
            "hi_correlator": BeamformerBackend.BeamformerBackend,
            "cal_correlator": BeamformerBackend.BeamformerBackend,
            "frb_correlator": BeamformerBackend.BeamformerBackend,
            "correlator_save": BeamformerBackend.BeamformerBackend,
            "pulsar_beamformer": BeamformerBackend.BeamformerBackend,
            "flag_diagnostic": BeamformerBackend.BeamformerBackend,
            "flag_pfb": BeamformerBackend.BeamformerBackend,
            "flag_pfb_corr": BeamformerBackend.BeamformerBackend,
            "flag_bx_mode": BeamformerBackend.BeamformerBackend,
            "flag_xrfi_corr": BeamformerBackend.BeamformerBackend
        }

        self.dibas_dir = os.getenv('DIBAS_DIR')

        if self.dibas_dir == None:
            raise Exception("'DIBAS_DIR' is not set!")
        self.backend = None
        self.roach = None
        self.valon = None
        self.hpc_macs = {}
        self.simulate = simulate
        print "Player::Bank(): self.simulate=", self.simulate

        # Bank name may be provided, or if not will be inferred from the config file.
        self.bank_name = bank_name.upper() if bank_name else None
        self.bank_data = BankData()
        self.mode_data = {}
        self.banks = {}
        self.current_mode = None
        #self.check_shared_memory()
        self.read_config_file(self.dibas_dir + '/etc/config/dibas.conf')
        self.scan_number = 1

        # This turns on the automatic reaping of dead child processes (anti-zombification)
        signal.signal(signal.SIGCHLD, signal.SIG_IGN)

        #print "setting backend:"
        #self.backend = VegasBackend.Backend(self.bank_data, self.mode_data['MODE1'])

        #print "status mem: ", self.backend.get_status()

    def __del__(self):
        """
        Perform cleanup activities for a Bank object.
        """
        pass

    def check_shared_memory(self):
        """
        This method creates the status shared memory segment if necessary.
        If the segment exists, the state of the status memory is not modified.
        """
        if not self.simulate:
            os.system(self.dibas_dir + '/bin/init_status_memory')

    def clear_shared_memory(self):
        """
        This method clears the status shared memory segment if necessary.
        """
        pass
        #if not self.simulate:
        # os.system(self.dibas_dir + '/bin/x86_64-linux/check_vegas_status -C -I %d' % self.instance_id)
        # Command removed by RB 3/17/17 -- doesn't actually clear shared memory and just clutters the console with unnecessary output
        # print 'not cleaning status memory -- fix this'

        # Clear shared memory segments
        # command = "hashpipe_clean_shmem -I %d" % (self.instance_id)
        # ps_clean = subprocess.Popen(command.split())
        # ps_clean.wait()

    def reformat_data_buffers(self, mode):
        """
        Since the vegas and guppi HPC programs require different data buffer
        layouts, remove and re-create the databuffers as appropriate for the
        HPC program in use.
        """
        if self.simulate:
            return
        if self.current_mode is None:
            raise Exception("No current mode is selected")

        fmt_path = self.dibas_dir + '/bin/re_create_data_buffers'

        hpc_program = self.mode_data[self.current_mode].hpc_program
        if hpc_program is None:
            raise Exception(
                "Configuration error: no field hpc_program specified in "
                "MODE section of %s " % (self.current_mode))

        #mem_fmt_process = subprocess.Popen((fmt_path,hpc_program))
        print "reformatting data buffers"
        os.system(fmt_path + ' ' + hpc_program)

    def get_bank_name(self, config):
        """Dive into the config file and fetch the bank name based on the
        current host running this bank.

        """
        banks = [p for p in config.sections() if 'BANK' in p]
        host = socket.gethostname()

        for i in banks:
            hpchost = config.get(i, 'hpchost')
            # either 'host' or 'hpchost' or both should contain the base
            # host name ('hpc1', 'hpc2', etc.) Just in case an alternate
            # name is used by either (such as 'hpc1-1'), compare them
            # both ways.
            if host in hpchost or hpchost in host:
                return i.upper()

    def bank2inst(self, bank):
        """
        Converts either the string or char bank name to an integer
        'instance id' for use with hashpipe and shared memory.
        Ex: 'BANKC' -> 3
        Ex: 'D' -> 4
        """
        if len(bank) > 1:
            # BANKC -> C
            bank = bank[-1]
        return ord(bank) - ord('A')

    def read_config_file(self, filename):
        """
        read_config_file(filename)

        Reads the config file 'filename' and loads the values into data
        structures in memory. 'filename' should be a fully qualified
        filename. The config file contains a 'bank' section of interest to
        this bank; in addition, it contains any number of 'MODEX' sections,
        where 'X' is a mode name/number.
        """

        try:
            config = ConfigParser.ConfigParser()
            config.readfp(open(filename))

            if not self.bank_name:
                self.bank_name = self.get_bank_name(config)

                if not self.bank_name:
                    sys.exit(0)

            bank = self.bank_name
            print "bank =", bank, "filename =", filename

            # Read general stuff:
            telescope = config.get(
                'DEFAULTS',
                'telescope').lstrip().rstrip().lstrip('"').rstrip('"')
            self.set_status(TELESCOP=telescope)

            # Read the HPC MAC addresses
            macs = config.items('HPCMACS')
            self.hpc_macs = {}

            for i in macs:
                #key = _ip_string_to_int(_hostname_to_ip(i[0])) & 0xFF
                #self.hpc_macs[key] = int(i[1], 16)
                # Change made due to different python version (At least I think that is the problem). Debugging was done with these lines in ipython - Mark R.
                key = _ip_string_to_int(i[0]) & 0xFF
                self.hpc_macs[key] = int(i[1], 16)

            # Get all bank data and store it. This is needed by any mode
            # where there is 1 ROACH and N Players & HPC programs
            banks = [s for s in config.sections() if 'BANK' in s]

            for bank in banks:
                b = BankData()
                b.load_config(config, bank)
                self.banks[bank] = b

            # Get config info on this bank's ROACH2. Normally there is 1
            # ROACH per Player/HPC node, so this is it.
            self.bank_data = self.banks[self.bank_name]
            self.instance_id = self.bank2inst(
                self.bank_name)  #self.bank_data.instance_id

            # Get config info on all modes
            modes = [s for s in config.sections() if 'MODE' in s]

            for mode in modes:
                m = ModeData()

                try:
                    m.load_config(config, mode)
                except Exception, e:
                    if self.simulate:
                        pass
                    else:
                        raise e

                self.mode_data[mode] = m

        except ConfigParser.NoSectionError as e:
            print str(e)
            return str(e)

        # Now that all the configuration data is loaded, set up some
        # basic things: KATCP, Valon, etc.  Not all backends will
        # have/need katcp & valon, so it config data says no roach &
        # valon, these steps will not happen.
        self.valon = None
        self.roach = None

        if not self.simulate and self.bank_data.has_roach:
            self.roach = katcp_wrapper.FpgaClient(self.bank_data.katcp_ip,
                                                  self.bank_data.katcp_port,
                                                  timeout=30.0)
            time.sleep(
                1
            )  # It takes the KATCP interface a little while to get ready. It's used below
            # by the Valon interface, so we must wait a little.

            # The Valon can be on this host ('local') or on the ROACH
            # ('katcp'), or None. Create accordingly.
            if self.bank_data.synth == 'local':
                import valon_synth
                self.valon = valon_synth.Synthesizer(self.bank_data.synth_port)
            elif self.bank_data.synth == 'katcp':
                from valon_katcp import ValonKATCP
                self.valon = ValonKATCP(self.roach, self.bank_data.synth_port)

            # Valon is now assumed to be working
            if self.valon:
                self.valon.set_ref_select(self.bank_data.synth_ref)
                self.valon.set_reference(self.bank_data.synth_ref_freq)
                self.valon.set_vco_range(0, *self.bank_data.synth_vco_range)
                self.valon.set_rf_level(0, self.bank_data.synth_rf_level)
                self.valon.set_options(0, *self.bank_data.synth_options)

            print "connecting to %s, port %i" % (self.bank_data.katcp_ip,
                                                 self.bank_data.katcp_port)
        print self.bank_data
        return "config file loaded."

    def set_scan_number(self, num):
        """
        set_scan_number(scan_number)

        Sets the scan number to the value specified
        """
        self.scan_number = num
        self.set_status(SCANNUM=num)
        self.set_status(SCAN=num)

    def increment_scan_number(self):
        """
        increment_scan_number()

        Increments the current scan number
        """
        self.scan_number = self.scan_number + 1
        self.set_scan_number(self.scan_number)

    def set_status(self, **kwargs):
        """
        set_status(self, **kwargs)

        Updates the values for the keys specified in the parameter list
        as keyword value pairs. So:

          set_status(PROJID='JUNK', OBS_MODE='HBW')

        would set those two parameters.
        """
        if self.backend is not None:
            self.backend.write_status(**kwargs)

    def get_status(self, keys=None):
        """
        get_status(keys=None)

        Returns the specified key's value, or the values of several
        keys, or the entire contents of the shared memory status buffer.

        'keys' == None: The entire buffer is returned, as a
        dictionary containing the key/value pairs.

        'keys' is a list of keys, which are strings: returns a dictionary
        containing the requested subset of key/value pairs.

        'keys' is a single string: a single value will be looked up and
        returned using 'keys' as the single key.
        """
        if self.backend is not None:
            return self.backend.get_status(keys)
        else:
            return None

    def list_modes(self):
        modes = self.mode_data.keys()
        modes.sort()
        return modes

    def set_cov_mode(self, n):

        if n == 1:

            string = 'HI'
            cmd = 'hashpipe_check_status -k COVMODE -s %s' % string
            print 'Writing mode into shared memory: %s' % cmd
            os.system(cmd)

        elif n == 2:
            string = 'PAF_CAL'
            cmd = 'hashpipe_check_status -k COVMODE -s %s' % string
            print 'Writing mode into shared memory: %s' % cmd
            os.system(cmd)

        elif n == 3:
            string = 'FRB'
            cmd = 'hashpipe_check_status -k COVMODE -s %s' % string
            print 'Writing mode into shared memory: %s' % cmd
            os.system(cmd)

        else:

            string = 'NONE'
            print 'Correct mode not found'
            cmd = 'hashpipe_check_status -k COVMODE -f %s' % string
            print 'Writing mode into shared memory: %s' % cmd
            os.system(cmd)

    def set_mode(self, mode, bandwidth=None, force=False):
        """set_mode(mode, bandwidth = None, force=False)

        Sets the operating mode for the roach.  Does this by programming
        the roach.

        *mode:*
          A string; A keyword which is one of the '[MODEX]'
          sections of the configuration file, which must have been loaded
          earlier.

        *bandwidth:*
          The valon bandwidth for this new mode. If not
          specified the last value used will be reused. If no value was
          ever provided the config file value will be used.

        *force:*
          A boolean flag; if 'True' and the new mode is the same as
          the current mode, the mode will be reloaded. It is set to
          'False' by default, in which case the new mode will not be
          reloaded if it is already the current mode.

        Returns a tuple consisting of (status, 'msg') where 'status' is
        a boolean, 'True' if the mode was loaded, 'False' otherwise; and
        'msg' explains the error if any.

        Example::

            s, msg = f.set_mode('MODE1') s, msg =
            f.set_mode(mode='MODE1', force=True)

        """
        frequency = bandwidth
        if mode:
            if mode in self.mode_data:
                if force or mode != self.current_mode or frequency != self.mode_data[
                        mode].frequency:
                    #self.check_shared_memory()
                    print "New mode specified and/or bandwidth specified!"
                    if self.current_mode:
                        old_hpc_program = self.mode_data[
                            self.current_mode].hpc_program
                    else:
                        old_hpc_program = "none"

                    self.current_mode = mode
                    new_hpc_program = self.mode_data[mode].hpc_program

                    if (mode == 'MODE42'):
                        self.set_cov_mode(1)
                    if (mode == 'MODE44'):
                        self.set_cov_mode(2)
                    if (mode == 'MODE45'):
                        self.set_cov_mode(3)

                    if old_hpc_program != new_hpc_program:
                        #if 0:
                        self.clear_shared_memory()
                        #self.reformat_data_buffers(mode)
                    else:
                        print 'Not reformatting buffers'

                    # The correct Backend type will be chosen from the
                    # dictionary of available Backend types self.BET,
                    # based on the MODENAME key in the MODE
                    # sections. The modes include VEGAS-style spectral
                    # modes, and GUPPI-style pulsar modes. The spectral
                    # modes are further divided into HBW and LBW modes,
                    # and the GUPPI modes into coherent dedispersion
                    # (CDD) and incoherent mode. CDD modes are
                    # characterised by having only 1 ROACH sending data
                    # to 8 different HPC servers. The ROACH has 8
                    # network adapters for the purpose. Because of the
                    # 1->8 arrangement, one of the Players is Master and
                    # programs the ROACH. The others set up everything
                    # except their ROACH; in fact the others deprogram
                    # their ROACH so that the IP addresses may be used
                    # in the one CDD ROACH.
                    #
                    # The other kind of mode has 1 ROACH -> 1 HPC
                    # server, so each Player programs its ROACH.

                    # based upon the mode's backend config setting, create the appropriate
                    # parameter calculator 'backend'
                    if self.backend is not None:
                        self.backend.cleanup()
                        print "set_mode(%s): cleaned up old backend." % mode
                        del (self.backend)
                        self.backend = None

                    # If 'frequency' is provided record in mode data
                    # for use and reuse if not subsequently
                    # provided. Initial value of mode data frequency
                    # is from config file.
                    if frequency:
                        self.mode_data[mode].frequency = frequency

                    # get backend type based on backend name: h1k, h16k, l1/lbw1, etc.
                    Backend = self.BET[
                        self.mode_data[mode].backend_name.lower()]
                    print "set_mode(%s): Creating new %s" % (mode,
                                                             Backend.__name__)
                    # instantiate our new backend:
                    self.backend = Backend(self.bank_data,
                                           self.mode_data[mode], self.roach,
                                           self.valon, self.hpc_macs,
                                           self.simulate)
                    print "set_mode(%s): beginning wait for DAQ program" % mode
                    self.backend._wait_for_status('DAQSTATE', 'stopped',
                                                  timedelta(seconds=1))
                    print "set_mode(%s): wait for DAQ program ended." % mode

                    if self.simulate:
                        time.sleep(10)  # make it realistic

                    return (True, 'New mode %s set!' % mode)
                else:
                    return (
                        False,
                        'Mode %s is already set! Use \'force=True\' to force.'
                        % mode)
            else:
                return (False, 'Mode %s is not supported.' % mode)
        else:
            return (False, "Mode is 'None', doing nothing.")

    def get_mode(self):
        """
        get_mode()

        Returns the current operating mode for the bank.
        """
        return self.current_mode

    def write_cmd(self, string):

        if self.backend:
            print string
            self.backend.hpc_cmd(string)
            self.backend.fits_writer_cmd(string)

        if string == 'QUIT':
            os.system('clean_ipc')

    def startin(self, inSecs, durSecs):
        "An alternative method for running a scan, only available for Beamformer backend."
        mode = self.get_mode()
        assert self.current_mode == mode
        print "Current mode is", self.current_mode
        if self.backend:  # should modify to check equal to beamformerbackend type because other backends donn't have a startin function defined
            self.backend.startin(inSecs, durSecs)

    def start(self, starttime=None):
        """
        start(self, starttimeelf= None)

        starttime: a datetime object representing a start time, in UTC

        --OR--

        starttime: a tuple or list(for ease of JSON serialization) of
        datetime compatible values: (year, month, day, hour, minute,
        second, microsecond), UTC.

        Sets up the system for a measurement and kicks it off at the
        appropriate time, based on 'starttime'.  If 'starttime' is not
        on a PPS boundary it is bumped up to the next PPS boundary.  If
        'starttime' is not given, the earliest possible start time is
        used.

        start() may require a needed arm delay time, which is specified
        in every mode section of the configuration file as
        'needed_arm_delay'. During this delay it tells the HPC program
        to start its net, accum and disk threads, and waits for the HPC
        program to report that it is receiving data. It then calculates
        the time it needs to sleep until just after the penultimate PPS
        signal. At that time it wakes up and arms the ROACH. The ROACH
        should then send the initial packet at that time.

        If a start time is specified that cannot be met an Exception is
        thrown with a message stating the problem.
        """

        if self.backend:
            #     self.increment_scan_number()
            print starttime
            return self.backend.start(starttime)

    def monitor(self):
        """monitor(self)

        monitor() requests that the DAQ program go into monitor
        mode. This is handy for troubleshooting issues like no data. In
        monitor mode the DAQ's net thread starts receiving data but does
        not do anything with that data. However the thread's status may
        be read in the status memory: NETSTAT will say 'receiving' if
        packets are arriving, 'waiting' if not.

        """

        if self.backend:
            return self.backend.monitor()
        else:
            return (False, "No backend selected!")

    def stop(self):
        """
        Stops a running scan, by telling the current backend to stop.
        """

        if self.backend:
            return self.backend.stop()
        else:
            return (False, "No backend selected!")

    def scan_status(self):
        """
        scan_status(self):

        Returns the state of currently running scan. The return type is
        a tuple, backend dependent.
        """

        if self.backend:
            return self.backend.scan_status()
        else:
            return (False, "No backend selected!")

    def earliest_start(self):
        if self.backend:
            return (True, datetime_to_tuple(self.backend.earliest_start()))
        else:
            return (False, "No backend selected!")

    def set_param(self, **kvpairs):
        """
        A pass-thru method which conveys a backend specific parameter to the modes parameter engine.

        Example usage:
        set_param(exposure=x,switch_period=1.0, ...)
        """

        if self.backend is not None:
            for k, v in kvpairs.items():
                return self.backend.set_param(str(k), v)
        else:
            raise Exception("Cannot set parameters until a mode is selected")

    def help_param(self, name=None):
        """
        A pass-thru method which conveys a backend specific parameter to the modes parameter engine.

        Example usage::

          help_param(exposure)
        """

        if self.backend is not None:
            return self.backend.help_param(name)
        else:
            raise Exception("Cannot set parameters until a mode is selected")

    def get_param(self, name=None):
        """A pass-thru method which gets the values of a backend specific parameter.

        Example usage::

          get_param(exposure)

        """

        if self.backend is not None:
            return self.backend.get_param(name)
        else:
            raise Exception("Cannot get parameters until a mode is selected")

    def prepare(self):
        """
        Perform calculations for the current set of parameter settings
        """
        if self.backend is not None:
            self.backend.prepare()
        else:
            raise Exception("Cannot prepare until a mode is selected")

    def reset_roach(self):
        """
        reset_roach(self):

        Sends a sequence of commands to reset the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """

        if self.backend:
            self.backend.reset_roach()

    def arm_roach(self):
        """
        arm_roach(self):

        Sends a sequence of commands to arm the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """
        if self.backend:
            self.backend.arm_roach()

    def disarm_roach(self):
        """
        disarm_roach(self):

        Sends a sequence of commands to disarm the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """
        if self.backend:
            self.backend.disarm_roach()

    def clear_switching_states(self):
        """
        resets/deletes the switching_states (backend dependent)
        """
        if self.backend:
            return self.backend.clear_switching_states()
        else:
            raise Exception(
                "Cannot clear switcvhing states until a mode has been selected"
            )

    def add_switching_state(self,
                            duration,
                            blank=False,
                            cal=False,
                            sig_ref_1=False):
        """add_switching_state(duration, blank, cal, sig):

        Add a description of one switching phase (backend dependent).

        *duration*
          the length of this phase in seconds,
        *blank*
          the state of the blanking signal (True = blank, False = no blank)
        *cal*
          the state of the cal signal (True = cal, False = no cal)
        *sig*
          the state of the sig_ref signal (True = ref, false = sig)

        Example to set up a 8 phase signal (4-phase if blanking is not
        considered) with blanking, cal, and sig/ref, total of 400 mS::

          be = Backend(None) # no real backend needed for example
          be.clear_switching_states()
          be.add_switching_state(0.01, blank = True, cal = True, sig = True)
          be.add_switching_state(0.09, cal = True, sig = True)
          be.add_switching_state(0.01, blank = True, cal = True)
          be.add_switching_state(0.09, cal = True)
          be.add_switching_state(0.01, blank = True, sig = True)
          be.add_switching_state(0.09, sig = True)
          be.add_switching_state(0.01, blank = True)
          be.add_switching_state(0.09)

        """
        if self.backend:
            return self.backend.add_switching_state(duration, blank, cal,
                                                    sig_ref_1)
        else:
            raise Exception(
                "Cannot add switching states until a mode has been selected.")

    def set_gbt_ss(self, period, ss_list):
        """set_gbt_ss(period, ss_list):

        adds a complete GBT style switching signal description.

        *period*
          The complete period length of the switching signal.
        *ss_list*
          A list of GBT phase components. Each component is a tuple:
          (phase_start, sig_ref, cal, blanking_time) There is one of
          these tuples per GBT style phase.

        Example::

            b.set_gbt_ss(period = 0.1,
                         ss_list = ((0.0, SWbits.SIG, SWbits.CALON, 0.025),
                                    (0.25, SWbits.SIG, SWbits.CALOFF, 0.025),
                                    (0.5, SWbits.REF, SWbits.CALON, 0.025),
                                    (0.75, SWbits.REF, SWbits.CALOFF, 0.025))
                        )

        """
        if self.backend:
            return self.backend.set_gbt_ss(period, ss_list)
        else:
            raise Exception(
                "Cannot set switching states until a mode has been selected.")

    def _watchdog(self):
        """
        Looks at internal conditions every so often.
        """

        if self.backend:
            # Monitor scan status
            if self.backend.scan_running:
                now = datetime.utcnow()
                start = self.backend.start_time
                scanlength = self.backend.scan_length + 1

                if all((start, scanlength)):
                    sl = timedelta(seconds=scanlength)
                    rem = (start + sl) - now
                    # lets have a little scan countdown in status memory
                    self.set_status(SCANREM=rem.seconds - 1)

                    if now > start + sl:

                        print np.str(scanlength)
                        isDone = False
                        ctr = 0
                        while not isDone:
                            netStatus = self.get_status("NETSTAT")
                            if netStatus == "IDLE":
                                isDone = True
                            else:
                                time.sleep(.25)
                                ctr += 1

                            if ctr >= 20:
                                print "BFBE: Something froze???..."
                                isDone = True

                        self.stop()

                        self.set_status(SCANREM='scan terminated')
Example #4
0
    def read_config_file(self, filename):
        """
        read_config_file(filename)

        Reads the config file 'filename' and loads the values into data
        structures in memory. 'filename' should be a fully qualified
        filename. The config file contains a 'bank' section of interest to
        this bank; in addition, it contains any number of 'MODEX' sections,
        where 'X' is a mode name/number.
        """

        try:
            config = ConfigParser.ConfigParser()
            config.readfp(open(filename))

            if not self.bank_name:
                self.bank_name = self.get_bank_name(config)

                if not self.bank_name:
                    sys.exit(0)

            bank = self.bank_name
            print "bank =", bank, "filename =", filename

            # Read general stuff:
            telescope = config.get(
                'DEFAULTS',
                'telescope').lstrip().rstrip().lstrip('"').rstrip('"')
            self.set_status(TELESCOP=telescope)

            # Read the HPC MAC addresses
            macs = config.items('HPCMACS')
            self.hpc_macs = {}

            for i in macs:
                #key = _ip_string_to_int(_hostname_to_ip(i[0])) & 0xFF
                #self.hpc_macs[key] = int(i[1], 16)
                # Change made due to different python version (At least I think that is the problem). Debugging was done with these lines in ipython - Mark R.
                key = _ip_string_to_int(i[0]) & 0xFF
                self.hpc_macs[key] = int(i[1], 16)

            # Get all bank data and store it. This is needed by any mode
            # where there is 1 ROACH and N Players & HPC programs
            banks = [s for s in config.sections() if 'BANK' in s]

            for bank in banks:
                b = BankData()
                b.load_config(config, bank)
                self.banks[bank] = b

            # Get config info on this bank's ROACH2. Normally there is 1
            # ROACH per Player/HPC node, so this is it.
            self.bank_data = self.banks[self.bank_name]
            self.instance_id = self.bank2inst(
                self.bank_name)  #self.bank_data.instance_id

            # Get config info on all modes
            modes = [s for s in config.sections() if 'MODE' in s]

            for mode in modes:
                m = ModeData()

                try:
                    m.load_config(config, mode)
                except Exception, e:
                    if self.simulate:
                        pass
                    else:
                        raise e

                self.mode_data[mode] = m

        except ConfigParser.NoSectionError as e:
            print str(e)
            return str(e)

        # Now that all the configuration data is loaded, set up some
        # basic things: KATCP, Valon, etc.  Not all backends will
        # have/need katcp & valon, so it config data says no roach &
        # valon, these steps will not happen.
        self.valon = None
        self.roach = None

        if not self.simulate and self.bank_data.has_roach:
            self.roach = katcp_wrapper.FpgaClient(self.bank_data.katcp_ip,
                                                  self.bank_data.katcp_port,
                                                  timeout=30.0)
            time.sleep(
                1
            )  # It takes the KATCP interface a little while to get ready. It's used below
            # by the Valon interface, so we must wait a little.

            # The Valon can be on this host ('local') or on the ROACH
            # ('katcp'), or None. Create accordingly.
            if self.bank_data.synth == 'local':
                import valon_synth
                self.valon = valon_synth.Synthesizer(self.bank_data.synth_port)
            elif self.bank_data.synth == 'katcp':
                from valon_katcp import ValonKATCP
                self.valon = ValonKATCP(self.roach, self.bank_data.synth_port)

            # Valon is now assumed to be working
            if self.valon:
                self.valon.set_ref_select(self.bank_data.synth_ref)
                self.valon.set_reference(self.bank_data.synth_ref_freq)
                self.valon.set_vco_range(0, *self.bank_data.synth_vco_range)
                self.valon.set_rf_level(0, self.bank_data.synth_rf_level)
                self.valon.set_options(0, *self.bank_data.synth_options)

            print "connecting to %s, port %i" % (self.bank_data.katcp_ip,
                                                 self.bank_data.katcp_port)
        print self.bank_data
        return "config file loaded."
Example #5
0
class ADCCalibrations:
    def __init__(self,
                 test=False,
                 conf_dir=None,
                 data_dir=None,
                 roaches=None,
                 mmcm_trials=None,
                 ogp_trials=None,
                 test_tone=None,
                 ampl=None,
                 now=None,
                 manual=False,
                 do_ogps=True,
                 do_mmcms=True,
                 gpib_addr=None):

        self.test = test
        self.now = now
        self.conf_dir = conf_dir if conf_dir is not None else '.'
        self.data_dir = data_dir if data_dir is not None else '.'
        self.roaches = roaches if roaches is not None else self.get_roach_names_from_config(
        )
        self.banks = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
        self.manual = manual

        self.do_mmcms = do_mmcms
        self.do_ogps = do_ogps
        if not self.do_ogps and not self.do_mmcms:
            raise Exception("One of do_ogps and do_mmcms must be True")

        # mmcms
        self.mmcm_trials = mmcm_trials if mmcm_trials is not None else 5
        self.mmcm_tolerance = 4

        # ogps/inls
        self.ogp_bof = 'h1k_ver106_2014_Apr_11_1612.bof'
        self.testfreq = test_tone if test_tone is not None else 18.3105  # MHz
        self.ampl = ampl if ampl is not None else -3
        self.ogp_trials = ogp_trials if ogp_trials is not None else 10
        self.gpibaddr = gpib_addr if gpib_addr is not None else '10.16.96.174'  # tape room

        # helper classes
        self.cp = ConfigParser.ConfigParser()
        self.roach = None
        self.valon = None
        self.cal = None
        self.adcConf = None

    def find_all_calibrations(self):
        if self.do_mmcms:
            self.find_all_mmcms()
        if self.do_ogps:
            self.find_all_ogps()

    def find_all_ogps(self):
        for r in self.roaches:
            self.find_ogps(r)

    def find_all_mmcms(self):
        for r in self.roaches:
            self.find_mmcms(r)

    def get_roach_names_from_config(self):
        "Determines what roaches to connect to from the vegas.conf file"

        fn = "%s/%s" % (self.conf_dir, "vegas.conf")
        r = cp.read(fn)
        if len(r) == 0:
            print "Could not find roach names from: ", fn
            return []

        # what banks to read?
        sec = "DEFAULTS"
        subsys = self.cp.get(sec, "subsystems")
        subsys = [int(s) for s in subsys.split(',')]

        # what roaches corresopond to those banks?
        roaches = []
        for s in subsys:
            bank = self.banks[s - 1]
            sec = "BANK%s" % bank
            # roach_host = vegasr2-1.gb.nrao.edu
            roaches.append(cp.get(sec, "roach_host").split('.')[0])
        return roaches

    def get_adc_config_filename(self, roach_name):
        fn = "%s/%s-adc.conf" % (self.conf_dir, roach_name)
        logger.info("MMCM config file: %s" % fn)
        return fn

    def init_for_roach(self, roach_name):
        "Inits the helper classes we use to interact w/ the given roach name"

        # connect to roach
        tmsg = 'Connecting to %s' % roach_name
        logger.info(tmsg)
        if not self.test:
            self.roach = corr.katcp_wrapper.FpgaClient(roach_name)
            time.sleep(1)
            if not self.roach.is_connected():
                raise Exception("Cannot connect to %s" % roach_name)

        # we'll need this to change the frequency
        valonSerial = "/dev/ttyS1"  # this should never change
        if not self.test:
            self.valon = ValonKATCP(self.roach, valonSerial)

        # this is the object that will find the MMCM value
        self.cal = ADCCalibrate(dir=self.data_dir,
                                roach_name=roach_name,
                                gpib_addr=self.gpibaddr,
                                roach=self.roach,
                                now=self.now,
                                test=self.test)

        # read the config file and find the mmcm  through each mode
        fn = self.get_adc_config_filename(roach_name)
        self.adcConf = ADCConfFile(fn)

    def find_ogps(self, roach_name):

        self.init_for_roach(roach_name)

        for frq, value in self.adcConf.ogp_info.items():
            #i, ogp0, ogp1 = value
            # determine the OGP values for this clockrate
            tmsg = "Finding OGPs for clockrate: %s" % frq
            logger.info(tmsg)
            ogp0, ogp1 = self.find_this_ogp(frq)
            if ogp0 is not None and ogp1 is not None:
                self.adcConf.write_ogps(frq, 0, ogp0)
                self.adcConf.write_ogps(frq, 1, ogp1)

        self.adcConf.write_to_file()

        # the INLs don't change w/ either bof, or clockrate,
        # so now that this roach is in a suitable state (it's
        # MMCM and OGP calibrations are done), let's do one INL
        for z in range(2):
            self.cal.do_inl(z)
            self.adcConf.write_inls(z, self.cal.inl.inls)
        self.adcConf.write_to_file()

    def change_bof(self, bof):

        if self.test:
            return

        # switch to the given bof file
        self.roach.progdev(bof)
        tmsg = "Roach BOF file set to: %s" % bof
        logger.info(tmsg)
        time.sleep(2)

    def change_frequency(self, freq):
        "If necessary, use the Valon Synth to change the Roach board clockrate."

        if self.test:
            return

        # we also need to switch to the given frequency
        valonSynth = 0  # neither should this (0: A, 8: B)
        current_clkrate = self.valon.get_frequency(valonSynth)
        tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate
        clkrate = freq / 1e6  # Hz -> MHz
        logger.info(tmsg)
        if abs(current_clkrate - clkrate) > 0.001:
            self.valon.set_frequency(valonSynth, clkrate)
            time.sleep(1)
            current_clkrate = self.valon.get_frequency(valonSynth)
            tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate
            logger.info(tmsg)

        # in addition, this class uses clockrate for ogps
        self.cal.set_clockrate(clkrate)

    def find_this_ogp(self, freq):

        # we may not need to do this, but its probably safer.
        # OGPs don't change w/ bof file, so this is arbitrary
        self.change_bof(self.ogp_bof)

        # but OGPs *do* change with clock rate
        self.change_frequency(freq)

        # since we reprogrammed the roach, mmcm calibrate
        self.cal.do_mmcm(2)

        # TBF: do this once manually at the beginning?
        if not self.cal.gpib_test(
                2, self.testfreq, self.ampl, manual=self.manual):
            logger.info("canceling find_this_ogp for frequency %s" % freq)
            return None, None

        # now find the ogps
        self.cal.do_ogp(0, self.testfreq, self.ogp_trials)
        ogp0 = self.cal.ogp.ogps
        self.cal.do_ogp(1, self.testfreq, self.ogp_trials)
        ogp1 = self.cal.ogp.ogps

        return ogp0, ogp1

    def find_mmcms(self, roach_name):
        """
        For the given roach, determine all the MMCM optimal phase values
        for all the different combinations of bof file and frequency.
        """

        self.init_for_roach(roach_name)

        for key, value in self.adcConf.mmcm_info.items():
            bof, frq = key
            i, adc0, adc1, _ = value
            # determine the MMCM optimal phase values for this combo
            # of bof file and frequency
            adc0s = []
            adc1s = []
            for trial in range(self.mmcm_trials):
                adc0, adc1 = self.find_this_mmcm(
                    bof, frq)  #v, cal, roach, bof, frq)
                tmsg = "Found ADC mmcm's for trial %d: %s, %s" % (trial, adc0,
                                                                  adc1)
                print tmsg
                logger.info(tmsg)
                if self.has_adc_difference(adc0, adc1, adc0s, adc1s):
                    adc0s.append(adc0)
                    adc1s.append(adc1)

            self.adcConf.write_mmcms(bof, frq, 0, adc0s)
            self.adcConf.write_mmcms(bof, frq, 1, adc1s)

        self.adcConf.write_to_file()

    def has_adc_difference(self, adc0, adc1, adc0s, adc1s):
        assert len(adc0s) == len(adc1s)
        if len(adc0s) == 0:
            return True
        has_diff = True
        adcs = zip(adc0s, adc1s)
        t = self.mmcm_tolerance
        for i in range(len(adc0s)):
            #if ((abs(adc0s[i] - adc0) < tolerance) and (abs(adc1s[i] - adc1) < tolerance)):
            if self.is_within_tolerance(adc0, adc0s[i], t) \
                and self.is_within_tolerance(adc1, adc1s[i], t):
                has_diff = False
        return has_diff

    def is_within_tolerance(self, x, y, tolerance):
        # they are both None
        if x is None and y is None:
            return True
        # one of them is None and the other isn't
        if x is None or y is None:
            return True  # if this were False we'd get None's written to .conf!
        # none of them are none
        return abs(x - y) < tolerance

    def find_this_mmcm(self, bof, freq):  #valon, adcCal, roach, bof, freq):
        """
        Sets the given bof file and frequency (Hz) for a roach board using
        the given valon and ADCCalibration objects, returns the ADCs'
        MMCM optimal phase results.
        """

        # switch to the given bof file
        self.change_bof(bof)

        # we also need to switch to the given frequency
        self.change_frequency(freq)

        # Now actually find the MMCM optimal phase for each ADC
        set_phase = False  # TBF?
        self.cal.set_zdok(0)
        adc0, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase=set_phase)
        tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (0, adc0, g)
        logger.info(tmsg)
        self.cal.set_zdok(1)
        adc1, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase=set_phase)
        tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (1, adc1, g)
        logger.info(tmsg)

        return adc0, adc1
Example #6
0
class Bank(object):
    """
    A roach bank manager class.
    """

    def __init__(self, bank_name = None, simulate = False):

        print "Bank!"

        # Set up a dictionary of BackEnd Types. This will be used to
        # create the correct backend based on the MODENAME key in the
        # MODE sections of the configuration file:
        self.BET = {"h1k"        : VegasHBWBackend.VegasHBWBackend,
                    "h16k"       : VegasHBWBackend.VegasHBWBackend,
                    "l1/lbw1"    : VegasL1LBWBackend.VegasL1LBWBackend,
                    "l8/lbw1"    : VegasL8LBWBackend.VegasL8LBWBackend,
                    "l8/lbw8"    : VegasL8LBWBackend.VegasL8LBWBackend,
                    "guppi-inco" : GuppiBackend.GuppiBackend,
                    "guppi-codd" : GuppiCODDBackend.GuppiCODDBackend,
                    "beamformer" : BeamformerBackend.BeamformerBackend}

        self.dibas_dir = os.getenv('DIBAS_DIR')

        if self.dibas_dir == None:
            raise Exception("'DIBAS_DIR' is not set!")
        self.backend = None
        self.roach = None
        self.valon = None
        self.hpc_macs = {}
        self.simulate = simulate
        print "self.simulate=", self.simulate

        # Bank name may be provided, or if not will be inferred from the config file.
        self.bank_name = bank_name.upper() if bank_name else None
        self.bank_data = BankData()
        self.mode_data = {}
        self.banks = {}
        self.current_mode = None
        self.check_shared_memory()
        self.read_config_file(self.dibas_dir + '/etc/config/dibas.conf')
        self.scan_number = 1

        # This turns on the automatic reaping of dead child processes (anti-zombification)
        signal.signal(signal.SIGCHLD, signal.SIG_IGN)

        #print "setting backend:"
        #self.backend = VegasBackend.Backend(self.bank_data, self.mode_data['MODE1'])

        #print "status mem: ", self.backend.get_status()

    def __del__(self):
        """
        Perform cleanup activities for a Bank object.
        """
        pass

    def check_shared_memory(self):
        """
        This method creates the status shared memory segment if necessary.
        If the segment exists, the state of the status memory is not modified.
        """
        if not self.simulate:
            os.system(self.dibas_dir + '/bin/init_status_memory')


    def clear_shared_memory(self):
        """
        This method clears the status shared memory segment if necessary.
        """
        if not self.simulate:
            os.system(self.dibas_dir + '/bin/x86_64-linux/check_vegas_status -C')
        # print 'not cleaning status memory -- fix this'

    def reformat_data_buffers(self, mode):
        """
        Since the vegas and guppi HPC programs require different data buffer
        layouts, remove and re-create the databuffers as appropriate for the
        HPC program in use.
        """
        if self.simulate:
            return
        if self.current_mode is None:
            raise Exception( "No current mode is selected" )

        fmt_path = self.dibas_dir + '/bin/re_create_data_buffers'

        hpc_program = self.mode_data[self.current_mode].hpc_program
        if hpc_program is None:
            raise Exception("Configuration error: no field hpc_program specified in "
                            "MODE section of %s " % (self.current_mode))

        #mem_fmt_process = subprocess.Popen((fmt_path,hpc_program))
        print "reformatting data buffers"
        os.system(fmt_path + ' ' + hpc_program)

    def get_bank_name(self, config):
        """Dive into the config file and fetch the bank name based on the
        current host running this bank.

        """
        banks = [p for p in config.sections() if 'BANK' in p]
        host = socket.gethostname()

        for i in banks:
            hpchost = config.get(i, 'hpchost')
            # either 'host' or 'hpchost' or both should contain the base
            # host name ('hpc1', 'hpc2', etc.) Just in case an alternate
            # name is used by either (such as 'hpc1-1'), compare them
            # both ways.
            if host in hpchost or hpchost in host:
                return i.upper()

    def read_config_file(self, filename):
        """
        read_config_file(filename)

        Reads the config file 'filename' and loads the values into data
        structures in memory. 'filename' should be a fully qualified
        filename. The config file contains a 'bank' section of interest to
        this bank; in addition, it contains any number of 'MODEX' sections,
        where 'X' is a mode name/number.
        """

        try:
            config = ConfigParser.ConfigParser()
            config.readfp(open(filename))

            if not self.bank_name:
                self.bank_name = self.get_bank_name(config)

                if not self.bank_name:
                    sys.exit(0)

            bank = self.bank_name
            print "bank =", bank, "filename =", filename

            # Read general stuff:
            telescope = config.get('DEFAULTS', 'telescope').lstrip().rstrip().lstrip('"').rstrip('"')
            self.set_status(TELESCOP=telescope)

            # Read the HPC MAC addresses
            macs = config.items('HPCMACS')
            self.hpc_macs = {}

            for i in macs:
                key = _ip_string_to_int(_hostname_to_ip(i[0])) & 0xFF
                self.hpc_macs[key] = int(i[1], 16)

            # Get all bank data and store it. This is needed by any mode
            # where there is 1 ROACH and N Players & HPC programs
            banks = [s for s in config.sections() if 'BANK' in s]

            for bank in banks:
                b = BankData()
                b.load_config(config, bank)
                self.banks[bank] = b

            # Get config info on this bank's ROACH2. Normally there is 1
            # ROACH per Player/HPC node, so this is it.
            self.bank_data = self.banks[self.bank_name]

            # Get config info on all modes
            modes = [s for s in config.sections() if 'MODE' in s]

            for mode in modes:
                m = ModeData()

                try:
                    m.load_config(config, mode)
                except Exception, e:
                    if self.simulate:
                        pass
                    else:
                        raise e

                self.mode_data[mode] = m

        except ConfigParser.NoSectionError as e:
            print str(e)
            return str(e)

        # Now that all the configuration data is loaded, set up some
        # basic things: KATCP, Valon, etc.  Not all backends will
        # have/need katcp & valon, so it config data says no roach &
        # valon, these steps will not happen.
        self.valon = None
        self.roach = None

        if not self.simulate and self.bank_data.has_roach:
            self.roach = katcp_wrapper.FpgaClient(self.bank_data.katcp_ip,
                                                  self.bank_data.katcp_port,
                                                  timeout = 30.0)
            time.sleep(1) # It takes the KATCP interface a little while to get ready. It's used below
                          # by the Valon interface, so we must wait a little.

            # The Valon can be on this host ('local') or on the ROACH
            # ('katcp'), or None. Create accordingly.
            if self.bank_data.synth == 'local':
                import valon_synth
                self.valon = valon_synth.Synthesizer(self.bank_data.synth_port)
            elif self.bank_data.synth == 'katcp':
                from valon_katcp import ValonKATCP
                self.valon = ValonKATCP(self.roach, self.bank_data.synth_port)

            # Valon is now assumed to be working
            if self.valon:
                self.valon.set_ref_select(self.bank_data.synth_ref)
                self.valon.set_reference(self.bank_data.synth_ref_freq)
                self.valon.set_vco_range(0, *self.bank_data.synth_vco_range)
                self.valon.set_rf_level(0, self.bank_data.synth_rf_level)
                self.valon.set_options(0, *self.bank_data.synth_options)

            print "connecting to %s, port %i" % (self.bank_data.katcp_ip, self.bank_data.katcp_port)
        print self.bank_data
        return "config file loaded."

    def set_scan_number(self, num):
        """
        set_scan_number(scan_number)

        Sets the scan number to the value specified
        """
        self.scan_number = num
        self.set_status(SCANNUM=num)
        self.set_status(SCAN=num)

    def increment_scan_number(self):
        """
        increment_scan_number()

        Increments the current scan number
        """
        self.scan_number = self.scan_number+1
        self.set_scan_number(self.scan_number)

    def set_status(self, **kwargs):
        """
        set_status(self, **kwargs)

        Updates the values for the keys specified in the parameter list
        as keyword value pairs. So:

          set_status(PROJID='JUNK', OBS_MODE='HBW')

        would set those two parameters.
        """
        if self.backend is not None:
            self.backend.write_status(**kwargs)

    def get_status(self, keys = None):
        """
        get_status(keys=None)

        Returns the specified key's value, or the values of several
        keys, or the entire contents of the shared memory status buffer.

        'keys' == None: The entire buffer is returned, as a
        dictionary containing the key/value pairs.

        'keys' is a list of keys, which are strings: returns a dictionary
        containing the requested subset of key/value pairs.

        'keys' is a single string: a single value will be looked up and
        returned using 'keys' as the single key.
        """
        if self.backend is not None:
            return self.backend.get_status(keys)
        else:
            return None

    def list_modes(self):
        modes = self.mode_data.keys()
        modes.sort()
        return modes

    def set_mode(self, mode, bandwidth = None, force = False):
        """set_mode(mode, bandwidth = None, force=False)

        Sets the operating mode for the roach.  Does this by programming
        the roach.

        *mode:*
          A string; A keyword which is one of the '[MODEX]'
          sections of the configuration file, which must have been loaded
          earlier.

        *bandwidth:*
          The valon bandwidth for this new mode. If not
          specified the last value used will be reused. If no value was
          ever provided the config file value will be used.

        *force:*
          A boolean flag; if 'True' and the new mode is the same as
          the current mode, the mode will be reloaded. It is set to
          'False' by default, in which case the new mode will not be
          reloaded if it is already the current mode.

        Returns a tuple consisting of (status, 'msg') where 'status' is
        a boolean, 'True' if the mode was loaded, 'False' otherwise; and
        'msg' explains the error if any.

        Example::

            s, msg = f.set_mode('MODE1') s, msg =
            f.set_mode(mode='MODE1', force=True)

        """
        frequency = bandwidth

        if mode:
            if mode in self.mode_data:
                if force or mode != self.current_mode or frequency != self.mode_data[mode].frequency:
                    self.check_shared_memory()
                    print "New mode specified and/or bandwidth specified!"

                    if self.current_mode:
                        old_hpc_program = self.mode_data[self.current_mode].hpc_program
                    else:
                        old_hpc_program = "none"

                    self.current_mode = mode
                    new_hpc_program = self.mode_data[mode].hpc_program

                    #if old_hpc_program != new_hpc_program:
                    if 0:
                        self.clear_shared_memory()
                        self.reformat_data_buffers(mode)
                    else:
                        print 'Not reformatting buffers'

                    # The correct Backend type will be chosen from the
                    # dictionary of available Backend types self.BET,
                    # based on the MODENAME key in the MODE
                    # sections. The modes include VEGAS-style spectral
                    # modes, and GUPPI-style pulsar modes. The spectral
                    # modes are further divided into HBW and LBW modes,
                    # and the GUPPI modes into coherent dedispersion
                    # (CDD) and incoherent mode. CDD modes are
                    # characterised by having only 1 ROACH sending data
                    # to 8 different HPC servers. The ROACH has 8
                    # network adapters for the purpose. Because of the
                    # 1->8 arrangement, one of the Players is Master and
                    # programs the ROACH. The others set up everything
                    # except their ROACH; in fact the others deprogram
                    # their ROACH so that the IP addresses may be used
                    # in the one CDD ROACH.
                    #
                    # The other kind of mode has 1 ROACH -> 1 HPC
                    # server, so each Player programs its ROACH.

                    # based upon the mode's backend config setting, create the appropriate
                    # parameter calculator 'backend'
                    if self.backend is not None:
                        self.backend.cleanup()
                        print "set_mode(%s): cleaned up old backend." % mode
                        del(self.backend)
                        self.backend = None

                    # If 'frequency' is provided record in mode data
                    # for use and reuse if not subsequently
                    # provided. Initial value of mode data frequency
                    # is from config file.
                    if frequency:
                        self.mode_data[mode].frequency = frequency

                    # get backend type based on backend name: h1k, h16k, l1/lbw1, etc.
                    Backend = self.BET[self.mode_data[mode].backend_name.lower()]
                    print "set_mode(%s): Creating new %s" % (mode, Backend.__name__)
                    # instantiate our new backend:
                    self.backend = Backend(self.bank_data,
                                           self.mode_data[mode],
                                           self.roach,
                                           self.valon,
                                           self.hpc_macs,
                                           self.simulate)
                    print "set_mode(%s): beginning wait for DAQ program" % mode
                    self.backend._wait_for_status('DAQSTATE', 'stopped', timedelta(seconds=1))
                    print "set_mode(%s): wait for DAQ program ended." % mode

                    if self.simulate:
                        sleep(10) # make it realistic

                    return (True, 'New mode %s set!' % mode)
                else:
                    return (False, 'Mode %s is already set! Use \'force=True\' to force.' % mode)
            else:
                return (False, 'Mode %s is not supported.' % mode)
        else:
            return (False, "Mode is 'None', doing nothing.")

    def get_mode(self):
        """
        get_mode()

        Returns the current operating mode for the bank.
        """
        return self.current_mode

    def startin(self, inSecs, durSecs):
        "An alternative method for running a scan, only available for Beamformer backend."
        # HACK^3
        assert self.current_mode == 'MODE42'
        if self.backend:
            self.backend.start(inSecs, durSecs)

    def start(self, starttime = None):
        """
        start(self, starttime = None)

        starttime: a datetime object representing a start time, in UTC

        --OR--

        starttime: a tuple or list(for ease of JSON serialization) of
        datetime compatible values: (year, month, day, hour, minute,
        second, microsecond), UTC.

        Sets up the system for a measurement and kicks it off at the
        appropriate time, based on 'starttime'.  If 'starttime' is not
        on a PPS boundary it is bumped up to the next PPS boundary.  If
        'starttime' is not given, the earliest possible start time is
        used.

        start() may require a needed arm delay time, which is specified
        in every mode section of the configuration file as
        'needed_arm_delay'. During this delay it tells the HPC program
        to start its net, accum and disk threads, and waits for the HPC
        program to report that it is receiving data. It then calculates
        the time it needs to sleep until just after the penultimate PPS
        signal. At that time it wakes up and arms the ROACH. The ROACH
        should then send the initial packet at that time.

        If a start time is specified that cannot be met an Exception is
        thrown with a message stating the problem.
        """

        if self.backend:
            self.increment_scan_number()
            print starttime
            return self.backend.start(starttime)


    def monitor(self):
        """monitor(self)

        monitor() requests that the DAQ program go into monitor
        mode. This is handy for troubleshooting issues like no data. In
        monitor mode the DAQ's net thread starts receiving data but does
        not do anything with that data. However the thread's status may
        be read in the status memory: NETSTAT will say 'receiving' if
        packets are arriving, 'waiting' if not.

        """

        if self.backend:
            return self.backend.monitor()
        else:
            return (False, "No backend selected!")

    def stop(self):
        """
        Stops a running scan, by telling the current backend to stop.
        """

        if self.backend:
            return self.backend.stop()
        else:
            return (False, "No backend selected!")

    def scan_status(self):
        """
        scan_status(self):

        Returns the state of currently running scan. The return type is
        a tuple, backend dependent.
        """

        if self.backend:
            return self.backend.scan_status()
        else:
            return (False, "No backend selected!")


    def earliest_start(self):
        if self.backend:
            return (True, datetime_to_tuple(self.backend.earliest_start()))
        else:
            return (False, "No backend selected!")


    def set_param(self, **kvpairs):
        """
        A pass-thru method which conveys a backend specific parameter to the modes parameter engine.

        Example usage:
        set_param(exposure=x,switch_period=1.0, ...)
        """

        if self.backend is not None:
            for k,v in kvpairs.items():
                return self.backend.set_param(str(k), v)
        else:
            raise Exception("Cannot set parameters until a mode is selected")

    def help_param(self, name = None):
        """
        A pass-thru method which conveys a backend specific parameter to the modes parameter engine.

        Example usage::

          help_param(exposure)
        """

        if self.backend is not None:
            return self.backend.help_param(name)
        else:
            raise Exception("Cannot set parameters until a mode is selected")

    def get_param(self, name = None):
        """A pass-thru method which gets the values of a backend specific parameter.

        Example usage::

          get_param(exposure)

        """

        if self.backend is not None:
            return self.backend.get_param(name)
        else:
            raise Exception("Cannot get parameters until a mode is selected")


    def prepare(self):
        """
        Perform calculations for the current set of parameter settings
        """
        if self.backend is not None:
            self.backend.prepare()
        else:
            raise Exception("Cannot prepare until a mode is selected")

    def reset_roach(self):
        """
        reset_roach(self):

        Sends a sequence of commands to reset the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """

        if self.backend:
            self.backend.reset_roach()


    def arm_roach(self):
        """
        arm_roach(self):

        Sends a sequence of commands to arm the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """
        if self.backend:
            self.backend.arm_roach()

    def disarm_roach(self):
        """
        disarm_roach(self):

        Sends a sequence of commands to disarm the ROACH. This is mode
        dependent and mode should have been specified in advance, as the
        sequence of commands is obtained from the 'MODEX' section of the
        configuration file.
        """
        if self.backend:
            self.backend.disarm_roach()


    def clear_switching_states(self):
        """
        resets/deletes the switching_states (backend dependent)
        """
        if self.backend:
            return self.backend.clear_switching_states()
        else:
            raise Exception("Cannot clear switcvhing states until a mode has been selected")

    def add_switching_state(self, duration, blank = False, cal = False, sig_ref_1 = False):
        """add_switching_state(duration, blank, cal, sig):

        Add a description of one switching phase (backend dependent).

        *duration*
          the length of this phase in seconds,
        *blank*
          the state of the blanking signal (True = blank, False = no blank)
        *cal*
          the state of the cal signal (True = cal, False = no cal)
        *sig*
          the state of the sig_ref signal (True = ref, false = sig)

        Example to set up a 8 phase signal (4-phase if blanking is not
        considered) with blanking, cal, and sig/ref, total of 400 mS::

          be = Backend(None) # no real backend needed for example
          be.clear_switching_states()
          be.add_switching_state(0.01, blank = True, cal = True, sig = True)
          be.add_switching_state(0.09, cal = True, sig = True)
          be.add_switching_state(0.01, blank = True, cal = True)
          be.add_switching_state(0.09, cal = True)
          be.add_switching_state(0.01, blank = True, sig = True)
          be.add_switching_state(0.09, sig = True)
          be.add_switching_state(0.01, blank = True)
          be.add_switching_state(0.09)

        """
        if self.backend:
            return self.backend.add_switching_state(duration, blank, cal, sig_ref_1)
        else:
            raise Exception("Cannot add switching states until a mode has been selected.")

    def set_gbt_ss(self, period, ss_list):
        """set_gbt_ss(period, ss_list):

        adds a complete GBT style switching signal description.

        *period*
          The complete period length of the switching signal.
        *ss_list*
          A list of GBT phase components. Each component is a tuple:
          (phase_start, sig_ref, cal, blanking_time) There is one of
          these tuples per GBT style phase.

        Example::

            b.set_gbt_ss(period = 0.1,
                         ss_list = ((0.0, SWbits.SIG, SWbits.CALON, 0.025),
                                    (0.25, SWbits.SIG, SWbits.CALOFF, 0.025),
                                    (0.5, SWbits.REF, SWbits.CALON, 0.025),
                                    (0.75, SWbits.REF, SWbits.CALOFF, 0.025))
                        )

        """
        if self.backend:
            return self.backend.set_gbt_ss(period, ss_list)
        else:
            raise Exception("Cannot set switching states until a mode has been selected.")

    def _watchdog(self):
        """
        Looks at internal conditions every so often.
        """

        if self.backend:
             # Monitor scan status
            if self.backend.scan_running:
                now = datetime.utcnow()
                start = self.backend.start_time
                scanlength = self.backend.scan_length + 1

                if all((start, scanlength)):
                    sl = timedelta(seconds=scanlength)
                    rem = (start + sl) - now
                    # lets have a little scan countdown in status memory
                    self.set_status(SCANREM=rem.seconds - 1)

                    if now > start + sl:
                        self.stop()
                        self.set_status(SCANREM='scan terminated')
Example #7
0
    def read_config_file(self, filename):
        """
        read_config_file(filename)

        Reads the config file 'filename' and loads the values into data
        structures in memory. 'filename' should be a fully qualified
        filename. The config file contains a 'bank' section of interest to
        this bank; in addition, it contains any number of 'MODEX' sections,
        where 'X' is a mode name/number.
        """

        try:
            config = ConfigParser.ConfigParser()
            config.readfp(open(filename))

            if not self.bank_name:
                self.bank_name = self.get_bank_name(config)

                if not self.bank_name:
                    sys.exit(0)

            bank = self.bank_name
            print "bank =", bank, "filename =", filename

            # Read general stuff:
            telescope = config.get('DEFAULTS', 'telescope').lstrip().rstrip().lstrip('"').rstrip('"')
            self.set_status(TELESCOP=telescope)

            # Read the HPC MAC addresses
            macs = config.items('HPCMACS')
            self.hpc_macs = {}

            for i in macs:
                key = _ip_string_to_int(_hostname_to_ip(i[0])) & 0xFF
                self.hpc_macs[key] = int(i[1], 16)

            # Get all bank data and store it. This is needed by any mode
            # where there is 1 ROACH and N Players & HPC programs
            banks = [s for s in config.sections() if 'BANK' in s]

            for bank in banks:
                b = BankData()
                b.load_config(config, bank)
                self.banks[bank] = b

            # Get config info on this bank's ROACH2. Normally there is 1
            # ROACH per Player/HPC node, so this is it.
            self.bank_data = self.banks[self.bank_name]

            # Get config info on all modes
            modes = [s for s in config.sections() if 'MODE' in s]

            for mode in modes:
                m = ModeData()

                try:
                    m.load_config(config, mode)
                except Exception, e:
                    if self.simulate:
                        pass
                    else:
                        raise e

                self.mode_data[mode] = m

        except ConfigParser.NoSectionError as e:
            print str(e)
            return str(e)

        # Now that all the configuration data is loaded, set up some
        # basic things: KATCP, Valon, etc.  Not all backends will
        # have/need katcp & valon, so it config data says no roach &
        # valon, these steps will not happen.
        self.valon = None
        self.roach = None

        if not self.simulate and self.bank_data.has_roach:
            self.roach = katcp_wrapper.FpgaClient(self.bank_data.katcp_ip,
                                                  self.bank_data.katcp_port,
                                                  timeout = 30.0)
            time.sleep(1) # It takes the KATCP interface a little while to get ready. It's used below
                          # by the Valon interface, so we must wait a little.

            # The Valon can be on this host ('local') or on the ROACH
            # ('katcp'), or None. Create accordingly.
            if self.bank_data.synth == 'local':
                import valon_synth
                self.valon = valon_synth.Synthesizer(self.bank_data.synth_port)
            elif self.bank_data.synth == 'katcp':
                from valon_katcp import ValonKATCP
                self.valon = ValonKATCP(self.roach, self.bank_data.synth_port)

            # Valon is now assumed to be working
            if self.valon:
                self.valon.set_ref_select(self.bank_data.synth_ref)
                self.valon.set_reference(self.bank_data.synth_ref_freq)
                self.valon.set_vco_range(0, *self.bank_data.synth_vco_range)
                self.valon.set_rf_level(0, self.bank_data.synth_rf_level)
                self.valon.set_options(0, *self.bank_data.synth_options)

            print "connecting to %s, port %i" % (self.bank_data.katcp_ip, self.bank_data.katcp_port)
        print self.bank_data
        return "config file loaded."
Example #8
0
def main():

    p = OptionParser()
    
    p.set_usage('%prog [options]')
    p.set_description(__doc__)
    p.add_option('-p', '--skip_prog', dest='prog_fpga',action='store_false', default=True,
        help='Skip FPGA programming (assumes already programmed).  Default: program the FPGAs')
    p.add_option('-v', '--verbosity', dest='verbosity',type='int', default=1,
        help='Verbosity level. Default: 1')
    p.add_option('-r', '--roach', dest='roach',type='str', default='srbsr2-1',
        help='ROACH IP address or hostname. Default: srbsr2-1')
    p.add_option('-b', '--boffile', dest='boffile',type='str', default='h1k_ver105_2013_Dec_02_1551.bof',
        help='Boffile to program. Default: h1k_ver105_2013_Dec_02_1551.bof')
    p.add_option('-N', '--n_trials', dest='n_trials',type='int', default=10,
        help='Number of snap/fit trials. Default: 10')
    p.add_option('-c', '--clockrate', dest='clockrate', type='float', default=1500.0,
        help='Clock rate in MHz, for use when plotting frequency axes. If none is given, rate will be estimated from FPGA clock')
    p.add_option('-f', '--testfreq', dest='testfreq', type='float', default=18.3105,
        help='sine wave test frequency input in MHz. Default = 18.3105')
    p.add_option('-l', '--ampl', dest='ampl', type='float', default=3.0,
        help='Power level of test tone input in dBm. Default = 3.0')
    p.add_option('-g', '--gpibaddr', dest='gpibaddr', type='str', default='10.16.96.174',
        help='IP Address of the GPIB.  Current default is set to tape room machine. Default = 10.16.96.174')
    p.add_option('-s', '--snapname', dest='snapname', type='str', default='adcsnap',
        help='snapname. Default = adcsnap')
    p.add_option('-z', '--zdok', dest='zdok', type='int', default=2,
        help='ZDOK, 0 or 1, if input is 2, then refers to both. Default = 2')
    p.add_option('-d', '--directory', dest='dir', type='str', default='.',    
        help='name of directory to put all files')
    p.add_option('-o', '--ogp', dest='do_ogp', type='int', default=1,
        help='Do OGP calibration? Default = 1')
    p.add_option('-i', '--inl', dest='do_inl', type='int', default=1,
        help='Do INL calibration (OGP must be completed first)? Default = 1')
    p.add_option('-t', '--test', dest='test', type='int', default=1,
        help='Test after calibration is completed. Default=1')
    p.add_option('-m', '--manual', dest='manual', type='int', default=1,
        help='Manual control of the calibration process. Default=1')
    p.add_option('-S', '--save', dest='save', type='int', default=1,
        help='To save the plots. Default=1 (save)')
    p.add_option('-V', '--view', dest='view', type='int', default=1,
        help='To show the plots interactively (will be forced to 0 if manual is off). Default=1 (show)')
    p.add_option('-u', '--update_conf', dest='update_conf', action='store_false', default=True,
        help='Update the <roach_name>-adc.conf file?')

    opts, args = p.parse_args(sys.argv[1:])

    # setup log file name:
    current_time = datetime.datetime.now().strftime('%Y-%m%d-%H%M%S')
    timestamp = "_%s_z%d_%s"%(opts.roach, opts.zdok, current_time)
    AdcCalLoggingFileHandler.timestamp = timestamp

    # load log config file
    var = "YGOR_TELESCOPE"
    confdir = '.' if not os.environ.has_key(var) else  os.path.join(os.environ[var], "etc/config")
    conffile = "%s/%s" % (confdir, 'adc_cal_logging.conf')
    if not os.path.isfile(conffile):
        print "Cannot find config file for logging: %s" % conffile
        sys.exit(0)

    logging.config.fileConfig(conffile)
    logger = logging.getLogger('adc5gLogging')
    logger.info("Started")
    
    if not opts.verbosity:
        logger.setLevel(logging.INFO)

    logger.info("opts :\n\t" + str(opts))
    logger.info("args :\n\t" + str(args))
    logger.info("log file name:\n\t" + AdcCalLoggingFileHandler.logfilename)

    tmsg = 'Connecting to %s'%opts.roach
    logger.info(tmsg)
    r = corr.katcp_wrapper.FpgaClient(opts.roach)
    time.sleep(0.2)
    tmsg = 'ROACH is connected? ' +  str(r.is_connected())
    logger.info(tmsg)

    if opts.prog_fpga:
        tmsg = 'Programming ROACH with boffile %s'%opts.boffile
        r.progdev(opts.boffile)
        time.sleep(0.5)
        logger.info(tmsg)

    tmsg = 'Estimating clock speed...'
    logger.info(tmsg)
    clk_est = r.est_brd_clk()
    tmsg = 'Clock estimated speed is %d MHz'%clk_est
    logger.info(tmsg)

    if opts.clockrate is None:
        clkrate = clk_est*16
        print "SETTING clkrate to: ", clkrate
    else:
        clkrate = opts.clockrate

    # Before progressing furth, check that the Valon Synth
    # is actually set to the clkrate.
    # ValonKATCP is not a worker class belonging to ADCCalibrate
    # since ADCCalibrate is inherited code, while Valon is ours.
    valonSerial = "/dev/ttyS1" # this should never change
    valonSynth = 0 # neither should this (0: A, 8: B)
    v = ValonKATCP(r, valonSerial) 
    current_clkrate = v.get_frequency(valonSynth)
    tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate
    logger.info(tmsg)
    if abs(current_clkrate - clkrate) > 0.001:
        v.set_frequency(valonSynth, clkrate)
        time.sleep(1)
        current_clkrate = v.get_frequency(valonSynth)
        tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate
        logger.info(tmsg)

    # Time to make our worker class
    cal = ADCCalibrate(dir = opts.dir 
                     , roach_name = opts.roach
                     , gpib_addr = opts.gpibaddr
                     , clockrate = clkrate
                     , bof = opts.boffile
                     , config = opts.update_conf
                     , roach = r)

    cal.set_freq(opts.testfreq)
    cal.set_ampl(opts.ampl)

    if opts.prog_fpga:
        tmsg = 'Calibrating ADCs (MMCM)'
        logger.info(tmsg)
        cal.do_mmcm(opts.zdok)
        if opts.manual:
            if cal.user_input("Check the test ramps now?"):
                cal.check_ramp(opts.zdok, save=opts.save, view=opts.view) #, filename = fn)


    if opts.do_ogp or opts.test:
        if  cal.gpib_test(opts.zdok, opts.testfreq, opts.ampl, manual=opts.manual): 
            tmsg = 'Current test tone power level: %.4f'%opts.ampl
            logger.debug(tmsg)
            tmsg ='Current test tone frequency: %.4f'%opts.testfreq
            logger.debug(tmsg)
            cal.check_raw(opts.zdok, save=opts.save, view=(opts.manual and opts.view))
            if opts.manual:
                if cal.user_input("Adjust power level now?"):
                    cal.ampl_setup(opts.zdok, manual = True)
        else:
            tmsg = "Problem with synthesizer, aborting OGP calibration & testing..."
            logger.warning(tmsg)
            opts.do_ogp = 0
            opts.test = 0


    if opts.do_ogp:
        cal.do_ogp(opts.zdok, opts.testfreq, opts.n_trials)
        if opts.do_inl:
            cal.do_inl(opts.zdok) 

    if opts.test:
        if opts.manual:
            logger.info("Startinging manual testing...")
            check_spec = cal.user_input("Check spectrum?")
            while(check_spec):
                cal.freq_setup(opts.zdok, manual=True)
                cal.ampl_setup(opts.zdok, manual=True)
                fn = cal.get_check_filename("post_adjustment_test_%.4fMHz" % cal.gpib.freq, opts.zdok) 
                cal.check_spec(opts.zdok, save=opts.save, view=opts.view, filename=fn) #, filename=fn)
                check_spec = cal.user_input("Check spectrum?")
            if cal.user_input("Do frequency scan?"):
                cal.freq_scan(save=opts.save, view=opts.view) #, filename=fn)
        else:
            logger.info("Starting automatic testing...")
            for i in range(0, 5):
                test_freq = random.random()*cal.clockrate
                cal.freq_setup(manual=False, freq = test_freq)
                fn = "post_adjustment_test_%.4fMHz"%cal.gpib.freq + cal.file_label
                cal.check_spec(save=opts.save, view=False, filename=fn)
            fn = 'freq_scan' + cal.file_label #timestamp
            cal.freq_scan(save=opts.save, view=False, filename=fn)
Example #9
0
def main():

    p = OptionParser()

    p.set_usage('%prog [options]')
    p.set_description(__doc__)
    p.add_option(
        '-p',
        '--skip_prog',
        dest='prog_fpga',
        action='store_false',
        default=True,
        help=
        'Skip FPGA programming (assumes already programmed).  Default: program the FPGAs'
    )
    p.add_option('-v',
                 '--verbosity',
                 dest='verbosity',
                 type='int',
                 default=1,
                 help='Verbosity level. Default: 1')
    p.add_option('-r',
                 '--roach',
                 dest='roach',
                 type='str',
                 default='srbsr2-1',
                 help='ROACH IP address or hostname. Default: srbsr2-1')
    p.add_option(
        '-b',
        '--boffile',
        dest='boffile',
        type='str',
        default='h1k_ver105_2013_Dec_02_1551.bof',
        help='Boffile to program. Default: h1k_ver105_2013_Dec_02_1551.bof')
    p.add_option('-N',
                 '--n_trials',
                 dest='n_trials',
                 type='int',
                 default=10,
                 help='Number of snap/fit trials. Default: 10')
    p.add_option(
        '-c',
        '--clockrate',
        dest='clockrate',
        type='float',
        default=1500.0,
        help=
        'Clock rate in MHz, for use when plotting frequency axes. If none is given, rate will be estimated from FPGA clock'
    )
    p.add_option(
        '-f',
        '--testfreq',
        dest='testfreq',
        type='float',
        default=18.3105,
        help='sine wave test frequency input in MHz. Default = 18.3105')
    p.add_option('-l',
                 '--ampl',
                 dest='ampl',
                 type='float',
                 default=3.0,
                 help='Power level of test tone input in dBm. Default = 3.0')
    p.add_option(
        '-g',
        '--gpibaddr',
        dest='gpibaddr',
        type='str',
        default='10.16.96.174',
        help=
        'IP Address of the GPIB.  Current default is set to tape room machine. Default = 10.16.96.174'
    )
    p.add_option('-s',
                 '--snapname',
                 dest='snapname',
                 type='str',
                 default='adcsnap',
                 help='snapname. Default = adcsnap')
    p.add_option(
        '-z',
        '--zdok',
        dest='zdok',
        type='int',
        default=2,
        help='ZDOK, 0 or 1, if input is 2, then refers to both. Default = 2')
    p.add_option('-d',
                 '--directory',
                 dest='dir',
                 type='str',
                 default='.',
                 help='name of directory to put all files')
    p.add_option('-o',
                 '--ogp',
                 dest='do_ogp',
                 type='int',
                 default=1,
                 help='Do OGP calibration? Default = 1')
    p.add_option(
        '-i',
        '--inl',
        dest='do_inl',
        type='int',
        default=1,
        help='Do INL calibration (OGP must be completed first)? Default = 1')
    p.add_option('-t',
                 '--test',
                 dest='test',
                 type='int',
                 default=1,
                 help='Test after calibration is completed. Default=1')
    p.add_option('-m',
                 '--manual',
                 dest='manual',
                 type='int',
                 default=1,
                 help='Manual control of the calibration process. Default=1')
    p.add_option('-S',
                 '--save',
                 dest='save',
                 type='int',
                 default=1,
                 help='To save the plots. Default=1 (save)')
    p.add_option(
        '-V',
        '--view',
        dest='view',
        type='int',
        default=1,
        help=
        'To show the plots interactively (will be forced to 0 if manual is off). Default=1 (show)'
    )
    p.add_option('-u',
                 '--update_conf',
                 dest='update_conf',
                 action='store_false',
                 default=True,
                 help='Update the <roach_name>-adc.conf file?')

    opts, args = p.parse_args(sys.argv[1:])

    # setup log file name:
    current_time = datetime.datetime.now().strftime('%Y-%m%d-%H%M%S')
    timestamp = "_%s_z%d_%s" % (opts.roach, opts.zdok, current_time)
    AdcCalLoggingFileHandler.timestamp = timestamp

    # load log config file
    var = "YGOR_TELESCOPE"
    confdir = '.' if not os.environ.has_key(var) else os.path.join(
        os.environ[var], "etc/config")
    conffile = "%s/%s" % (confdir, 'adc_cal_logging.conf')
    if not os.path.isfile(conffile):
        print "Cannot find config file for logging: %s" % conffile
        sys.exit(0)

    logging.config.fileConfig(conffile)
    logger = logging.getLogger('adc5gLogging')
    logger.info("Started")

    if not opts.verbosity:
        logger.setLevel(logging.INFO)

    logger.info("opts :\n\t" + str(opts))
    logger.info("args :\n\t" + str(args))
    logger.info("log file name:\n\t" + AdcCalLoggingFileHandler.logfilename)

    tmsg = 'Connecting to %s' % opts.roach
    logger.info(tmsg)
    r = corr.katcp_wrapper.FpgaClient(opts.roach)
    time.sleep(0.2)
    tmsg = 'ROACH is connected? ' + str(r.is_connected())
    logger.info(tmsg)

    if opts.prog_fpga:
        tmsg = 'Programming ROACH with boffile %s' % opts.boffile
        r.progdev(opts.boffile)
        time.sleep(0.5)
        logger.info(tmsg)

    tmsg = 'Estimating clock speed...'
    logger.info(tmsg)
    clk_est = r.est_brd_clk()
    tmsg = 'Clock estimated speed is %d MHz' % clk_est
    logger.info(tmsg)

    if opts.clockrate is None:
        clkrate = clk_est * 16
        print "SETTING clkrate to: ", clkrate
    else:
        clkrate = opts.clockrate

    # Before progressing furth, check that the Valon Synth
    # is actually set to the clkrate.
    # ValonKATCP is not a worker class belonging to ADCCalibrate
    # since ADCCalibrate is inherited code, while Valon is ours.
    valonSerial = "/dev/ttyS1"  # this should never change
    valonSynth = 0  # neither should this (0: A, 8: B)
    v = ValonKATCP(r, valonSerial)
    current_clkrate = v.get_frequency(valonSynth)
    tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate
    logger.info(tmsg)
    if abs(current_clkrate - clkrate) > 0.001:
        v.set_frequency(valonSynth, clkrate)
        time.sleep(1)
        current_clkrate = v.get_frequency(valonSynth)
        tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate
        logger.info(tmsg)

    # Time to make our worker class
    cal = ADCCalibrate(dir=opts.dir,
                       roach_name=opts.roach,
                       gpib_addr=opts.gpibaddr,
                       clockrate=clkrate,
                       bof=opts.boffile,
                       config=opts.update_conf,
                       roach=r)

    cal.set_freq(opts.testfreq)
    cal.set_ampl(opts.ampl)

    if opts.prog_fpga:
        tmsg = 'Calibrating ADCs (MMCM)'
        logger.info(tmsg)
        cal.do_mmcm(opts.zdok)
        if opts.manual:
            if cal.user_input("Check the test ramps now?"):
                cal.check_ramp(opts.zdok, save=opts.save,
                               view=opts.view)  #, filename = fn)

    if opts.do_ogp or opts.test:
        if cal.gpib_test(opts.zdok,
                         opts.testfreq,
                         opts.ampl,
                         manual=opts.manual):
            tmsg = 'Current test tone power level: %.4f' % opts.ampl
            logger.debug(tmsg)
            tmsg = 'Current test tone frequency: %.4f' % opts.testfreq
            logger.debug(tmsg)
            cal.check_raw(opts.zdok,
                          save=opts.save,
                          view=(opts.manual and opts.view))
            if opts.manual:
                if cal.user_input("Adjust power level now?"):
                    cal.ampl_setup(opts.zdok, manual=True)
        else:
            tmsg = "Problem with synthesizer, aborting OGP calibration & testing..."
            logger.warning(tmsg)
            opts.do_ogp = 0
            opts.test = 0

    if opts.do_ogp:
        cal.do_ogp(opts.zdok, opts.testfreq, opts.n_trials)
        if opts.do_inl:
            cal.do_inl(opts.zdok)

    if opts.test:
        if opts.manual:
            logger.info("Startinging manual testing...")
            check_spec = cal.user_input("Check spectrum?")
            while (check_spec):
                cal.freq_setup(opts.zdok, manual=True)
                cal.ampl_setup(opts.zdok, manual=True)
                fn = cal.get_check_filename(
                    "post_adjustment_test_%.4fMHz" % cal.gpib.freq, opts.zdok)
                cal.check_spec(opts.zdok,
                               save=opts.save,
                               view=opts.view,
                               filename=fn)  #, filename=fn)
                check_spec = cal.user_input("Check spectrum?")
            if cal.user_input("Do frequency scan?"):
                cal.freq_scan(save=opts.save, view=opts.view)  #, filename=fn)
        else:
            logger.info("Starting automatic testing...")
            for i in range(0, 5):
                test_freq = random.random() * cal.clockrate
                cal.freq_setup(manual=False, freq=test_freq)
                fn = "post_adjustment_test_%.4fMHz" % cal.gpib.freq + cal.file_label
                cal.check_spec(save=opts.save, view=False, filename=fn)
            fn = 'freq_scan' + cal.file_label  #timestamp
            cal.freq_scan(save=opts.save, view=False, filename=fn)
Example #10
0
class ADCCalibrations:

    def __init__(self
               , test = False
               , conf_dir = None
               , data_dir = None
               , roaches = None
               , mmcm_trials = None
               , ogp_trials = None
               , test_tone = None
               , ampl = None
               , now = None
               , manual = False
               , do_ogps = True
               , do_mmcms = True
               , gpib_addr = None):

        self.test = test
        self.now = now
        self.conf_dir = conf_dir if conf_dir is not None else '.'
        self.data_dir = data_dir if data_dir is not None else '.'
        self.roaches = roaches if roaches is not None else self.get_roach_names_from_config()
        self.banks = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
        self.manual = manual

        self.do_mmcms = do_mmcms
        self.do_ogps = do_ogps
        if not self.do_ogps and not self.do_mmcms:
            raise Exception("One of do_ogps and do_mmcms must be True")

        # mmcms
        self.mmcm_trials = mmcm_trials if mmcm_trials is not None else 5
        self.mmcm_tolerance = 4
        
        # ogps/inls
        self.ogp_bof = 'h1k_ver106_2014_Apr_11_1612.bof'
        self.testfreq = test_tone if test_tone is not None else 18.3105 # MHz
        self.ampl = ampl if ampl is not None else -3
        self.ogp_trials = ogp_trials if ogp_trials is not None else 10
        self.gpibaddr = gpib_addr if gpib_addr is not None else '10.16.96.174' # tape room


        # helper classes
        self.cp = ConfigParser.ConfigParser()
        self.roach = None
        self.valon = None
        self.cal = None
        self.adcConf = None

    def find_all_calibrations(self):
        if self.do_mmcms:
            self.find_all_mmcms()
        if self.do_ogps:
            self.find_all_ogps()

    def find_all_ogps(self):
        for r in self.roaches:
            self.find_ogps(r)

    def find_all_mmcms(self):
        for r in self.roaches:
            self.find_mmcms(r)

    def get_roach_names_from_config(self):
        "Determines what roaches to connect to from the vegas.conf file"
    
        fn = "%s/%s" % (self.conf_dir, "vegas.conf")
        r = cp.read(fn)
        if len(r)==0:
            print "Could not find roach names from: ", fn
            return []
        
        # what banks to read?
        sec = "DEFAULTS"
        subsys = self.cp.get(sec, "subsystems")
        subsys = [int(s) for s in subsys.split(',')]
    
        # what roaches corresopond to those banks?
        roaches = []
        for s in subsys:
            bank = self.banks[s-1]
            sec = "BANK%s" % bank
            # roach_host = vegasr2-1.gb.nrao.edu
            roaches.append(cp.get(sec, "roach_host").split('.')[0])
        return roaches    
        
    def get_adc_config_filename(self, roach_name): 
        fn = "%s/%s-adc.conf" % (self.conf_dir, roach_name)
        logger.info("MMCM config file: %s" % fn)
        return fn

    def init_for_roach(self, roach_name):
        "Inits the helper classes we use to interact w/ the given roach name"

        # connect to roach
        tmsg = 'Connecting to %s'%roach_name
        logger.info(tmsg)
        if not self.test:
            self.roach = corr.katcp_wrapper.FpgaClient(roach_name)
            time.sleep(1)
            if not self.roach.is_connected():
                raise Exception("Cannot connect to %s" % roach_name)
    
        # we'll need this to change the frequency
        valonSerial = "/dev/ttyS1" # this should never change
        if not self.test:
            self.valon = ValonKATCP(self.roach, valonSerial) 
    
        # this is the object that will find the MMCM value
        self.cal = ADCCalibrate(dir = self.data_dir 
                         , roach_name = roach_name
                         , gpib_addr = self.gpibaddr
                         , roach = self.roach
                         , now = self.now
                         , test = self.test)
    
        # read the config file and find the mmcm  through each mode
        fn = self.get_adc_config_filename(roach_name)
        self.adcConf = ADCConfFile(fn)

    def find_ogps(self, roach_name):

        self.init_for_roach(roach_name)

        for frq, value in self.adcConf.ogp_info.items():
            #i, ogp0, ogp1 = value
            # determine the OGP values for this clockrate
            tmsg = "Finding OGPs for clockrate: %s" % frq
            logger.info(tmsg)
            ogp0, ogp1 = self.find_this_ogp(frq)
            if ogp0 is not None and ogp1 is not None:
                self.adcConf.write_ogps(frq, 0, ogp0)
                self.adcConf.write_ogps(frq, 1, ogp1)

        self.adcConf.write_to_file()

        # the INLs don't change w/ either bof, or clockrate,
        # so now that this roach is in a suitable state (it's
        # MMCM and OGP calibrations are done), let's do one INL
        for z in range(2):
            self.cal.do_inl(z)
            self.adcConf.write_inls(z, self.cal.inl.inls)
        self.adcConf.write_to_file()    

    def change_bof(self, bof):

        if self.test:
            return

        # switch to the given bof file
        self.roach.progdev(bof)
        tmsg = "Roach BOF file set to: %s" % bof
        logger.info(tmsg)
        time.sleep(2)
    
    def change_frequency(self, freq):
        "If necessary, use the Valon Synth to change the Roach board clockrate."

        if self.test:
            return

        # we also need to switch to the given frequency
        valonSynth = 0 # neither should this (0: A, 8: B)
        current_clkrate = self.valon.get_frequency(valonSynth)
        tmsg = "Valon Synth set to frequency: %f MHz" % current_clkrate
        clkrate = freq / 1e6 # Hz -> MHz
        logger.info(tmsg)
        if abs(current_clkrate - clkrate) > 0.001:
            self.valon.set_frequency(valonSynth, clkrate)
            time.sleep(1)
            current_clkrate = self.valon.get_frequency(valonSynth)
            tmsg = "Valon Synth changed to frequency: %f MHz" % current_clkrate
            logger.info(tmsg)

        # in addition, this class uses clockrate for ogps
        self.cal.set_clockrate(clkrate)

    def find_this_ogp(self, freq):
    

        # we may not need to do this, but its probably safer.
        # OGPs don't change w/ bof file, so this is arbitrary
        self.change_bof(self.ogp_bof)

        # but OGPs *do* change with clock rate
        self.change_frequency(freq)

        # since we reprogrammed the roach, mmcm calibrate
        self.cal.do_mmcm(2)

        # TBF: do this once manually at the beginning? 
        if not self.cal.gpib_test(2, self.testfreq, self.ampl, manual=self.manual):
            logger.info("canceling find_this_ogp for frequency %s" % freq)
            return None, None

        # now find the ogps
        self.cal.do_ogp(0, self.testfreq, self.ogp_trials)
        ogp0 = self.cal.ogp.ogps
        self.cal.do_ogp(1, self.testfreq, self.ogp_trials)
        ogp1 = self.cal.ogp.ogps

        return ogp0, ogp1

    def find_mmcms(self, roach_name):
        """
        For the given roach, determine all the MMCM optimal phase values
        for all the different combinations of bof file and frequency.
        """
   
        self.init_for_roach(roach_name)
    
        for key, value in self.adcConf.mmcm_info.items():
            bof, frq = key
            i, adc0, adc1, _ = value
            # determine the MMCM optimal phase values for this combo
            # of bof file and frequency
            adc0s = []
            adc1s = []
            for trial in range(self.mmcm_trials):
                adc0, adc1 = self.find_this_mmcm(bof, frq) #v, cal, roach, bof, frq)
                tmsg = "Found ADC mmcm's for trial %d: %s, %s" % (trial, adc0, adc1)
                print tmsg
                logger.info(tmsg)
                if self.has_adc_difference(adc0, adc1, adc0s, adc1s):
                    adc0s.append(adc0)
                    adc1s.append(adc1)
    
            self.adcConf.write_mmcms(bof, frq, 0, adc0s)
            self.adcConf.write_mmcms(bof, frq, 1, adc1s)
    
        self.adcConf.write_to_file()
    
    def has_adc_difference(self, adc0, adc1, adc0s, adc1s):
        assert len(adc0s) == len(adc1s)
        if len(adc0s) == 0:
            return True
        has_diff = True    
        adcs = zip(adc0s, adc1s)    
        t = self.mmcm_tolerance 
        for i in range(len(adc0s)):
            #if ((abs(adc0s[i] - adc0) < tolerance) and (abs(adc1s[i] - adc1) < tolerance)): 
            if self.is_within_tolerance(adc0, adc0s[i], t) \
                and self.is_within_tolerance(adc1, adc1s[i], t):
                has_diff = False
        return has_diff        
    
    def is_within_tolerance(self, x, y, tolerance):
        # they are both None
        if x is None and y is None:
            return True
        # one of them is None and the other isn't    
        if x is None or y is None:
            return True # if this were False we'd get None's written to .conf!
        # none of them are none
        return abs(x - y) < tolerance
    
    def find_this_mmcm(self, bof, freq): #valon, adcCal, roach, bof, freq):
        """
        Sets the given bof file and frequency (Hz) for a roach board using
        the given valon and ADCCalibration objects, returns the ADCs'
        MMCM optimal phase results.
        """
    
        # switch to the given bof file
        self.change_bof(bof)
    
        # we also need to switch to the given frequency
        self.change_frequency(freq)
    
        # Now actually find the MMCM optimal phase for each ADC
        set_phase = False # TBF?
        self.cal.set_zdok(0)
        adc0, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase = set_phase)  
        tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (0, adc0, g)
        logger.info(tmsg)
        self.cal.set_zdok(1)
        adc1, g = self.cal.mmcm.calibrate_mmcm_phase(set_phase = set_phase)  
        tmsg = "MMCM (Opt. Phase, Glitches) for zdok %d: %s, %s" % (1, adc1, g)
        logger.info(tmsg)
    
        return adc0, adc1