def __init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test=False): """ Creates an instance of the vegas internals. """ # mode_number may be treated as a constant; the Player will # delete this backend object and create a new one on mode # change. Backend.__init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test) # Important to do this as soon as possible, so that status application # can change its data buffer format print "VEGAS finished init for Backend" self.write_status(BACKEND='VEGAS') print "after write_status" # In VEGAS mode, i_am_master means this particular backend # controls the switching signals. (self.bank is from base class.) self.i_am_master = self.bank.i_am_master # the switching signals builder self.ss = SwitchingSignals() # Parameters: self.setPolarization('SELF') self.setNumberChannels(self.mode.nchan) self.requested_integration_time = 1.0 # self.setValonFrequency(self.mode.frequency) # dependent values, computed from Parameters: self.nspectra = 1 self.nsubbands = 1 self.frequency_resolution = 0.0 self.fpga_clock = None self.fits_writer_process = None self.scan_length = 30.0 self.spec_tick = self.computeSpecTick() self.setHwExposr(self.mode.hwexposr) # setup the parameter dictionary/methods self.params["polarization"] = self.setPolarization self.params["nchan"] = self.setNumberChannels self.params["exposure"] = self.setIntegrationTime self.params["hwexposr"] = self.setHwExposr self.params["num_spectra"] = self.setNumberSpectra # the status memory key/value pair dictionary self.sskeys = {} self.ss.set_spec_tick(self.spec_tick) self.ss.set_hwexposr(self.hwexposr) self.fits_writer_program = "vegasFitsWriter"
def __init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test = False): """ Creates an instance of the vegas internals. """ # mode_number may be treated as a constant; the Player will # delete this backend object and create a new one on mode # change. Backend.__init__(self, theBank, theMode, theRoach , theValon, hpc_macs, unit_test) # Important to do this as soon as possible, so that status application # can change its data buffer format self.write_status(BACKEND='VEGAS') # In VEGAS mode, i_am_master means this particular backend # controls the switching signals. (self.bank is from base class.) self.i_am_master = self.bank.i_am_master # the switching signals builder self.ss = SwitchingSignals() # Parameters: self.setPolarization('SELF') self.setNumberChannels(self.mode.nchan) self.requested_integration_time = 1.0 # self.setValonFrequency(self.mode.frequency) # dependent values, computed from Parameters: self.nspectra = 1 self.nsubbands = 1 self.frequency_resolution = 0.0 self.fpga_clock = None self.fits_writer_process = None self.scan_length = 30.0 self.spec_tick = self.computeSpecTick() self.setHwExposr(self.mode.hwexposr) # setup the parameter dictionary/methods self.params["polarization" ] = self.setPolarization self.params["nchan" ] = self.setNumberChannels self.params["exposure" ] = self.setIntegrationTime self.params["hwexposr" ] = self.setHwExposr self.params["num_spectra" ] = self.setNumberSpectra # the status memory key/value pair dictionary self.sskeys = {} self.ss.set_spec_tick(self.spec_tick) self.ss.set_hwexposr(self.hwexposr) self.fits_writer_program = "vegasFitsWriter"
class VegasBackend(Backend): """ A class which implements some of the VEGAS specific parameter calculations. VegasBackend(theBank, theMode, theRoach = None, theValon = None) Where: * *theBank:* Instance of specific bank configuration data BankData. * *theMode:* Instance of specific mode configuration data ModeData. * *theRoach:* Instance of katcp_wrapper * *theValon:* instance of ValonKATCP * *unit_test:* Set to true to unit test. Will not attempt to talk to roach, shared memory, etc. """ def __init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test = False): """ Creates an instance of the vegas internals. """ # mode_number may be treated as a constant; the Player will # delete this backend object and create a new one on mode # change. Backend.__init__(self, theBank, theMode, theRoach , theValon, hpc_macs, unit_test) # Important to do this as soon as possible, so that status application # can change its data buffer format self.write_status(BACKEND='VEGAS') # In VEGAS mode, i_am_master means this particular backend # controls the switching signals. (self.bank is from base class.) self.i_am_master = self.bank.i_am_master # the switching signals builder self.ss = SwitchingSignals() # Parameters: self.setPolarization('SELF') self.setNumberChannels(self.mode.nchan) self.requested_integration_time = 1.0 # self.setValonFrequency(self.mode.frequency) # dependent values, computed from Parameters: self.nspectra = 1 self.nsubbands = 1 self.frequency_resolution = 0.0 self.fpga_clock = None self.fits_writer_process = None self.scan_length = 30.0 self.spec_tick = self.computeSpecTick() self.setHwExposr(self.mode.hwexposr) # setup the parameter dictionary/methods self.params["polarization" ] = self.setPolarization self.params["nchan" ] = self.setNumberChannels self.params["exposure" ] = self.setIntegrationTime self.params["hwexposr" ] = self.setHwExposr self.params["num_spectra" ] = self.setNumberSpectra # the status memory key/value pair dictionary self.sskeys = {} self.ss.set_spec_tick(self.spec_tick) self.ss.set_hwexposr(self.hwexposr) self.fits_writer_program = "vegasFitsWriter" def cleanup(self): """ This explicitly cleans up any child processes. This will be called by the player before deleting the backend object. """ print "VegasBackend: cleaning up hpc and fits writer." self.stop_hpc() self.stop_fits_writer() def computeSpecTick(self): """Returns the spec_tick value for this backend (the HBW value) """ st = float(self.nchan) / (convertToMHz(self.frequency) * 1e6) return st ### Methods to set user or mode specified parameters ### def setPolarization(self, polar): """ setPolarization(self, polar) *x* is a string 'CROSS', 'SELF1', 'SELF2', or 'SELF' """ try: self.num_stokes = {'CROSS': 4, 'SELF1': 1, 'SELF2': 1, 'SELF': 2}[polar] self.polarization = polar except KeyError: raise Exception("polarization string must be one of: CROSS, SELF1, SELF2, or SELF") def setNumberChannels(self, nchan): """ Sets the number of channels used by this mode. This is bof specific, and should match the requirements of the bof. """ self.nchan = nchan def setADCsnap(self, snap): """ """ self.adc_snap = snap # TBF: What does nspectra do? def setNumberSpectra(self, nspectra): """ Number of sub-bands. """ self.nspectra = nspectra def setIntegrationTime(self, int_time): """ Sets the integration time for each integration. """ self.requested_integration_time = int_time def setHwExposr(self, hwexposr): """Sets the hwexposr value, usually the value is set from the dibas.conf configuration file. Also sets the acc_len, which falls out of the computation to ensure hwexposure is an even multiple of spec_ticks (that multiple is acc_len). """ fpart, ipart = math.modf(hwexposr / self.spec_tick) if fpart >= 0.5: ipart = int(ipart) + 1 self.hwexposr = self.spec_tick * ipart self.acc_len = int(ipart) def needs_reset(self): """ For some BOF's there is a status register which can flag an error state. """ # treat this as an error state if self.roach is None: return False val = self.roach.read_int('status') if val & 0x01: return True else: return False # This function takes the current state of the backend and computes # some dependencies, in the order given; then it sets up the status # memory and roach dictionaries with all the values of parameters # and dependencies. Derived classes will override this function, but # call the parent function. Each derived class should do this, so # that the prepares cascade from base class on down. Only the # bottom-most class should then write the values to status memory, # HPC program, and roach. def prepare(self): """ This command writes calculated values to the hardware and status memory. This command should be run prior to the first scan to properly setup the hardware. The sequence of commands to set up a measurement is thus typically:: be.set_param(...) be.set_param(...) ... be.set_param(...) be.prepare() """ super(VegasBackend, self).prepare() # calculate the fpga_clock and sampler frequency self._fpga_clock_dep() self._sampler_frequency_dep() self._chan_bw_dep() self._obs_bw_dep() self._exposure_dep() # program I2C: input filters, noise source, noise or tone self.set_if_bits() # update all the status keywords needed for this mode: self._set_state_table_keywords() # record the switching signal specification for the roach: self.set_register(ssg_length=self.ss.total_duration_granules()) self.set_register(ssg_lut_bram=self.ss.packed_lut_string()) # set roach master/slave selects master = 1 if self.bank.i_am_master else 0 sssource = 0 # internal bsource = 0 # internal ssg_ms_sel = self.mode.master_slave_sels[master][sssource][bsource] self.set_register(ssg_ms_sel=ssg_ms_sel) # Algorithmic dependency methods, not normally called by a users def _fpga_clocks_per_spec_tick(self): return self.nchan / 8 def _exposure_dep(self): """Computes the actual exposure, based on the requested integration time. If the number of switching phases is > 1, then the actual exposure will be an integer multiple of the switching period. If the number of switching phases is == 1, then the exposure will be an integer multiple of hwexposr. """ init_exp = self.requested_integration_time fpga_clocks_per_spec_tick = self._fpga_clocks_per_spec_tick() if self.ss.number_phases() > 1: sw_period = self.ss.total_duration() sw_granules = self.ss.total_duration_granules() r = init_exp / sw_period fpart, ipart = math.modf(r) if fpart > (sw_period / 100.0): ipart = ipart + 1.0 self.expoclks = int(ipart * sw_granules * fpga_clocks_per_spec_tick) else: # number of phases = 1 r = init_exp / self.hwexposr fpart, ipart = math.modf(r) if fpart > (init_exp / 100.0): ipart = ipart + 1.0 self.expoclks = int(ipart * self.hwexposr * self.fpga_clock) self.exposure = float(self.expoclks) / self.fpga_clock def _chan_bw_dep(self): self.chan_bw = self.sampler_frequency / (self.nchan * 2) self.frequency_resolution = abs(self.chan_bw) def _fpga_clock_dep(self): """ Computes the FPGA clock. """ self.fpga_clock = convertToMHz(self.frequency) * 1e6 / 8 def _sampler_frequency_dep(self): """ Computes the effective frequency of the A/D sampler based on mode """ pass def clear_switching_states(self): """ resets/deletes the switching_states """ self.ss.clear_phases() return (True, self.ss.number_phases()) def add_switching_state(self, duration, blank = False, cal = False, sig_ref_1 = False): """ add_switching_state(duration, blank, cal, sig_ref_1): Add a description of one switching phase. Where: * *duration* is the length of this phase in seconds, * *blank* is the state of the blanking signal (True = blank, False = no blank) * *cal* is the state of the cal signal (True = cal, False = no cal) * *sig_ref_1* is 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_ref_1 = True) be.add_switching_state(0.09, blank = False, cal = True, sig_ref_1 = True) be.add_switching_state(0.01, blank = True, cal = True, sig_ref_1 = False) be.add_switching_state(0.09, blank = False, cal = True, sig_ref_1 = False) be.add_switching_state(0.01, blank = True, cal = False, sig_ref_1 = True) be.add_switching_state(0.09, blank = False, cal = False, sig_ref_1 = True) be.add_switching_state(0.01, blank = True, cal = False, sig_ref_1 = False) be.add_switching_state(0.09, blank = False, cal = False, sig_ref_1 = False) """ dur = int(math.ceil(duration / self.hwexposr) * self.hwexposr / self.spec_tick) self.ss.add_phase(dur = dur, bl = blank, cal = cal, sr1 = sig_ref_1) return (True, self.ss.number_phases()) 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)) ) """ try: self.nPhases = len(ss_list) self.clear_switching_states() for i in range(self.nPhases): this_start = ss_list[i][0] next_start = 1.0 if i + 1 == self.nPhases else ss_list[i + 1][0] duration = next_start * period - this_start * period blt = ss_list[i][3] nblt = duration - blt self.add_switching_state(blt, sig_ref_1 = ss_list[i][1], \ cal = ss_list[i][2], blank = True) self.add_switching_state(nblt, sig_ref_1 = ss_list[i][1], \ cal = ss_list[i][2], blank = False) except TypeError: # input error, leave it in a sane state. self.clear_switching_states() self.add_switching_state(1.0, blank = False, cal = False, sig_ref_1 = True) raise Exception("Possible syntax error with parameter 'ss_list'. " \ "If 'ss_list' only has one phase element, please " \ "use the '((),)' syntax instead of '(())'") return (True, self.ss.number_phases()) def show_switching_setup(self): srline="" clline="" blline="" calOnSym = "--------" calOffSym= "________" srSigSym = "--------" srRefSym = "________" blnkSym = "^ %.3f " noBlkSym = " " states = self.ss.gbt_phase_starts() print states for i in range(len(states['phase-starts'])): if states['sig/ref'][i]: srline = srline + srSigSym else: srline = srline + srRefSym if states['cal'][i]: clline = clline + calOnSym else: clline = clline + calOffSym if states['blanking'][i] > 0.0: blline = blline + blnkSym % states['blanking'][i] else: blline = blline + noBlkSym print "CAL :", clline print "SIG/REF:", srline print "BLANK :", blline def _setSSKeys(self): sskeys = {} states = self.ss.gbt_phase_starts() cal = states['cal'] sig_ref_1 = states['sig/ref'] self.nPhases = len(sig_ref_1) empty_list = [0 for i in range(self.nPhases)] # For sig_ref_2, or I or E as appropriate for i in range(len(states['blanking'])): sskeys['_SBLK_%02d' % (i+1)] = states['blanking'][i] for i in range(len(cal)): sskeys['_SCAL_%02d' % (i+1)] = cal[i] for i in range(len(states['phase-starts'])): sskeys['_SPHS_%02d' % (i+1)] = states['phase-starts'][i] for i in range(len(sig_ref_1)): sskeys['_SSRF_%02d' % (i+1)] = sig_ref_1[i] master = self.i_am_master # TBF! Make sure this exists... sskeys.update(self._setEcal(empty_list if master else cal)) sskeys.update(self._setEsigRef1(empty_list if master else sig_ref_1)) sskeys.update(self._setEsigRef2(empty_list)) sskeys.update(self._setIcal(cal if master else empty_list)) sskeys.update(self._setIsigRef1(sig_ref_1 if master else empty_list)) sskeys.update(self._setIsigRef2(empty_list)) return sskeys def _setEcal(self, cals): """ External CAL cals is a list of integers where 1 indicates the external cal is ON 0 indicates the external cal is OFF """ d = {} for i in range(len(cals)): d['_AECL_%02d' % (i+1)] = cals[i] return d def _setEsigRef1(self, sr): """ External Sig/Ref 1 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AESA_%02d' % (i+1)] = sr[i] return d def _setEsigRef2(self, sr): """ External Sig/Ref 2 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AESB_%02d' % (i+1)] = sr[i] return d def _setIcal(self, cals): """ Internal CAL cals is a list of integers where 1 indicates the external cal is ON 0 indicates the external cal is OFF """ d = {} for i in range(len(cals)): d['_AICL_%02d' % (i+1)] = cals[i] return d def _setIsigRef1(self, sr): """ Internal Sig/Ref 1 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AISA_%02d' % (i+1)] = sr[i] return d def _setIsigRef2(self, sr): """ Internal Sig/Ref 2 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AISB_%02d' % (i+1)] = sr[i] return d def _obs_bw_dep(self): """ Observation bandwidth dependency """ self.obs_bw = self.chan_bw * self.nchan def _set_state_table_keywords(self): """ Gather status sets here Not yet sure what to place here... """ print "_set_state_table_keywords() called." DEFAULT_VALUE = "unspecified" self.set_status(BW_MODE = DEFAULT_VALUE) self.set_status(CAL_DCYC = DEFAULT_VALUE) self.set_status(CAL_FREQ = DEFAULT_VALUE) self.set_status(CAL_MODE = DEFAULT_VALUE) self.set_status(CAL_PHS = DEFAULT_VALUE) self.set_status(CHAN_BW = DEFAULT_VALUE) self.set_status(DATADIR = DEFAULT_VALUE) self.set_status(DATAHOST = DEFAULT_VALUE) self.set_status(DATAPORT = DEFAULT_VALUE) self.set_status(EFSAMPFR = DEFAULT_VALUE) self.set_status(EXPOSURE = DEFAULT_VALUE) self.set_status(FILENUM = DEFAULT_VALUE) self.set_status(FPGACLK = DEFAULT_VALUE) self.set_status(HWEXPOSR = DEFAULT_VALUE) self.set_status(M_STTMJD = 0) self.set_status(M_STTOFF = 0) self.set_status(NBITS = 8) self.set_status(NBITSADC = 8) self.set_status(NCHAN = DEFAULT_VALUE) self.set_status(NPKT = DEFAULT_VALUE) self.set_status(NPOL = DEFAULT_VALUE) self.set_status(NSUBBAND = DEFAULT_VALUE) self.set_status(OBSBW = DEFAULT_VALUE) self.set_status(OBSFREQ = DEFAULT_VALUE) self.set_status(OBSNCHAN = DEFAULT_VALUE) self.set_status(OBS_MODE = DEFAULT_VALUE) self.set_status(OBSERVER = DEFAULT_VALUE) self.set_status(OBSID = DEFAULT_VALUE) self.set_status(PKTFMT = DEFAULT_VALUE) self.set_status(SRC_NAME = DEFAULT_VALUE) self.set_status(RA = DEFAULT_VALUE) self.set_status(DEC = DEFAULT_VALUE) self.set_status(RA_STR = DEFAULT_VALUE) self.set_status(DEC_STR = DEFAULT_VALUE) self.set_status(SUB0FREQ = DEFAULT_VALUE) self.set_status(SUB1FREQ = DEFAULT_VALUE) self.set_status(SUB2FREQ = DEFAULT_VALUE) self.set_status(SUB3FREQ = DEFAULT_VALUE) self.set_status(SUB4FREQ = DEFAULT_VALUE) self.set_status(SUB5FREQ = DEFAULT_VALUE) self.set_status(SUB6FREQ = DEFAULT_VALUE) self.set_status(SUB7FREQ = DEFAULT_VALUE) self.set_status(SWVER = DEFAULT_VALUE) self.set_status(TELESCOP = DEFAULT_VALUE) if self.mode.shmkvpairs: self.set_status(**self.mode.shmkvpairs) # set the switching signal stuff: self.set_status(**self._setSSKeys()) # all the rest... self.set_status(OBSERVER = self.observer) self.set_status(SRC_NAME = self.source) if self.source_ra_dec: ra = self.source_ra_dec[0] dec = self.source_ra_dec[1] self.set_status(RA = ra.degrees) self.set_status(DEC = dec.degrees) self.set_status(RA_STR = "%02i:%02i:%05.3f" % ra.hms) self.set_status(DEC_STR = apw.degreesToString(dec.degrees)) self.set_status(TELESCOP = self.telescope) self.set_status(BOFFILE = str(self.bof_file)) self.set_status(CHAN_BW = str(self.chan_bw)) self.set_status(EFSAMPFR = str(self.sampler_frequency)) self.set_status(EXPOSURE = str(self.exposure)) self.set_status(FPGACLK = str(self.fpga_clock)) self.set_status(OBSNCHAN = str(self.nchan)) self.set_status(HWEXPOSR = str(self.hwexposr)) self.set_status(OBSBW = self.obs_bw) self.set_status(PKTFMT = "SPEAD") self.set_status(NCHAN = str(self.nchan)) self.set_status(NPOL = str(2)) self.set_status(NSUBBAND = self.nsubbands) # convertToMHz() normalizes the frequency to MHz, just in case # it is provided as Hz. So this will work in either case. self.set_status(SUB0FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB1FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB2FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB3FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB4FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB5FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB6FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB7FREQ = convertToMHz(self.frequency) * 1e6 / 2) self.set_status(BASE_BW = self.filter_bw) # From MODE self.set_status(BANKNAM = self.bank.name if self.bank else 'NOBANK') self.set_status(MODENUM = str(self.mode.name)) # from MODE self.set_status(NOISESRC = "OFF") # TBD?? self.set_status(NUMPHASE = str(self.nPhases)) self.set_status(SWPERIOD = str(self.ss.total_duration())) self.set_status(SWMASTER = "VEGAS") # TBD self.set_status(POLARIZE = self.polarization) self.set_status(CRPIX1 = str(self.nchan/2 + 1)) self.set_status(SWPERINT = str(int(self.exposure \ / self.ss.total_duration() + 0.5))) self.set_status(NMSTOKES = str(self.num_stokes)) # should this get set by Backend? self.set_status(DATAHOST = self.datahost) self.set_status(DATAPORT = self.dataport) self.set_status(DATADIR = self.dataroot) self.set_status(PROJID = self.projectid) self.set_status(SCANLEN = self.scan_length) self.set_status(CAL_FREQ = self.cal_freq) for i in range(8): self.set_status(**{"_MCR1_%02d" % (i+1): str(self.chan_bw), "_MCDL_%02d" % (i+1): str(self.chan_bw), "_MFQR_%02d" % (i+1): str(self.frequency_resolution)}) def earliest_start(self): """ Reports earliest possible start time, in UTC, for this backend. """ now = datetime.utcnow() earliest_start = self.round_second_up(now + self.mode.needed_arm_delay + timedelta(seconds=2)) return earliest_start def start(self, starttime = None): """ start(self, starttime = None) *starttime:* a *datetime* object, representing a UTC start time. --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()* will 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 this function cannot start the measurement by *starttime*, an exception is thrown. """ if self.scan_running: return (False, "Scan already started.") now = datetime.utcnow() earliest_start = self.round_second_up(now) + self.mode.needed_arm_delay print "Earliest time is",earliest_start if starttime: if type(starttime) == tuple or type(starttime) == list: starttime = datetime(*starttime) if type(starttime) != datetime: raise Exception("starttime must be a datetime or datetime compatible tuple or list.") # Force the start time to the next 1-second boundary. The # ROACH is triggered by a 1PPS signal. starttime = self.round_second_up(starttime) # starttime must be 'needed_arm_delay' seconds from now. if starttime < earliest_start: raise Exception("Not enough time to arm ROACH. Start: %s, earliest possible start: %s" \ % (str(starttime), str(earliest_start))) else: # No start time provided starttime = earliest_start self.start_time = starttime max_delay = self.mode.needed_arm_delay - timedelta(microseconds = 1500000) print "MAX DELAY!!!!!!!!",max_delay print now, starttime, max_delay # if simulating, just sleep until start time and return if self.test_mode: sleeptime = self.start_time - now sleep(sleeptime.seconds) return (True, "Successfully started roach for starttime=%s" % str(self.start_time)) # everything OK now, starttime is valid, go through the start procedure. if self.hpc_process is None: self.start_hpc() if self.fits_writer_process is None: self.start_fits_writer() # The CODD bof's don't have a status register #if self.needs_reset(): # self.reset_roach() self.hpc_cmd('START') self.fits_writer_cmd('START') #status,wait = self._wait_for_status('NETSTAT', 'receiving', max_delay) #if not status: # self.hpc_cmd('STOP') # self.fits_writer_cmd('STOP') # raise Exception("start(): timed out waiting for 'NETSTAT=receiving'") # # print "start(): waited %s for HPC program to be ready." % str(wait) # now sleep until arm_time # PPS PPS # ________|__________|_____ # ^ ^ # arm_time start_time arm_time = starttime - timedelta(microseconds = 900000) now = datetime.utcnow() if now > arm_time: self.hpc_cmd('STOP') self.fits_writer_cmd('STOP') raise Exception("start(): deadline missed, arm time is in the past.") tdelta = arm_time - now sleep_time = tdelta.seconds + tdelta.microseconds / 1e6 time.sleep(sleep_time) # We're now within a second of the desired start time. Arm: self.arm_roach() self.scan_running = True self.write_status(ACCBLKOU='-') return (True, "Successfully started roach for starttime=%s" % str(self.start_time)) def stop(self): """ Stops a scan. """ if self.scan_running: self.hpc_cmd('stop') self.fits_writer_cmd('stop') self.scan_running = False self.write_status(DISKSTAT='-') return (True, "Scan ended") if self.monitor_mode: self.hpc_cmd('stop') self.monitor_mode = False return (True, "Ending monitor mode.") return (False, "No scan running!") def monitor(self): """ Tells DAQ program to enter monitor mode. """ self.hpc_cmd('monitor') self.monitor_mode = True return (True, "Start monitor mode.") def scan_status(self): """ Returns the current state of a scan, as a tuple: (scan_running (bool), 'NETSTAT=' (string), and 'DISKSTAT=' (string)) """ return (self.scan_running, 'NETSTAT=%s' % self.get_status('NETSTAT'), 'DISKSTAT=%s' % self.get_status('DISKSTAT')) def start_fits_writer(self): """ start_fits_writer() Starts the fits writer program running. Stops any previously running instance. """ if self.test_mode: return self.stop_fits_writer() #fits_writer_program = "vegasFitsWriter" sp_path = self.dibas_dir + '/exec/x86_64-linux/' + self.fits_writer_program print "starting bfFitsWriter sp_path", sp_path self.fits_writer_process = subprocess.Popen((sp_path, ), stdin=subprocess.PIPE) def stop_fits_writer(self): """ stop_fits_writer() Stops the fits writer program and make it exit. To stop an observation use 'stop()' instead. """ if self.test_mode: return if self.fits_writer_process is None: return False # Nothing to do try: # First ask nicely self.fits_writer_process.communicate("quit\n") time.sleep(1) # Kill if necessary if self.fits_writer_process.poll() == None: # still running, try once more self.fits_writer_process.terminate() time.sleep(1) if self.fits_writer_process.poll() is not None: killed = True else: self.fits_writer_process.kill() killed = True else: killed = False self.fits_writer_process = None except OSError, e: print "While killing child process:", e killed = False finally:
class VegasBackend(Backend): """ A class which implements some of the VEGAS specific parameter calculations. VegasBackend(theBank, theMode, theRoach = None, theValon = None) Where: * *theBank:* Instance of specific bank configuration data BankData. * *theMode:* Instance of specific mode configuration data ModeData. * *theRoach:* Instance of katcp_wrapper * *theValon:* instance of ValonKATCP * *unit_test:* Set to true to unit test. Will not attempt to talk to roach, shared memory, etc. """ def __init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test=False): """ Creates an instance of the vegas internals. """ # mode_number may be treated as a constant; the Player will # delete this backend object and create a new one on mode # change. Backend.__init__(self, theBank, theMode, theRoach, theValon, hpc_macs, unit_test) # Important to do this as soon as possible, so that status application # can change its data buffer format self.write_status(BACKEND='VEGAS') # In VEGAS mode, i_am_master means this particular backend # controls the switching signals. (self.bank is from base class.) self.i_am_master = self.bank.i_am_master # the switching signals builder self.ss = SwitchingSignals() # Parameters: self.setPolarization('SELF') self.setNumberChannels(self.mode.nchan) self.requested_integration_time = 1.0 # self.setValonFrequency(self.mode.frequency) # dependent values, computed from Parameters: self.nspectra = 1 self.nsubbands = 1 self.frequency_resolution = 0.0 self.fpga_clock = None self.fits_writer_process = None self.scan_length = 30.0 self.spec_tick = self.computeSpecTick() self.setHwExposr(self.mode.hwexposr) # setup the parameter dictionary/methods self.params["polarization"] = self.setPolarization self.params["nchan"] = self.setNumberChannels self.params["exposure"] = self.setIntegrationTime self.params["hwexposr"] = self.setHwExposr self.params["num_spectra"] = self.setNumberSpectra # the status memory key/value pair dictionary self.sskeys = {} self.ss.set_spec_tick(self.spec_tick) self.ss.set_hwexposr(self.hwexposr) self.fits_writer_program = "vegasFitsWriter" def cleanup(self): """ This explicitly cleans up any child processes. This will be called by the player before deleting the backend object. """ print "VegasBackend: cleaning up hpc and fits writer." self.stop_hpc() self.stop_fits_writer() def computeSpecTick(self): """Returns the spec_tick value for this backend (the HBW value) """ st = float(self.nchan) / (convertToMHz(self.frequency) * 1e6) return st ### Methods to set user or mode specified parameters ### def setPolarization(self, polar): """ setPolarization(self, polar) *x* is a string 'CROSS', 'SELF1', 'SELF2', or 'SELF' """ try: self.num_stokes = { 'CROSS': 4, 'SELF1': 1, 'SELF2': 1, 'SELF': 2 }[polar] self.polarization = polar except KeyError: raise Exception( "polarization string must be one of: CROSS, SELF1, SELF2, or SELF" ) def setNumberChannels(self, nchan): """ Sets the number of channels used by this mode. This is bof specific, and should match the requirements of the bof. """ self.nchan = nchan def setADCsnap(self, snap): """ """ self.adc_snap = snap # TBF: What does nspectra do? def setNumberSpectra(self, nspectra): """ Number of sub-bands. """ self.nspectra = nspectra def setIntegrationTime(self, int_time): """ Sets the integration time for each integration. """ self.requested_integration_time = int_time def setHwExposr(self, hwexposr): """Sets the hwexposr value, usually the value is set from the dibas.conf configuration file. Also sets the acc_len, which falls out of the computation to ensure hwexposure is an even multiple of spec_ticks (that multiple is acc_len). """ fpart, ipart = math.modf(hwexposr / self.spec_tick) if fpart >= 0.5: ipart = int(ipart) + 1 self.hwexposr = self.spec_tick * ipart self.acc_len = int(ipart) def needs_reset(self): """ For some BOF's there is a status register which can flag an error state. """ # treat this as an error state if self.roach is None: return False val = self.roach.read_int('status') if val & 0x01: return True else: return False # This function takes the current state of the backend and computes # some dependencies, in the order given; then it sets up the status # memory and roach dictionaries with all the values of parameters # and dependencies. Derived classes will override this function, but # call the parent function. Each derived class should do this, so # that the prepares cascade from base class on down. Only the # bottom-most class should then write the values to status memory, # HPC program, and roach. def prepare(self): """ This command writes calculated values to the hardware and status memory. This command should be run prior to the first scan to properly setup the hardware. The sequence of commands to set up a measurement is thus typically:: be.set_param(...) be.set_param(...) ... be.set_param(...) be.prepare() """ super(VegasBackend, self).prepare() # calculate the fpga_clock and sampler frequency self._fpga_clock_dep() self._sampler_frequency_dep() self._chan_bw_dep() self._obs_bw_dep() self._exposure_dep() # program I2C: input filters, noise source, noise or tone self.set_if_bits() # update all the status keywords needed for this mode: self._set_state_table_keywords() # record the switching signal specification for the roach: self.set_register(ssg_length=self.ss.total_duration_granules()) self.set_register(ssg_lut_bram=self.ss.packed_lut_string()) # set roach master/slave selects master = 1 if self.bank.i_am_master else 0 sssource = 0 # internal bsource = 0 # internal ssg_ms_sel = self.mode.master_slave_sels[master][sssource][bsource] self.set_register(ssg_ms_sel=ssg_ms_sel) # Algorithmic dependency methods, not normally called by a users def _fpga_clocks_per_spec_tick(self): return self.nchan / 8 def _exposure_dep(self): """Computes the actual exposure, based on the requested integration time. If the number of switching phases is > 1, then the actual exposure will be an integer multiple of the switching period. If the number of switching phases is == 1, then the exposure will be an integer multiple of hwexposr. """ init_exp = self.requested_integration_time fpga_clocks_per_spec_tick = self._fpga_clocks_per_spec_tick() if self.ss.number_phases() > 1: sw_period = self.ss.total_duration() sw_granules = self.ss.total_duration_granules() r = init_exp / sw_period fpart, ipart = math.modf(r) if fpart > (sw_period / 100.0): ipart = ipart + 1.0 self.expoclks = int(ipart * sw_granules * fpga_clocks_per_spec_tick) else: # number of phases = 1 r = init_exp / self.hwexposr fpart, ipart = math.modf(r) if fpart > (init_exp / 100.0): ipart = ipart + 1.0 self.expoclks = int(ipart * self.hwexposr * self.fpga_clock) self.exposure = float(self.expoclks) / self.fpga_clock def _chan_bw_dep(self): self.chan_bw = self.sampler_frequency / (self.nchan * 2) self.frequency_resolution = abs(self.chan_bw) def _fpga_clock_dep(self): """ Computes the FPGA clock. """ self.fpga_clock = convertToMHz(self.frequency) * 1e6 / 8 def _sampler_frequency_dep(self): """ Computes the effective frequency of the A/D sampler based on mode """ pass def clear_switching_states(self): """ resets/deletes the switching_states """ self.ss.clear_phases() return (True, self.ss.number_phases()) def add_switching_state(self, duration, blank=False, cal=False, sig_ref_1=False): """ add_switching_state(duration, blank, cal, sig_ref_1): Add a description of one switching phase. Where: * *duration* is the length of this phase in seconds, * *blank* is the state of the blanking signal (True = blank, False = no blank) * *cal* is the state of the cal signal (True = cal, False = no cal) * *sig_ref_1* is 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_ref_1 = True) be.add_switching_state(0.09, blank = False, cal = True, sig_ref_1 = True) be.add_switching_state(0.01, blank = True, cal = True, sig_ref_1 = False) be.add_switching_state(0.09, blank = False, cal = True, sig_ref_1 = False) be.add_switching_state(0.01, blank = True, cal = False, sig_ref_1 = True) be.add_switching_state(0.09, blank = False, cal = False, sig_ref_1 = True) be.add_switching_state(0.01, blank = True, cal = False, sig_ref_1 = False) be.add_switching_state(0.09, blank = False, cal = False, sig_ref_1 = False) """ dur = int( math.ceil(duration / self.hwexposr) * self.hwexposr / self.spec_tick) self.ss.add_phase(dur=dur, bl=blank, cal=cal, sr1=sig_ref_1) return (True, self.ss.number_phases()) 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)) ) """ try: self.nPhases = len(ss_list) self.clear_switching_states() for i in range(self.nPhases): this_start = ss_list[i][0] next_start = 1.0 if i + 1 == self.nPhases else ss_list[i + 1][0] duration = next_start * period - this_start * period blt = ss_list[i][3] nblt = duration - blt self.add_switching_state(blt, sig_ref_1 = ss_list[i][1], \ cal = ss_list[i][2], blank = True) self.add_switching_state(nblt, sig_ref_1 = ss_list[i][1], \ cal = ss_list[i][2], blank = False) except TypeError: # input error, leave it in a sane state. self.clear_switching_states() self.add_switching_state(1.0, blank=False, cal=False, sig_ref_1=True) raise Exception("Possible syntax error with parameter 'ss_list'. " \ "If 'ss_list' only has one phase element, please " \ "use the '((),)' syntax instead of '(())'") return (True, self.ss.number_phases()) def show_switching_setup(self): srline = "" clline = "" blline = "" calOnSym = "--------" calOffSym = "________" srSigSym = "--------" srRefSym = "________" blnkSym = "^ %.3f " noBlkSym = " " states = self.ss.gbt_phase_starts() print states for i in range(len(states['phase-starts'])): if states['sig/ref'][i]: srline = srline + srSigSym else: srline = srline + srRefSym if states['cal'][i]: clline = clline + calOnSym else: clline = clline + calOffSym if states['blanking'][i] > 0.0: blline = blline + blnkSym % states['blanking'][i] else: blline = blline + noBlkSym print "CAL :", clline print "SIG/REF:", srline print "BLANK :", blline def _setSSKeys(self): sskeys = {} states = self.ss.gbt_phase_starts() cal = states['cal'] sig_ref_1 = states['sig/ref'] self.nPhases = len(sig_ref_1) empty_list = [0 for i in range(self.nPhases) ] # For sig_ref_2, or I or E as appropriate for i in range(len(states['blanking'])): sskeys['_SBLK_%02d' % (i + 1)] = states['blanking'][i] for i in range(len(cal)): sskeys['_SCAL_%02d' % (i + 1)] = cal[i] for i in range(len(states['phase-starts'])): sskeys['_SPHS_%02d' % (i + 1)] = states['phase-starts'][i] for i in range(len(sig_ref_1)): sskeys['_SSRF_%02d' % (i + 1)] = sig_ref_1[i] master = self.i_am_master # TBF! Make sure this exists... sskeys.update(self._setEcal(empty_list if master else cal)) sskeys.update(self._setEsigRef1(empty_list if master else sig_ref_1)) sskeys.update(self._setEsigRef2(empty_list)) sskeys.update(self._setIcal(cal if master else empty_list)) sskeys.update(self._setIsigRef1(sig_ref_1 if master else empty_list)) sskeys.update(self._setIsigRef2(empty_list)) return sskeys def _setEcal(self, cals): """ External CAL cals is a list of integers where 1 indicates the external cal is ON 0 indicates the external cal is OFF """ d = {} for i in range(len(cals)): d['_AECL_%02d' % (i + 1)] = cals[i] return d def _setEsigRef1(self, sr): """ External Sig/Ref 1 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AESA_%02d' % (i + 1)] = sr[i] return d def _setEsigRef2(self, sr): """ External Sig/Ref 2 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AESB_%02d' % (i + 1)] = sr[i] return d def _setIcal(self, cals): """ Internal CAL cals is a list of integers where 1 indicates the external cal is ON 0 indicates the external cal is OFF """ d = {} for i in range(len(cals)): d['_AICL_%02d' % (i + 1)] = cals[i] return d def _setIsigRef1(self, sr): """ Internal Sig/Ref 1 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AISA_%02d' % (i + 1)] = sr[i] return d def _setIsigRef2(self, sr): """ Internal Sig/Ref 2 sr is a list of integers where 1 indicates REF 0 indicates SIG """ d = {} for i in range(len(sr)): d['_AISB_%02d' % (i + 1)] = sr[i] return d def _obs_bw_dep(self): """ Observation bandwidth dependency """ self.obs_bw = self.chan_bw * self.nchan def _set_state_table_keywords(self): """ Gather status sets here Not yet sure what to place here... """ print "_set_state_table_keywords() called." DEFAULT_VALUE = "unspecified" self.set_status(BW_MODE=DEFAULT_VALUE) self.set_status(CAL_DCYC=DEFAULT_VALUE) self.set_status(CAL_FREQ=DEFAULT_VALUE) self.set_status(CAL_MODE=DEFAULT_VALUE) self.set_status(CAL_PHS=DEFAULT_VALUE) self.set_status(CHAN_BW=DEFAULT_VALUE) self.set_status(DATADIR=DEFAULT_VALUE) self.set_status(DATAHOST=DEFAULT_VALUE) self.set_status(DATAPORT=DEFAULT_VALUE) self.set_status(EFSAMPFR=DEFAULT_VALUE) self.set_status(EXPOSURE=DEFAULT_VALUE) self.set_status(FILENUM=DEFAULT_VALUE) self.set_status(FPGACLK=DEFAULT_VALUE) self.set_status(HWEXPOSR=DEFAULT_VALUE) self.set_status(M_STTMJD=0) self.set_status(M_STTOFF=0) self.set_status(NBITS=8) self.set_status(NBITSADC=8) self.set_status(NCHAN=DEFAULT_VALUE) self.set_status(NPKT=DEFAULT_VALUE) self.set_status(NPOL=DEFAULT_VALUE) self.set_status(NSUBBAND=DEFAULT_VALUE) self.set_status(OBSBW=DEFAULT_VALUE) self.set_status(OBSFREQ=DEFAULT_VALUE) self.set_status(OBSNCHAN=DEFAULT_VALUE) self.set_status(OBS_MODE=DEFAULT_VALUE) self.set_status(OBSERVER=DEFAULT_VALUE) self.set_status(OBSID=DEFAULT_VALUE) self.set_status(PKTFMT=DEFAULT_VALUE) self.set_status(SRC_NAME=DEFAULT_VALUE) self.set_status(RA=DEFAULT_VALUE) self.set_status(DEC=DEFAULT_VALUE) self.set_status(RA_STR=DEFAULT_VALUE) self.set_status(DEC_STR=DEFAULT_VALUE) self.set_status(SUB0FREQ=DEFAULT_VALUE) self.set_status(SUB1FREQ=DEFAULT_VALUE) self.set_status(SUB2FREQ=DEFAULT_VALUE) self.set_status(SUB3FREQ=DEFAULT_VALUE) self.set_status(SUB4FREQ=DEFAULT_VALUE) self.set_status(SUB5FREQ=DEFAULT_VALUE) self.set_status(SUB6FREQ=DEFAULT_VALUE) self.set_status(SUB7FREQ=DEFAULT_VALUE) self.set_status(SWVER=DEFAULT_VALUE) self.set_status(TELESCOP=DEFAULT_VALUE) if self.mode.shmkvpairs: self.set_status(**self.mode.shmkvpairs) # set the switching signal stuff: self.set_status(**self._setSSKeys()) # all the rest... self.set_status(OBSERVER=self.observer) self.set_status(SRC_NAME=self.source) if self.source_ra_dec: ra = self.source_ra_dec[0] dec = self.source_ra_dec[1] self.set_status(RA=ra.degrees) self.set_status(DEC=dec.degrees) self.set_status(RA_STR="%02i:%02i:%05.3f" % ra.hms) self.set_status(DEC_STR=apw.degreesToString(dec.degrees)) self.set_status(TELESCOP=self.telescope) self.set_status(BOFFILE=str(self.bof_file)) self.set_status(CHAN_BW=str(self.chan_bw)) self.set_status(EFSAMPFR=str(self.sampler_frequency)) self.set_status(EXPOSURE=str(self.exposure)) self.set_status(FPGACLK=str(self.fpga_clock)) self.set_status(OBSNCHAN=str(self.nchan)) self.set_status(HWEXPOSR=str(self.hwexposr)) self.set_status(OBSBW=self.obs_bw) self.set_status(PKTFMT="SPEAD") self.set_status(NCHAN=str(self.nchan)) self.set_status(NPOL=str(2)) self.set_status(NSUBBAND=self.nsubbands) # convertToMHz() normalizes the frequency to MHz, just in case # it is provided as Hz. So this will work in either case. self.set_status(SUB0FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB1FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB2FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB3FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB4FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB5FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB6FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(SUB7FREQ=convertToMHz(self.frequency) * 1e6 / 2) self.set_status(BASE_BW=self.filter_bw) # From MODE self.set_status(BANKNAM=self.bank.name if self.bank else 'NOBANK') self.set_status(MODENUM=str(self.mode.name)) # from MODE self.set_status(NOISESRC="OFF") # TBD?? self.set_status(NUMPHASE=str(self.nPhases)) self.set_status(SWPERIOD=str(self.ss.total_duration())) self.set_status(SWMASTER="VEGAS") # TBD self.set_status(POLARIZE=self.polarization) self.set_status(CRPIX1=str(self.nchan / 2 + 1)) self.set_status(SWPERINT = str(int(self.exposure \ / self.ss.total_duration() + 0.5))) self.set_status(NMSTOKES=str(self.num_stokes)) # should this get set by Backend? self.set_status(DATAHOST=self.datahost) self.set_status(DATAPORT=self.dataport) self.set_status(DATADIR=self.dataroot) self.set_status(PROJID=self.projectid) self.set_status(SCANLEN=self.scan_length) self.set_status(CAL_FREQ=self.cal_freq) for i in range(8): self.set_status( **{ "_MCR1_%02d" % (i + 1): str(self.chan_bw), "_MCDL_%02d" % (i + 1): str(self.chan_bw), "_MFQR_%02d" % (i + 1): str(self.frequency_resolution) }) def earliest_start(self): """ Reports earliest possible start time, in UTC, for this backend. """ now = datetime.utcnow() earliest_start = self.round_second_up(now + self.mode.needed_arm_delay + timedelta(seconds=2)) return earliest_start def start(self, starttime=None): """ start(self, starttime = None) *starttime:* a *datetime* object, representing a UTC start time. --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()* will 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 this function cannot start the measurement by *starttime*, an exception is thrown. """ if self.scan_running: return (False, "Scan already started.") now = datetime.utcnow() earliest_start = self.round_second_up(now) + self.mode.needed_arm_delay if starttime: if type(starttime) == tuple or type(starttime) == list: starttime = datetime(*starttime) if type(starttime) != datetime: raise Exception( "starttime must be a datetime or datetime compatible tuple or list." ) # Force the start time to the next 1-second boundary. The # ROACH is triggered by a 1PPS signal. starttime = self.round_second_up(starttime) # starttime must be 'needed_arm_delay' seconds from now. if starttime < earliest_start: raise Exception("Not enough time to arm ROACH. Start: %s, earliest possible start: %s" \ % (str(starttime), str(earliest_start))) else: # No start time provided starttime = earliest_start self.start_time = starttime max_delay = self.mode.needed_arm_delay - timedelta( microseconds=1500000) print now, starttime, max_delay # if simulating, just sleep until start time and return if self.test_mode: sleeptime = self.start_time - now sleep(sleeptime.seconds) return (True, "Successfully started roach for starttime=%s" % str(self.start_time)) # everything OK now, starttime is valid, go through the start procedure. if self.hpc_process is None: self.start_hpc() if self.fits_writer_process is None: self.start_fits_writer() # The CODD bof's don't have a status register #if self.needs_reset(): # self.reset_roach() self.hpc_cmd('START') self.fits_writer_cmd('START') #status,wait = self._wait_for_status('NETSTAT', 'receiving', max_delay) #if not status: # self.hpc_cmd('STOP') # self.fits_writer_cmd('STOP') # raise Exception("start(): timed out waiting for 'NETSTAT=receiving'") # # print "start(): waited %s for HPC program to be ready." % str(wait) # now sleep until arm_time # PPS PPS # ________|__________|_____ # ^ ^ # arm_time start_time arm_time = starttime - timedelta(microseconds=900000) now = datetime.utcnow() if now > arm_time: self.hpc_cmd('STOP') self.fits_writer_cmd('STOP') raise Exception( "start(): deadline missed, arm time is in the past.") tdelta = arm_time - now sleep_time = tdelta.seconds + tdelta.microseconds / 1e6 time.sleep(sleep_time) # We're now within a second of the desired start time. Arm: self.arm_roach() self.scan_running = True self.write_status(ACCBLKOU='-') return (True, "Successfully started roach for starttime=%s" % str(self.start_time)) def stop(self): """ Stops a scan. """ if self.scan_running: self.hpc_cmd('stop') self.fits_writer_cmd('stop') self.scan_running = False self.write_status(DISKSTAT='-') return (True, "Scan ended") if self.monitor_mode: self.hpc_cmd('stop') self.monitor_mode = False return (True, "Ending monitor mode.") return (False, "No scan running!") def monitor(self): """ Tells DAQ program to enter monitor mode. """ self.hpc_cmd('monitor') self.monitor_mode = True return (True, "Start monitor mode.") def scan_status(self): """ Returns the current state of a scan, as a tuple: (scan_running (bool), 'NETSTAT=' (string), and 'DISKSTAT=' (string)) """ return (self.scan_running, 'NETSTAT=%s' % self.get_status('NETSTAT'), 'DISKSTAT=%s' % self.get_status('DISKSTAT')) def start_fits_writer(self): """ start_fits_writer() Starts the fits writer program running. Stops any previously running instance. """ if self.test_mode: return self.stop_fits_writer() #fits_writer_program = "vegasFitsWriter" sp_path = self.dibas_dir + '/exec/x86_64-linux/' + self.fits_writer_program print "starting bfFitsWriter sp_path", sp_path self.fits_writer_process = subprocess.Popen((sp_path, ), stdin=subprocess.PIPE) def stop_fits_writer(self): """ stop_fits_writer() Stops the fits writer program and make it exit. To stop an observation use 'stop()' instead. """ if self.test_mode: return if self.fits_writer_process is None: return False # Nothing to do try: # First ask nicely self.fits_writer_process.communicate("quit\n") time.sleep(1) # Kill if necessary if self.fits_writer_process.poll() == None: # still running, try once more self.fits_writer_process.terminate() time.sleep(1) if self.fits_writer_process.poll() is not None: killed = True else: self.fits_writer_process.kill() killed = True else: killed = False self.fits_writer_process = None except OSError, e: print "While killing child process:", e killed = False finally: