def run(self, starttime=None, endtime=None, duration=None, period=10): op = self.op # window in seconds that we allow for setup time so that we don't # issue a start command that's in the past when the flowgraph starts SETUP_TIME = 10 # print current time and NTP status if op.verbose and sys.platform.startswith("linux"): try: call(("timedatectl", "status")) except OSError: # no timedatectl command, ignore pass # parse time arguments st = drf.util.parse_identifier_to_time(starttime) if st is not None: # find next suitable start time by cycle repeat period now = datetime.utcnow() now = now.replace(tzinfo=pytz.utc) soon = now + timedelta(seconds=SETUP_TIME) diff = max(soon - st, timedelta(0)).total_seconds() periods_until_next = (diff - 1) // period + 1 st = st + timedelta(seconds=periods_until_next * period) if op.verbose: ststr = st.strftime("%a %b %d %H:%M:%S %Y") stts = (st - drf.util.epoch).total_seconds() print("Start time: {0} ({1})".format(ststr, stts)) et = drf.util.parse_identifier_to_time(endtime, ref_datetime=st) if et is not None: if op.verbose: etstr = et.strftime("%a %b %d %H:%M:%S %Y") etts = (et - drf.util.epoch).total_seconds() print("End time: {0} ({1})".format(etstr, etts)) if (et < (pytz.utc.localize(datetime.utcnow()) + timedelta(seconds=SETUP_TIME))) or (st is not None and et <= st): raise ValueError("End time is before launch time!") if op.realtime: r = gr.enable_realtime_scheduling() if op.verbose: if r == gr.RT_OK: print("Realtime scheduling enabled") else: print("Note: failed to enable realtime scheduling") # wait for the start time if it is not past while (st is not None) and ((st - pytz.utc.localize(datetime.utcnow())) > timedelta(seconds=SETUP_TIME)): ttl = int( (st - pytz.utc.localize(datetime.utcnow())).total_seconds()) if (ttl % 10) == 0: print("Standby {0} s remaining...".format(ttl)) sys.stdout.flush() time.sleep(1) # get UHD USRP source u = self._usrp_setup() # set device time tt = time.time() if op.sync: # wait until time 0.2 to 0.5 past full second, then latch # we have to trust NTP to be 0.2 s accurate while tt - math.floor(tt) < 0.2 or tt - math.floor(tt) > 0.3: time.sleep(0.01) tt = time.time() if op.verbose: print("Latching at " + str(tt)) # waits for the next pps to happen # (at time math.ceil(tt)) # then sets the time for the subsequent pps # (at time math.ceil(tt) + 1.0) u.set_time_unknown_pps(uhd.time_spec(math.ceil(tt) + 1.0)) # wait for time registers to be in known state time.sleep(math.ceil(tt) - tt + 1.0) else: u.set_time_now(uhd.time_spec(tt), uhd.ALL_MBOARDS) # wait for time registers to be in known state time.sleep(1) # set launch time # (at least 1 second out so USRP start time can be set properly and # there is time to set up flowgraph) if st is not None: lt = st else: now = pytz.utc.localize(datetime.utcnow()) # launch on integer second by default for convenience (ceil + 1) lt = now.replace(microsecond=0) + timedelta(seconds=2) ltts = (lt - drf.util.epoch).total_seconds() # adjust launch time forward so it falls on an exact sample since epoch lt_samples = np.ceil(ltts * op.samplerate) ltts = lt_samples / op.samplerate lt = drf.util.sample_to_datetime(lt_samples, op.samplerate) if op.verbose: ltstr = lt.strftime("%a %b %d %H:%M:%S.%f %Y") print("Launch time: {0} ({1})".format(ltstr, repr(ltts))) # command launch time ct_td = lt - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 ct_frac = ct_td.microseconds / 1000000.0 u.set_start_time(uhd.time_spec(ct_secs) + uhd.time_spec(ct_frac)) # populate flowgraph one channel at a time fg = gr.top_block() for k in range(op.nchs): mult_k = op.amplitudes[k] * np.exp(1j * op.phases[k]) if op.waveform is not None: waveform_k = mult_k * op.waveform src_k = blocks.vector_source_c(waveform_k.tolist(), repeat=True) else: src_k = analog.sig_source_c(0, analog.GR_CONST_WAVE, 0, 0, mult_k) fg.connect(src_k, (u, k)) # start the flowgraph once we are near the launch time # (start too soon and device buffers might not yet be flushed) # (start too late and device might not be able to start in time) while (lt - pytz.utc.localize(datetime.utcnow())) > timedelta(seconds=1.2): time.sleep(0.1) fg.start() # wait until end time or until flowgraph stops if et is None and duration is not None: et = lt + timedelta(seconds=duration) try: if et is None: fg.wait() else: # sleep until end time nears while pytz.utc.localize( datetime.utcnow()) < et - timedelta(seconds=2): time.sleep(1) else: # issue stream stop command at end time ct_td = et - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 ct_frac = ct_td.microseconds / 1000000.0 u.set_command_time( (uhd.time_spec(ct_secs) + uhd.time_spec(ct_frac)), uhd.ALL_MBOARDS, ) stop_enum = uhd.stream_cmd.STREAM_MODE_STOP_CONTINUOUS u.issue_stream_cmd(uhd.stream_cmd(stop_enum)) u.clear_command_time(uhd.ALL_MBOARDS) # sleep until after end time time.sleep(2) except KeyboardInterrupt: # catch keyboard interrupt and simply exit pass fg.stop() # need to wait for the flowgraph to clean up, otherwise it won't exit fg.wait() print("done") sys.stdout.flush()
def start_reception(self, samples_to_receive, freq, lo_offset, bw, gain, samples_to_receive_calibration, freq_calibration, lo_offset_calibration, bw_calibration, gain_calibration, time_to_recv, autocalibrate, acquisitions, acquisition_time): if acquisitions == 0: infinity = True else: infinity = False if time_to_recv is None: time_to_recv = np.ceil(self.usrp_source.get_time_last_pps().get_real_secs()) + 1.5 time_now = self.usrp_source.get_time_now().get_real_secs() if time_to_recv < time_now: print time_to_recv,time_now print "Can't start in the past!" return # retune once to reconfigure receiver self.retune(freq, lo_offset, gain, bw) usrp = self.usrp_source print "Parameters:", usrp.get_center_freq(0),usrp.get_gain(0),usrp.get_samp_rate(),usrp.get_bandwidth(0),samples_to_receive,usrp.get_antenna(0) time_to_sample_last = 0 # receiver acquisition loop while True: # get current time from usrp time_now = self.usrp_source.get_time_now().get_real_secs() print "time now:",time_now # without autocalibration we don't have to retune if not autocalibrate: if ((time_to_recv - time_now) < acquisition_time): time_to_sample = uhd.time_spec(time_to_recv) if not time_to_sample == time_to_sample_last: # ask for samples at a specific time stream_cmd = uhd.stream_cmd(uhd.stream_cmd_t.STREAM_MODE_NUM_SAMPS_AND_DONE ) # add 300 samples to the burst to get rid of transient stream_cmd.num_samps = samples_to_receive + 300 stream_cmd.stream_now = False stream_cmd.time_spec = time_to_sample self.usrp_source.issue_stream_cmd(stream_cmd) # remember last sampling time time_to_sample_last = time_to_sample # update the reception time for next acquisition time_to_recv = time_to_recv + acquisition_time print "Time to sample:", time_to_sample.get_real_secs() print "time to receive:", time_to_recv acquisitions -= 1 if not self.run_loop or (acquisitions <= 0 and not infinity): time.sleep(acquisition_time/4) break time.sleep(acquisition_time/4) else: if ((time_to_recv - time_now) < 3 *acquisition_time/10): # get times from USRP time_to_sample = uhd.time_spec(time_to_recv + 3 * acquisition_time/10.0) # ask for samples at a specific time stream_cmd = uhd.stream_cmd(uhd.stream_cmd_t.STREAM_MODE_NUM_SAMPS_AND_DONE) # add 300 samples to the burst to get rid of transient stream_cmd.num_samps = samples_to_receive + 300 stream_cmd.stream_now = False stream_cmd.time_spec = time_to_sample self.usrp_source.issue_stream_cmd(stream_cmd) time_to_calibrate = uhd.time_spec(time_to_recv + 7 * acquisition_time/10.0) # ask for samples at a specific time stream_cmd = uhd.stream_cmd(uhd.stream_cmd_t.STREAM_MODE_NUM_SAMPS_AND_DONE) # add 300 samples to the burst to get rid of transient stream_cmd.num_samps = samples_to_receive_calibration + 300 stream_cmd.stream_now = False stream_cmd.time_spec = time_to_calibrate self.usrp_source.issue_stream_cmd(stream_cmd) # synchronize LOs time_retune_1 = self.usrp_source.get_time_now().get_real_secs() self.retune(freq, lo_offset, gain, bw) time_now = self.usrp_source.get_time_now().get_real_secs() if (time_to_sample > time_now): if (time_now > time_to_recv): print "time_now > time_to_recv" self.scheduler.enter((4 * acquisition_time/10.0), 1, self.retune, ([freq_calibration, lo_offset_calibration, gain_calibration, bw_calibration])) else: print "time_now < time_to_recv" self.scheduler.enter(((time_to_recv-time_now) + 4 * acquisition_time/10.0), 1, self.retune, ([freq_calibration, lo_offset_calibration, gain_calibration, bw_calibration])) self.scheduler.run() print "Time retune 1:", time_retune_1 print "Time to sample:", time_to_sample.get_real_secs() if autocalibrate: #print "Time retune 2:", time_retune_2 print "Time to calibrate:", time_to_calibrate.get_real_secs() usrp = self.usrp_source print "Parameters:", usrp.get_center_freq(0),usrp.get_gain(0),usrp.get_samp_rate(),usrp.get_bandwidth(0),samples_to_receive,usrp.get_antenna(0) acquisitions -= 1 time_to_recv = time_to_recv + acquisition_time if not self.run_loop or (acquisitions <= 0 and not infinity): break
def run(self, starttime=None, endtime=None, duration=None, period=10): op = self.op # print current time and NTP status if op.verbose: call(('timedatectl', 'status')) # parse time arguments if starttime is None: st = None else: dtst = dateutil.parser.parse(starttime) epoch = datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) st = int((dtst - epoch).total_seconds()) # find next suitable start time by cycle repeat period soon = int(math.ceil(time.time())) + 5 periods_until_next = (max(soon - st, 0) - 1) // period + 1 st = st + periods_until_next * period if op.verbose: dtst = datetime.datetime.utcfromtimestamp(st) dtststr = dtst.strftime('%a %b %d %H:%M:%S %Y') print('Start time: {0} ({1})'.format(dtststr, st)) if endtime is None: et = None else: dtet = dateutil.parser.parse(endtime) epoch = datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) et = int((dtet - epoch).total_seconds()) if op.verbose: dtetstr = dtet.strftime('%a %b %d %H:%M:%S %Y') print('End time: {0} ({1})'.format(dtetstr, et)) if et is not None: if (et < time.time() + 5) or (st is not None and et <= st): raise ValueError('End time is before launch time!') if op.realtime: r = gr.enable_realtime_scheduling() if op.verbose: if r == gr.RT_OK: print('Realtime scheduling enabled') else: print('Note: failed to enable realtime scheduling') # create data directory so ringbuffer code can be started while waiting # to launch if not os.path.isdir(op.datadir): os.makedirs(op.datadir) # wait for the start time if it is not past while (st is not None) and (st - time.time()) > 10: ttl = int(math.floor(st - time.time())) if (ttl % 10) == 0: print('Standby {0} s remaining...'.format(ttl)) sys.stdout.flush() time.sleep(1) # get UHD USRP source u = self._usrp_setup() # force creation of the RX streamer ahead of time with a finite # acquisition (after setting time/clock sources, before setting the # device time) # this fixes timing with the B210 u.finite_acquisition_v(16384) # wait until time 0.2 to 0.5 past full second, then latch # we have to trust NTP to be 0.2 s accurate tt = time.time() while tt - math.floor(tt) < 0.2 or tt - math.floor(tt) > 0.3: time.sleep(0.01) tt = time.time() if op.verbose: print('Latching at ' + str(tt)) if op.sync: # waits for the next pps to happen # (at time math.ceil(tt)) # then sets the time for the subsequent pps # (at time math.ceil(tt) + 1.0) u.set_time_unknown_pps(uhd.time_spec(math.ceil(tt) + 1.0)) else: u.set_time_now(uhd.time_spec(tt)) # reset device stream and flush buffer to clear leftovers from finite # acquisition u.stop() # get output settings that depend on decimation rate samplerate_out = op.samplerate / op.dec samplerate_num_out = op.samplerate_num samplerate_den_out = op.samplerate_den * op.dec if op.dec > 1: sample_size = gr.sizeof_gr_complex sample_dtype = '<f4' taps = firdes.low_pass_2(1.0, float(op.samplerate), float(samplerate_out / 2.0), float(0.2 * samplerate_out), 80.0, window=firdes.WIN_BLACKMAN_hARRIS) else: sample_size = 2 * gr.sizeof_short sample_dtype = '<i2' # set launch time # (at least 1 second out so USRP time is set, time to set up flowgraph) if st is not None: lt = st else: lt = int(math.ceil(time.time() + 1.0)) # adjust launch time forward so it falls on an exact sample since epoch lt_samples = np.ceil(lt * samplerate_out) lt = lt_samples / samplerate_out if op.verbose: dtlt = datetime.datetime.utcfromtimestamp(lt) dtltstr = dtlt.strftime('%a %b %d %H:%M:%S.%f %Y') print('Launch time: {0} ({1})'.format(dtltstr, repr(lt))) # command time ct_samples = lt_samples # splitting ct into secs/frac lets us set a more accurate time_spec ct_secs = ct_samples // samplerate_out ct_frac = (ct_samples % samplerate_out) / samplerate_out u.set_start_time( uhd.time_spec(float(ct_secs)) + uhd.time_spec(float(ct_frac))) # populate flowgraph one channel at a time fg = gr.top_block() for k in range(op.nchs): # create digital RF sink chdir = os.path.join(op.datadir, op.chs[k]) dst = gr_drf.digital_rf_sink( dir=chdir, sample_size=sample_size, subdir_cadence_s=op.subdir_cadence_s, file_cadence_ms=op.file_cadence_ms, sample_rate_numerator=samplerate_num_out, sample_rate_denominator=samplerate_den_out, uuid=op.uuid, is_complex=True, num_subchannels=1, stop_on_dropped_packet=op.stop_on_dropped, start_sample_index=int(lt * samplerate_out), ignore_tags=False, ) if op.dec > 1: # create low-pass filter lpf = filter.freq_xlating_fir_filter_ccf( op.dec, taps, 0.0, float(op.samplerate)) # connections for usrp->lpf->drf connections = ((u, k), (lpf, 0), (dst, 0)) else: # connections for usrp->drf connections = ((u, k), (dst, 0)) # make channel connections in flowgraph fg.connect(*connections) # start to receive data fg.start() # write metadata one channel at a time for k in range(op.nchs): mbnum = op.mboardnum_bychan[k] # create metadata dir, dmd object, and write channel metadata mddir = os.path.join(op.datadir, op.chs[k], 'metadata') if not os.path.exists(mddir): os.makedirs(mddir) mdo = drf.DigitalMetadataWriter( metadata_dir=mddir, subdir_cadence_secs=op.subdir_cadence_s, file_cadence_secs=1, sample_rate_numerator=samplerate_num_out, sample_rate_denominator=samplerate_den_out, file_name='metadata', ) md = op.metadata.copy() md.update( # standard metadata by convention uuid_str=op.uuid, sample_rate_numerator=samplerate_num_out, sample_rate_denominator=samplerate_den_out, # put in a list because we want the data to be a 1-D array # and it would be a single value if we didn't center_frequencies=[np.array([op.centerfreqs[k]])], # additional receiver metadata for USRP receiver=dict( description='UHD USRP source using GNU Radio', info=dict(u.get_usrp_info(chan=k)), antenna=op.antennas[k], bandwidth=op.bandwidths[k], center_freq=op.centerfreqs[k], clock_rate=u.get_clock_rate(mboard=mbnum), clock_source=u.get_clock_source(mboard=mbnum), gain=op.gains[k], id=op.mboards_bychan[k], lo_offset=op.lo_offsets[k], otw_format=op.otw_format, samp_rate=u.get_samp_rate(), stream_args=','.join(op.stream_args), subdev=op.subdevs_bychan[k], time_source=u.get_time_source(mboard=mbnum), ), ) mdo.write( samples=int(lt * samplerate_out), data_dict=md, ) # wait until end time or until flowgraph stops if et is None and duration is not None: et = lt + duration try: if et is None: fg.wait() else: # sleep until end time nears while (time.time() < et - 1): time.sleep(1) else: # issue stream stop command at end time ct_secs = et // 1 ct_frac = et % 1 u.set_command_time( (uhd.time_spec(float(ct_secs)) + uhd.time_spec(float(ct_frac))), uhd.ALL_MBOARDS, ) stop_enum = uhd.stream_cmd.STREAM_MODE_STOP_CONTINUOUS u.issue_stream_cmd(uhd.stream_cmd(stop_enum)) u.clear_command_time(uhd.ALL_MBOARDS) # sleep until after end time time.sleep(1) except KeyboardInterrupt: # catch keyboard interrupt and simply exit pass fg.stop() print('done') sys.stdout.flush()
def run(self, starttime=None, endtime=None, duration=None, period=10): op = self.op # window in seconds that we allow for setup time so that we don't # issue a start command that's in the past when the flowgraph starts SETUP_TIME = 10 # print current time and NTP status if op.verbose: call(('timedatectl', 'status')) # parse time arguments st = drf.util.parse_identifier_to_time(starttime) if st is not None: # find next suitable start time by cycle repeat period now = datetime.utcnow() now = now.replace(tzinfo=pytz.utc) soon = now + timedelta(seconds=SETUP_TIME) diff = max(soon - st, timedelta(0)).total_seconds() periods_until_next = (diff - 1) // period + 1 st = st + timedelta(seconds=periods_until_next * period) if op.verbose: ststr = st.strftime('%a %b %d %H:%M:%S %Y') stts = (st - drf.util.epoch).total_seconds() print('Start time: {0} ({1})'.format(ststr, stts)) et = drf.util.parse_identifier_to_time(endtime, ref_datetime=st) if et is not None: if op.verbose: etstr = et.strftime('%a %b %d %H:%M:%S %Y') etts = (et - drf.util.epoch).total_seconds() print('End time: {0} ({1})'.format(etstr, etts)) if ((et < (pytz.utc.localize(datetime.utcnow()) + timedelta(seconds=SETUP_TIME))) or (st is not None and et <= st)): raise ValueError('End time is before launch time!') if op.realtime: r = gr.enable_realtime_scheduling() if op.verbose: if r == gr.RT_OK: print('Realtime scheduling enabled') else: print('Note: failed to enable realtime scheduling') # create data directory so ringbuffer code can be started while waiting # to launch if not os.path.isdir(op.datadir): os.makedirs(op.datadir) # wait for the start time if it is not past while (st is not None) and ((st - pytz.utc.localize(datetime.utcnow())) > timedelta(seconds=SETUP_TIME)): ttl = int( (st - pytz.utc.localize(datetime.utcnow())).total_seconds()) if (ttl % 10) == 0: print('Standby {0} s remaining...'.format(ttl)) sys.stdout.flush() time.sleep(1) # get UHD USRP source u = self._usrp_setup() # after USRP setup, get output settings that depend on decimation rate samplerate_out = op.samplerate / op.dec samplerate_num_out = op.samplerate_num samplerate_den_out = op.samplerate_den * op.dec if op.dec > 1: sample_dtype = '<c8' taps = firdes.low_pass_2(1.0, float(op.samplerate), float(samplerate_out / 2.0), float(0.2 * samplerate_out), 80.0, window=firdes.WIN_BLACKMAN_hARRIS) else: sample_dtype = np.dtype([('r', '<i2'), ('i', '<i2')]) # force creation of the RX streamer ahead of time with a start/stop # (after setting time/clock sources, before setting the # device time) # this fixes timing with the B210 u.start() # need to wait >0.1 s (constant in usrp_source_impl.c) for start/stop # to actually take effect, so sleep a bit time.sleep(0.2) u.stop() time.sleep(0.2) # set device time tt = time.time() if op.sync: # wait until time 0.2 to 0.5 past full second, then latch # we have to trust NTP to be 0.2 s accurate while tt - math.floor(tt) < 0.2 or tt - math.floor(tt) > 0.3: time.sleep(0.01) tt = time.time() if op.verbose: print('Latching at ' + str(tt)) # waits for the next pps to happen # (at time math.ceil(tt)) # then sets the time for the subsequent pps # (at time math.ceil(tt) + 1.0) u.set_time_unknown_pps(uhd.time_spec(math.ceil(tt) + 1.0)) else: u.set_time_now(uhd.time_spec(tt)) # set launch time # (at least 1 second out so USRP time is set, time to set up flowgraph) if st is not None: lt = st else: now = pytz.utc.localize(datetime.utcnow()) lt = now.replace(microsecond=0) + timedelta(seconds=2) ltts = (lt - drf.util.epoch).total_seconds() # adjust launch time forward so it falls on an exact sample since epoch lt_samples = np.ceil(ltts * samplerate_out) ltts = lt_samples / samplerate_out lt = drf.util.sample_to_datetime(lt_samples, samplerate_out) if op.verbose: ltstr = lt.strftime('%a %b %d %H:%M:%S.%f %Y') print('Launch time: {0} ({1})'.format(ltstr, repr(ltts))) # command launch time ct_td = lt - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 ct_frac = ct_td.microseconds / 1000000.0 u.set_start_time(uhd.time_spec(ct_secs) + uhd.time_spec(ct_frac)) # populate flowgraph one channel at a time fg = gr.top_block() for k in range(op.nchs): mbnum = op.mboardnum_bychan[k] # create digital RF sink dst = gr_drf.digital_rf_channel_sink( channel_dir=os.path.join(op.datadir, op.chs[k]), dtype=sample_dtype, subdir_cadence_secs=op.subdir_cadence_s, file_cadence_millisecs=op.file_cadence_ms, sample_rate_numerator=samplerate_num_out, sample_rate_denominator=samplerate_den_out, start=lt_samples, ignore_tags=False, is_complex=True, num_subchannels=1, uuid_str=op.uuid, center_frequencies=op.centerfreqs[k], metadata=dict( # receiver metadata for USRP receiver=dict( description='UHD USRP source using GNU Radio', info=dict(u.get_usrp_info(chan=k)), antenna=op.antennas[k], bandwidth=op.bandwidths[k], center_freq=op.centerfreqs[k], clock_rate=u.get_clock_rate(mboard=mbnum), clock_source=u.get_clock_source(mboard=mbnum), gain=op.gains[k], id=op.mboards_bychan[k], lo_offset=op.lo_offsets[k], otw_format=op.otw_format, samp_rate=u.get_samp_rate(), stream_args=','.join(op.stream_args), subdev=op.subdevs_bychan[k], time_source=u.get_time_source(mboard=mbnum), ), ), is_continuous=True, compression_level=0, checksum=False, marching_periods=True, stop_on_skipped=op.stop_on_dropped, debug=op.verbose, ) if op.dec > 1: # create low-pass filter lpf = filter.freq_xlating_fir_filter_ccf( op.dec, taps, 0.0, float(op.samplerate)) # connections for usrp->lpf->drf connections = ((u, k), (lpf, 0), (dst, 0)) else: # connections for usrp->drf connections = ((u, k), (dst, 0)) # make channel connections in flowgraph fg.connect(*connections) # start the flowgraph once we are near the launch time # (start too soon and device buffers might not yet be flushed) # (start too late and device might not be able to start in time) while ((lt - pytz.utc.localize(datetime.utcnow())) > timedelta(seconds=1.2)): time.sleep(0.1) fg.start() # wait until end time or until flowgraph stops if et is None and duration is not None: et = lt + timedelta(seconds=duration) try: if et is None: fg.wait() else: # sleep until end time nears while (pytz.utc.localize(datetime.utcnow()) < et - timedelta(seconds=2)): time.sleep(1) else: # issue stream stop command at end time ct_td = et - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 ct_frac = ct_td.microseconds / 1000000.0 u.set_command_time( (uhd.time_spec(ct_secs) + uhd.time_spec(ct_frac)), uhd.ALL_MBOARDS, ) stop_enum = uhd.stream_cmd.STREAM_MODE_STOP_CONTINUOUS u.issue_stream_cmd(uhd.stream_cmd(stop_enum)) u.clear_command_time(uhd.ALL_MBOARDS) # sleep until after end time time.sleep(2) except KeyboardInterrupt: # catch keyboard interrupt and simply exit pass fg.stop() # need to wait for the flowgraph to clean up, otherwise it won't exit fg.wait() print('done') sys.stdout.flush()