Beispiel #1
0
    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()
Beispiel #2
0
    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
Beispiel #3
0
    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()
Beispiel #4
0
    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()