Beispiel #1
0
    def open_device(self, name, ok):
        if not ok:
            self._main_window.show()
            return

        if self.dut:
            self.dut.disconnect()
        self._main_window.show()
        dut = WSA(connector=TwistedConnector(self._reactor))
        yield dut.connect(name)
        self.dev_set = {
            'attenuator': 0,
            'freq': 2450e6,
            'decimation': 1,
            'fshift': 0,
            'rfe_mode': 'SH',
            'iq_output_path': 'DIGITIZER'
        }
        self.dut = dut
        self.dut_prop = self.dut.properties
        self.bandwidth = self.dut_prop.FULL_BW[self.dev_set['rfe_mode']]
        self.rbw = 125000000 / SAMPLE_VALUES[3]
        self.enable_mhold = False
        self.mhold = []
        self.cap_dut = CaptureDevice(dut,
                                     async_callback=self.receive_capture,
                                     device_settings=self.dev_set)
        self.initUI()
        self.enable_controls()
        self.read_block()
Beispiel #2
0
    def open_device(self, name, ok):
        if not ok:
            self._main_window.show()
            return

        if self.dut:
            self.dut.disconnect()
        self._main_window.show()
        dut = WSA(connector=TwistedConnector(self._reactor))
        yield dut.connect(name)
        self.dev_set = {
            'attenuator': 1,
            'freq':2450e6,
            'decimation': 1,
            'fshift': 0,
            'rfe_mode': 'SH',
            'iq_output_path': 'DIGITIZER'}
        self.rbw = RBW_VALUES[4]
        self.enable_mhold = False
        self.mhold = []
        self.dut = dut
        self.dut_prop = self.dut.properties
        self.cap_dut = CaptureDevice(dut, async_callback=self.receive_capture,
            device_settings=self.dev_set)
        self.initUI()
        self.enable_controls()
        self.read_block()
Beispiel #3
0
 def open_device(self, name):
     if self.dut:
         self.dut.disconnect()
     dut = WSA(connector=TwistedConnector(self._reactor))
     yield dut.connect(name)
     if hasattr(dut.properties, 'MINIMUM_FW_VERSION') and parse_version(
             dut.fw_version) < parse_version(dut.properties.MINIMUM_FW_VERSION):
         too_old = QtGui.QMessageBox()
         too_old.setText('Your device firmware version is {0}'
             ' but this application is expecting at least version'
             ' {1}. Some features may not work properly'.format(
             dut.fw_version, dut.properties.MINIMUM_FW_VERSION))
         too_old.exec_()
     if self._output_file:
         dut.set_capture_output(self._output_file)
     self.dut = dut
     self.plot_state = gui_config.PlotState(dut.properties)
     self.dut_prop = self.dut.properties
     self.sweep_dut = SweepDevice(dut, self.receive_sweep)
     self.cap_dut = CaptureDevice(dut, async_callback=self.receive_capture,
         device_settings=self.plot_state.dev_set)
     self._dev_group.configure(self.dut.properties)
     self.enable_controls()
     cu._select_center_freq(self)
     self._rbw_box.setCurrentIndex(3)
     self._plot.const_window.show()
     self._plot.iq_window.show()
     self.plot_state.enable_block_mode(self)
     self.read_block()
Beispiel #4
0
    def set_device(self, dut=None, playback_filename=None):
        """
        Detach any currenly attached device and stop playback then
        optionally attach to a new device or playback file.

        :param dut: a :class:`pyrf.thinkrf.WSA` or None
        :param playback_filename: recorded VRT data filename or None
        :param playback_scheduler: function to schedule each playback capture
        """
        if self._playback_file:
            self._playback_file.close()
            self._playback_file = None

        if self._dut:
            self._dut.disconnect()

        if playback_filename:
            self._playback_file = open(playback_filename, 'rb')
            self._playback_started = False
            self._playback_context = {}
            vrt_packet = self._playback_vrt(auto_rewind=False)
            state_json = vrt_packet.fields['speca']
            # support old playback files
            if state_json['device_identifier'] == 'unknown':
                state_json['device_identifier'] = 'ThinkRF,WSA5000 v3,,'
            dut = Playback(state_json['device_class'],
                state_json['device_identifier'])
            self._sweep_device = SweepDevice(dut)
            self._capture_device = CaptureDevice(dut)
        elif dut:
            dut.reset()
            self._sweep_device = SweepDevice(dut, self.process_sweep)
            self._capture_device = CaptureDevice(dut, self.process_capture)
            state_json = dict(
                dut.properties.SPECA_DEFAULTS,
                device_identifier=dut.device_id)

        self._dut = dut
        if not dut:
            return

        self.device_change.emit(dut)
        self._apply_complete_settings(state_json, bool(self._playback_file))
        self.start_capture()
Beispiel #5
0
    def set_device(self, dut=None, playback_filename=None):
        """
        Detach any currenly attached device and stop playback then
        optionally attach to a new device or playback file.

        :param dut: a :class:`pyrf.thinkrf.WSA` or None
        :param playback_filename: recorded VRT data filename or None
        :param playback_scheduler: function to schedule each playback capture
        """
        if self._playback_file:
            self._playback_file.close()
            self._playback_file = None

        if self._dut:
            self._dut.disconnect()

        if playback_filename:
            self._playback_file = open(playback_filename, 'rb')
            self._playback_started = False
            self._playback_context = {}
            vrt_packet = self._playback_vrt(auto_rewind=False)
            state_json = vrt_packet.fields['speca']
            # support old playback files
            if state_json['device_identifier'] == 'unknown':
                state_json['device_identifier'] = 'ThinkRF,WSA5000 v3,0,0'
            dut = Playback(state_json['device_class'],
                           state_json['device_identifier'])
            self._sweep_device = SweepDevice(dut)
            self._capture_device = CaptureDevice(dut)
        elif dut:
            dut.reset()
            self._sweep_device = SweepDevice(dut, self.process_sweep)
            self._capture_device = CaptureDevice(dut, self.process_capture)
            state_json = dict(dut.properties.SPECA_DEFAULTS,
                              device_identifier=dut.device_id)

        self._dut = dut
        if not dut:
            return

        self.device_change.emit(dut)
        self._apply_complete_settings(state_json, bool(self._playback_file))
        self.start_capture()
Beispiel #6
0
class SpecAController(QtCore.QObject):
    """
    The controller for the rtsa-gui.

    Issues commands to device, stores and broadcasts changes to GUI state.
    """
    _dut = None
    _sweep_device = None
    _capture_device = None
    _plot_state = None
    _state = None
    _recording_file = None
    _playback_file = None
    _playback_sweep_data = None
    _user_xrange_control_enabled = True
    _pending_user_xrange = None
    _applying_user_xrange = False

    device_change = QtCore.Signal(object)
    state_change = QtCore.Signal(SpecAState, list)
    capture_receive = QtCore.Signal(SpecAState, float, float, object, object, object, object)
    options_change = QtCore.Signal(dict, list)

    def __init__(self):
        super(SpecAController, self).__init__()
        self._dsp_options = {}
        self._options = {}


    def set_device(self, dut=None, playback_filename=None):
        """
        Detach any currenly attached device and stop playback then
        optionally attach to a new device or playback file.

        :param dut: a :class:`pyrf.thinkrf.WSA` or None
        :param playback_filename: recorded VRT data filename or None
        :param playback_scheduler: function to schedule each playback capture
        """
        if self._playback_file:
            self._playback_file.close()
            self._playback_file = None

        if self._dut:
            self._dut.disconnect()

        if playback_filename:
            self._playback_file = open(playback_filename, 'rb')
            self._playback_started = False
            self._playback_context = {}
            vrt_packet = self._playback_vrt(auto_rewind=False)
            state_json = vrt_packet.fields['speca']
            # support old playback files
            if state_json['device_identifier'] == 'unknown':
                state_json['device_identifier'] = 'ThinkRF,WSA5000 v3,,'
            dut = Playback(state_json['device_class'],
                state_json['device_identifier'])
            self._sweep_device = SweepDevice(dut)
            self._capture_device = CaptureDevice(dut)
        elif dut:
            dut.reset()
            self._sweep_device = SweepDevice(dut, self.process_sweep)
            self._capture_device = CaptureDevice(dut, self.process_capture)
            state_json = dict(
                dut.properties.SPECA_DEFAULTS,
                device_identifier=dut.device_id)

        self._dut = dut
        if not dut:
            return

        self.device_change.emit(dut)
        self._apply_complete_settings(state_json, bool(self._playback_file))
        self.start_capture()

    def start_recording(self, filename=None):
        """
        Start a new recording with filename, or autogenerated filename.
        Stops previous recording if one in progress. Does nothing if we're
        currently playing a recording.
        """
        if self._playback_file:
            return
        self.stop_recording()
        if not filename:
            names = glob.glob('recording-*.vrt')
            last_index = -1
            for n in names:
                try:
                    last_index = max(last_index, int(n[10:-4]))
                except ValueError:
                    pass
            filename = 'recording-%04d.vrt' % (last_index + 1)
        self._recording_file = open(filename, 'wb')
        self._dut.set_recording_output(self._recording_file)
        self._dut.inject_recording_state(self._state.to_json_object())

    def stop_recording(self):
        """
        Stop recording or do nothing if not currently recording.
        """
        if not self._recording_file:
            return
        self._dut.set_recording_output(None)
        self._recording_file.close()
        self._recording_file = None

    def _apply_pending_user_xrange(self):
        if self._pending_user_xrange:
            self._applying_user_xrange = True
            start, stop = self._pending_user_xrange
            self._pending_user_xrange = None
            self.apply_settings(
                center=int((start + stop) / 2.0
                    / self._dut.properties.TUNING_RESOLUTION)
                    * self._dut.properties.TUNING_RESOLUTION,
                span=stop - start)
        else:
            self._applying_user_xrange = False

    def read_block(self):
        self._apply_pending_user_xrange()
        device_set = dict(self._state.device_settings)
        device_set['decimation'] = self._state.decimation
        device_set['fshift'] = self._state.fshift
        device_set['rfe_mode'] = self._state.rfe_mode()
        device_set['freq'] = self._state.center
        self._capture_device.configure_device(device_set)

        self._capture_device.capture_time_domain(
            self._state.mode,
            self._state.center,
            self._state.rbw)

    def read_sweep(self):
        self._apply_pending_user_xrange()
        device_set = dict(self._state.device_settings)
        device_set.pop('pll_reference')
        device_set.pop('iq_output_path')
        device_set.pop('trigger')
        self._sweep_device.capture_power_spectrum(
            self._state.center - self._state.span / 2.0,
            self._state.center + self._state.span / 2.0,
            self._state.rbw,
            device_set,
            mode=self._state.rfe_mode())

    def start_capture(self):
        if self._playback_file:
            self.schedule_playback()
        elif self._state.sweeping():
            self.read_sweep()
        else:
            self.read_block()

    def schedule_playback(self):
        if not self._playback_started:
            QtCore.QTimer.singleShot(0, self._playback_step)
            self._playback_started = True

    def _playback_step(self):
        if not self._playback_file:
            self._playback_started = False
            return

        QtCore.QTimer.singleShot(PLAYBACK_STEP_MSEC, self._playback_step)

        while True:
            pkt = self._playback_vrt()

            if pkt.is_context_packet():
                if 'speca' in pkt.fields:
                    self._playback_sweep_data = None
                    state_json = pkt.fields['speca']
                    self._apply_complete_settings(state_json, playback=True)
                else:
                    self._playback_context.update(pkt.fields)
                continue

            if self._state.sweeping():
                if not self._playback_sweep_step(pkt):
                    continue
                return
            break

        usable_bins = compute_usable_bins(
            self._dut.properties,
            self._state.rfe_mode(),
            len(pkt.data),
            self._state.decimation,
            self._state.fshift)

        usable_bins, fstart, fstop = adjust_usable_fstart_fstop(
            self._dut.properties,
            self._state.rfe_mode(),
            len(pkt.data),
            self._state.decimation,
            self._state.center,
            pkt.spec_inv,
            usable_bins)

        pow_data = compute_fft(
            self._dut,
            pkt,
            self._playback_context,
            **self._dsp_options)

        if not self._options.get('show_attenuated_edges'):
            pow_data, usable_bins, fstart, fstop = (
                trim_to_usable_fstart_fstop(
                    pow_data, usable_bins, fstart, fstop))

        self.capture_receive.emit(
            self._state,
            fstart,
            fstop,
            pkt,
            pow_data,
            usable_bins,
            None)

    def _playback_sweep_start(self):
        """
        ready a new playback sweep data array
        """
        nans = np.ones(int(self._state.span / self._state.rbw)) * np.nan
        self._playback_sweep_data = nans

    def _playback_sweep_step(self, pkt):
        """
        process one data packet from a recorded sweep and
        plot collected data after receiving complete sweep.

        returns True if data was plotted on this step.
        """
        if self._playback_sweep_data is None:
            self._playback_sweep_start()
            last_center = None
        else:
            last_center = self._playback_sweep_last_center

        sweep_start = float(self._state.center - self._state.span / 2)
        sweep_stop = float(self._state.center + self._state.span / 2)
        step_center = self._playback_context['rffreq']
        updated_plot = False
        if last_center is not None and last_center >= step_center:
            # starting a new sweep, plot the data we have
            self.capture_receive.emit(
                self._state,
                sweep_start,
                sweep_stop,
                None,
                self._playback_sweep_data,
                None,
                None)
            updated_plot = True
            self._playback_sweep_start()
        self._playback_sweep_last_center = step_center

        usable_bins = compute_usable_bins(
            self._dut.properties,
            self._state.rfe_mode(),
            len(pkt.data),
            self._state.decimation,
            self._state.fshift)

        usable_bins, fstart, fstop = adjust_usable_fstart_fstop(
            self._dut.properties,
            self._state.rfe_mode(),
            len(pkt.data),
            self._state.decimation,
            step_center,
            pkt.spec_inv,
            usable_bins)

        pow_data = compute_fft(
            self._dut,
            pkt,
            self._playback_context,
            **self._dsp_options)

        pow_data, usable_bins, fstart, fstop = (
            trim_to_usable_fstart_fstop(
                pow_data, usable_bins, fstart, fstop))

        clip_left = max(sweep_start, fstart)
        clip_right = min(sweep_stop, fstop)
        sweep_points = len(self._playback_sweep_data)
        point_left = int((clip_left - sweep_start) * sweep_points / (
            sweep_stop - sweep_start))
        point_right = int((clip_right - sweep_start) * sweep_points / (
            sweep_stop - sweep_start))
        xvalues = np.linspace(clip_left, clip_right, point_right - point_left)

        if point_left >= point_right:
            logger.info('received sweep step outside sweep: %r, %r' %
                ((fstart, fstop), (sweep_start, sweep_stop)))
        else:
            self._playback_sweep_data[point_left:point_right] = np.interp(
                xvalues, np.linspace(fstart, fstop, len(pow_data)), pow_data)

        return updated_plot


    def _playback_vrt(self, auto_rewind=True):
        """
        Return the next VRT packet in the playback file
        """
        reader = vrt_packet_reader(self._playback_file.read)
        data = None
        try:
            while True:
                data = reader.send(data)
        except StopIteration:
            pass
        except ValueError:
            return None

        if data == '' and auto_rewind:
            self._playback_file.seek(0)
            data = self._playback_vrt(auto_rewind=False)

        return None if data == '' else data


    def process_capture(self, fstart, fstop, data):
        # store usable bins before next call to capture_time_domain
        usable_bins = list(self._capture_device.usable_bins)

        # only read data if WSA digitizer is used
        if 'DIGITIZER' in self._state.device_settings['iq_output_path']:
            if self._state.sweeping():
                self.read_sweep()
                return
            self.read_block()
            if 'reflevel' in data['context_pkt']:
                self._ref_level = data['context_pkt']['reflevel']

            pow_data = compute_fft(
                self._dut,
                data['data_pkt'],
                data['context_pkt'],
                ref=self._ref_level,
                **self._dsp_options)

            if not self._options.get('show_attenuated_edges'):
                pow_data, usable_bins, fstart, fstop = (
                    trim_to_usable_fstart_fstop(
                        pow_data, usable_bins, fstart, fstop))

            self.capture_receive.emit(
                self._state,
                fstart,
                fstop,
                data['data_pkt'],
                pow_data,
                usable_bins,
                None)

    def process_sweep(self, fstart, fstop, data):
        sweep_segments = list(self._sweep_device.sweep_segments)
        if not self._state.sweeping():
            self.read_block()
            return
        self.read_sweep()

        if len(data) > 2:
            self.pow_data = data
        self.iq_data = None

        if not self._options.get('show_sweep_steps'):
            sweep_segments = None

        self.capture_receive.emit(
            self._state,
            fstart,
            fstop,
            None,
            self.pow_data,
            None,
            sweep_segments)

    def _state_changed(self, state, changed):
        """
        Emit signal and handle special cases where extra work is needed in
        response to a state change.
        """
        if not state.sweeping():
            # force span to correct value for the mode given
            if state.decimation > 1:
                span = (float(self._dut.properties.FULL_BW[state.rfe_mode()])
                    / state.decimation * self._dut.properties.DECIMATED_USABLE)
            else:
                span = self._dut.properties.USABLE_BW[state.rfe_mode()]
            state = SpecAState(state, span=span)
            changed = [x for x in changed if x != 'span']
            if not self._state or span != self._state.span:
                changed.append('span')

        self._state = state
        # start capture loop again when user switches output path
        # back to the internal digitizer XXX: very WSA5000-specific
        if 'device_settings.iq_output_path' in changed:
            if state.device_settings.get('iq_output_path') == 'DIGITIZER':
                self.start_capture()
            elif state.device_settings.get('iq_output_path') == 'CONNECTOR':
                if state.sweeping():
                    state.mode = self._dut.properties.RFE_MODES[0]

        if self._recording_file:
            self._dut.inject_recording_state(state.to_json_object())

        self.state_change.emit(state, changed)

    def apply_device_settings(self, **kwargs):
        """
        Apply device-specific settings and trigger a state change event.
        :param kwargs: keyword arguments of SpecAState.device_settings
        """
        device_settings = dict(self._state.device_settings, **kwargs)
        state = SpecAState(self._state, device_settings=device_settings)

        if device_settings.get('iq_output_path') == 'CONNECTOR' or 'trigger' in kwargs:
            self._capture_device.configure_device(device_settings)

        changed = ['device_settings.%s' % s for s in kwargs]
        self._state_changed(state, changed)

    def apply_settings(self, **kwargs):
        """
        Apply state settings and trigger a state change event.

        :param kwargs: keyword arguments of SpecAState attributes
        """

        if self._state is None:
            logger.warn('apply_settings with _state == None: %r' % kwargs)
            return
        state = SpecAState(self._state, **kwargs)
        self._state_changed(state, kwargs.keys())

    def _apply_complete_settings(self, state_json, playback):
        """
        Apply state setting changes from a complete JSON object. Used for
        initial settings and applying settings from a recording.
        """
        if self._state:
            old = self._state.to_json_object()
            old['playback'] = self._state.playback
        else:
            old = {}

        changed = [
            key for key, value in state_json.iteritems()
            if old.get(key) != value
            ]
        if old.get('playback') != playback:
            changed.append('playback')

        if 'device_settings' in changed:
            changed.remove('device_settings')
            oset = old.get('device_settings', {})
            dset = state_json['device_settings']
            changed.extend([
                'device_settings.%s' % key for key, value in dset.iteritems()
                if oset.get(key) != value])

        state = SpecAState.from_json_object(state_json, playback)
        self._state_changed(state, changed)

    def apply_options(self, **kwargs):
        """
        Apply menu options and signal the change

        :param kwargs: keyword arguments of the dsp options
        """
        self._options.update(kwargs)
        self.options_change.emit(dict(self._options),
            kwargs.keys())

        for key, value in kwargs.iteritems():
            if key.startswith('dsp.'):
                self._dsp_options[key[4:]] = value

        if 'free_plot_adjustment' in kwargs:
            self.enable_user_xrange_control(
                not kwargs['free_plot_adjustment'])

    def get_options(self):
        return dict(self._options)

    def enable_user_xrange_control(self, enable):
        self._user_xrange_control_enabled = enable
        if not enable:
            self._pending_user_xrange = None

    def user_xrange_changed(self, start, stop):
        if self._user_xrange_control_enabled:
            self._pending_user_xrange = start, stop

    def applying_user_xrange(self):
        return self._applying_user_xrange
Beispiel #7
0
class MainPanel(QtGui.QWidget):
    """
    The spectrum view and controls
    """
    def __init__(self, main_window):
        self._main_window = main_window
        self.ref_level = 0
        self.dut = None
        self.control_widgets = []
        super(MainPanel, self).__init__()
        screen = QtGui.QDesktopWidget().screenGeometry()
        self.setMinimumWidth(screen.width() * 0.8)
        self.setMinimumHeight(screen.height() * 0.6)
        self._vrt_context = {}
        self._reactor = self._get_reactor()

    def _get_reactor(self):
        # late import because installReactor is being used
        from twisted.internet import reactor
        return reactor

    @inlineCallbacks
    def open_device(self, name, ok):
        if not ok:
            self._main_window.show()
            return

        if self.dut:
            self.dut.disconnect()
        self._main_window.show()
        dut = WSA(connector=TwistedConnector(self._reactor))
        yield dut.connect(name)
        self.dev_set = {
            'attenuator': 1,
            'freq':2450e6,
            'decimation': 1,
            'fshift': 0,
            'rfe_mode': 'SH',
            'iq_output_path': 'DIGITIZER'}
        self.rbw = RBW_VALUES[4]
        self.enable_mhold = False
        self.mhold = []
        self.dut = dut
        self.dut_prop = self.dut.properties
        self.cap_dut = CaptureDevice(dut, async_callback=self.receive_capture,
            device_settings=self.dev_set)
        self.initUI()
        self.enable_controls()
        self.read_block()

    def read_block(self):
        rbw = self.rbw 
        if self.dev_set['rfe_mode'] == 'ZIF':
            rbw = self.rbw * 2
        self.cap_dut.capture_time_domain(self.dev_set['rfe_mode'],
                                    self.dev_set['freq'],
                                    rbw)

    def receive_capture(self, fstart, fstop, data):
        # store usable bins before next call to capture_time_domain
        self.usable_bins = list(self.cap_dut.usable_bins)
        self.sweep_segments = None

        self.read_block()
        if 'reflevel' in data['context_pkt']:
            self.ref_level = data['context_pkt']['reflevel']
        self.pow_data = compute_fft(self.dut, data['data_pkt'], data['context_pkt'], ref = self.ref_level)

        self.raw_data = data['data_pkt']
        self.update_plot()

    def initUI(self):
        grid = QtGui.QGridLayout()
        grid.setSpacing(5)
        plot_width = 8
        for x in range(plot_width):
            grid.setColumnMinimumWidth(x, 250)
        grid.setColumnMinimumWidth(plot_width + 1, 260)
        
        grid.addWidget(self._plot_layout(), 0,0,5,plot_width)
        
        controls_layout = QtGui.QVBoxLayout()
        controls_layout.setSpacing(1)
        controls_layout.addWidget(self._if_attenuator())
        controls_layout.addWidget(self._hdr_gain())
        controls_layout.addWidget(self._center_freq())
        controls_layout.addWidget(self._rbw_controls())
        
        controls_layout.addWidget(self._mode_controls())
        controls_layout.addWidget(self._maxh_controls())
        controls_layout.addStretch()
        grid.addLayout(controls_layout, 0, plot_width + 1, 0, 5)

        self._grid = grid
        self.setLayout(grid)
    def _plot_layout(self):

        # create spectral plot
        self.window = pg.PlotWidget(name='pyrf_plot')
        self.window.showGrid(True, True)
        self.window.setYRange(PLOT_YMIN ,PLOT_YMAX)
        self.fft_curve = self.window.plot(pen = 'g')
        self.mhold_curve = self.window.plot(pen = 'y')
       
        # create IQ plot widget
        self.iq_window = pg.PlotWidget(name='IQ Plot')
        self.i_curve = self.iq_window.plot(pen = 'r')
        self.q_curve = self.iq_window.plot(pen = 'g')
        
        # create split
        vsplit = QtGui.QSplitter()
        vsplit.setOrientation(QtCore.Qt.Vertical)
        vsplit.addWidget(self.window)
        vsplit.addWidget(self.iq_window)
        self._plot_layout = vsplit
        return self._plot_layout
        
    def _center_freq(self):
        grid, widget = self.create_grid_and_widget('Freq')
        freq_edit = QtGui.QLineEdit(str(self.dev_set['freq'] / float(M)))
        self._freq_edit = freq_edit
        self.control_widgets.append(self._freq_edit)
        
        def freq_change():
            self.dev_set['freq'] = float(freq_edit.text()) * M
            self.cap_dut.configure_device(self.dev_set)
        freq_edit.returnPressed.connect(lambda: freq_change())
        grid.addWidget(freq_edit, 0,1,0,1)
        widget.setLayout(grid)
        return widget

    def _if_attenuator(self):
        grid, widget = self.create_grid_and_widget('IF Attenuator')
        if_attenuator = QtGui.QLineEdit('25')

        self.control_widgets.append(if_attenuator)
        
        def atten_change():
            self.dut.var_attenuator(int(if_attenuator.text()))
        if_attenuator.returnPressed.connect(lambda: atten_change())
        grid.addWidget(if_attenuator, 0,1,0,1)
        widget.setLayout(grid)
        return widget

    def _hdr_gain(self):
        grid, widget = self.create_grid_and_widget('hdr Attenuator')
        hdr_attenuator = QtGui.QLineEdit('25')

        self.control_widgets.append(hdr_attenuator)
        
        def atten_change():
            self.dut.hdr_gain(int(hdr_attenuator.text()))
        hdr_attenuator.returnPressed.connect(lambda: atten_change())
        grid.addWidget(hdr_attenuator, 0,1,0,1)
        widget.setLayout(grid)
        return widget

    def _mode_controls(self):
        grid, widget = self.create_grid_and_widget('Mode')
        mode = QtGui.QComboBox(self)
        mode.addItems(MODES)

        def new_mode():
            self.dev_set['rfe_mode'] = MODES[mode.currentIndex()]
            self.dut.rfe_mode(self.dev_set['rfe_mode'])
        mode.setCurrentIndex(1)
        mode.currentIndexChanged.connect(new_mode)
        grid.addWidget(mode, 0,1,0,1)
        widget.setLayout(grid)
        self.control_widgets.append(mode)
        return widget

    def _rbw_controls(self):
        grid, widget = self.create_grid_and_widget('Sample Size')
        rbw = QtGui.QComboBox(self)
        rbw.setToolTip("Change the RBW of the FFT plot")

        self._hdr_points_values = HDR_RBW_VALUES
        self._rbw_box = rbw
        rbw.addItems([str(p) + ' ' for p in SAMPLE_VALUES])

        def new_rbw():
            self.rbw = RBW_VALUES[rbw.currentIndex()]
            if self.dev_set['rfe_mode'] == 'HDR':
                self.rbw = HDR_RBW_VALUES[rbw.currentIndex()]
        rbw.setCurrentIndex(3)
        rbw.currentIndexChanged.connect(new_rbw)
        grid.addWidget(rbw, 0,1,0,1)
        widget.setLayout(grid)
        self.control_widgets.append(self._rbw_box)
        return widget
        
    def _maxh_controls(self):
        grid, widget = self.create_grid_and_widget('Max Hold')
        max_hold = QtGui.QCheckBox(self)

        def change_max_hold():
            self.enable_mhold = max_hold.isChecked()

        max_hold.clicked.connect(change_max_hold)
        grid.addWidget(max_hold, 0,1,0,1)
        widget.setLayout(grid)
        return widget
            
    def create_grid_and_widget(self, name):
            grid = QtGui.QGridLayout()
            widget = QtGui.QWidget()
            if name is not None:
                grid.addWidget(QtGui.QLabel(name), 0,0,0,1)
            widget.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
            return grid, widget
    
    def update_plot(self):
        self.update_trace()
        
    def update_trace(self):
        freq_range = np.linspace(self.dev_set['freq'] -62.5, 
                                self.dev_set['freq'] + 62.5, 
                                len(self.pow_data)) 
        self.fft_curve.setData(freq_range, self.pow_data)
        if self.enable_mhold:
            if len(self.mhold) != len(self.pow_data):
                self.mhold = self.pow_data
            else:
                self.mhold = np.maximum(self.mhold, self.pow_data)
            self.mhold_curve.setData(freq_range, self.mhold)
        else:
            self.mhold_curve.setData([], [])
            self.mhold = []
            
        i_data, q_data, stream_id, spec_inv = _decode_data_pkts(self.raw_data)
        self.i_curve.setData(i_data)
        if q_data is not None:
            self.q_curve.setData(q_data)
        else:
            self.q_curve.setData([])
    def enable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(True)
    

    def disable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(False)
Beispiel #8
0
class MainPanel(QtGui.QWidget):
    """
    The spectrum view and controls
    """
    def __init__(self, main_window,output_file):
        self._main_window = main_window
        self.ref_level = 0
        self.dut = None
        self.control_widgets = []
        self._output_file = output_file
        super(MainPanel, self).__init__()
        screen = QtGui.QDesktopWidget().screenGeometry()
        self.setMinimumWidth(screen.width() * 0.7)
        self.setMinimumHeight(screen.height() * 0.6)
        self.plot_state = None
        # plot window
        self._plot = Plot(self)
        self._marker_trace = None
        self._vrt_context = {}
        self.initUI()
        self.disable_controls()
        self.ref_level = 0
        self._reactor = self._get_reactor()

    def _get_reactor(self):
        # late import because installReactor is being used
        from twisted.internet import reactor
        return reactor

    def open_device_dialog(self):
        name, ok = QtGui.QInputDialog.getText(self, 'Open Device',
            'Enter a hostname or IP address:')
        while True:
            if not ok:
                return
            try:
                self.open_device(name)
                break
            except socket.error:
                name, ok = QtGui.QInputDialog.getText(self, 'Open Device',
                    'Connection Failed, please try again\n\n'
                    'Enter a hostname or IP address:')

    @inlineCallbacks
    def open_device(self, name):
        if self.dut:
            self.dut.disconnect()
        dut = WSA(connector=TwistedConnector(self._reactor))
        yield dut.connect(name)
        if hasattr(dut.properties, 'MINIMUM_FW_VERSION') and parse_version(
                dut.fw_version) < parse_version(dut.properties.MINIMUM_FW_VERSION):
            too_old = QtGui.QMessageBox()
            too_old.setText('Your device firmware version is {0}'
                ' but this application is expecting at least version'
                ' {1}. Some features may not work properly'.format(
                dut.fw_version, dut.properties.MINIMUM_FW_VERSION))
            too_old.exec_()
        if self._output_file:
            dut.set_capture_output(self._output_file)
        self.dut = dut
        self.plot_state = gui_config.PlotState(dut.properties)
        self.dut_prop = self.dut.properties
        self.sweep_dut = SweepDevice(dut, self.receive_sweep)
        self.cap_dut = CaptureDevice(dut, async_callback=self.receive_capture,
            device_settings=self.plot_state.dev_set)
        self._dev_group.configure(self.dut.properties)
        self.enable_controls()
        cu._select_center_freq(self)
        self._rbw_box.setCurrentIndex(3)
        self._plot.const_window.show()
        self._plot.iq_window.show()
        self.plot_state.enable_block_mode(self)
        self.read_block()

    def read_sweep(self):
        device_set = dict(self.plot_state.dev_set)
        device_set.pop('rfe_mode')
        device_set.pop('freq')
        device_set.pop('decimation')
        device_set.pop('fshift')
        device_set.pop('iq_output_path')
        self.sweep_dut.capture_power_spectrum(self.plot_state.fstart,
                                              self.plot_state.fstop,
                                              self.plot_state.rbw,
                                              device_set,
                                              mode=self._sweep_mode)

    def read_block(self):
        self.cap_dut.capture_time_domain(self.plot_state.rbw)


    def receive_capture(self, fstart, fstop, data):
        # store usable bins before next call to capture_time_domain
        self.usable_bins = list(self.cap_dut.usable_bins)
        self.sweep_segments = None

        # only read data if WSA digitizer is used
        if self.plot_state.dev_set['iq_output_path'] == 'DIGITIZER':
            if not self.plot_state.block_mode:
                self.read_sweep()
                return
            self.read_block()
            if 'reflevel' in data['context_pkt']:
                self.ref_level = data['context_pkt']['reflevel']

            self.pow_data = compute_fft(self.dut, data['data_pkt'], data['context_pkt'], ref = self.ref_level)
            self.raw_data = data['data_pkt']



            self.update_plot()

    def receive_sweep(self, fstart, fstop, data):
        self.sweep_segments = list(self.sweep_dut.sweep_segments)
        self.usable_bins = None
        if self.plot_state.block_mode:
            self.read_block()
            return
        self.read_sweep()

        if len(data) > 2:
            self.pow_data = data
        self.iq_data = None

        self.update_plot()


    def keyPressEvent(self, event):
        if self.dut:
            hotkey_util(self, event)
           
    def mousePressEvent(self, event):
        if self.dut:
            marker = self._plot.markers[self._marker_tab.currentIndex()]
            trace = self._plot.traces[marker.trace_index]
            if event.button() == QtCore.Qt.MouseButton.LeftButton:
                click_pos =  event.pos().x() - 68
                plot_window_width = self._plot.window.width() - 68

                if click_pos < plot_window_width and click_pos > 0:

                    window_freq = self._plot.view_box.viewRange()[0]
                    window_bw =  (window_freq[1] - window_freq[0])
                    click_freq = ((float(click_pos) / float(plot_window_width)) * float(window_bw)) + window_freq[0]
                    index = find_nearest_index(click_freq, trace.freq_range)
                    self._plot.markers[self._marker_tab.currentIndex()].data_index = index

    def initUI(self):
        grid = QtGui.QGridLayout()
        grid.setSpacing(10)
        for x in range(8):
            grid.setColumnMinimumWidth(x, 300)

        # add plot widget
        plot_width = 8
        
        grid.addWidget(self._plot_layout(),0,0,13,plot_width)
        
        self.marker_labels = []
        marker_label, delta_label, diff_label = self._marker_labels()
        self.marker_labels.append(marker_label)
        self.marker_labels.append(delta_label)
        grid.addWidget(marker_label, 0, 1, 1, 2)
        grid.addWidget(delta_label, 0, 3, 1, 2)
        grid.addWidget(diff_label , 0, 5, 1, 2)
 
        y = 0
        x = plot_width

        controls_layout = QtGui.QVBoxLayout()
        controls_layout.addWidget(self._trace_controls())
        controls_layout.addWidget(self._plot_controls())
        controls_layout.addWidget(self._device_controls())
        controls_layout.addWidget(self._freq_controls())
        controls_layout.addStretch()
        grid.addLayout(controls_layout, y, x, 13, 5)

        self._grid = grid
        self.update_freq()


        self.setLayout(grid)

    def _plot_layout(self):
        vsplit = QtGui.QSplitter()
        vsplit.setOrientation(QtCore.Qt.Vertical)
        vsplit.addWidget(self._plot.window)

        hsplit = QtGui.QSplitter()
        hsplit.addWidget(self._plot.const_window)
        hsplit.addWidget(self._plot.iq_window)
        self._plot.const_window.heightForWidth(1)
        self._plot.const_window.hide()
        self._plot.iq_window.hide()
        vsplit.addWidget(hsplit)

        self._plot_layout = vsplit
        return self._plot_layout

    def _trace_controls(self):
        trace_group = QtGui.QGroupBox("Traces")
  
        self._trace_group = trace_group
        
        trace_controls_layout = QtGui.QVBoxLayout()
        
        # first row will contain the tabs
        first_row = QtGui.QHBoxLayout()
        
        # add tabs for each trace
        trace_tab = QtGui.QTabBar()
        count = 0
        for (trace,(r,g,b)) in zip(labels.TRACES, colors.TRACE_COLORS):
            trace_tab.addTab(trace)
            color = QtGui.QColor()
            color.setRgb(r,g,b)
            pixmap = QtGui.QPixmap(10,10)
            pixmap.fill(color)
            icon = QtGui.QIcon(pixmap)
            trace_tab.setTabIcon(count,icon)
            count += 1
        
        self._trace_tab = trace_tab
        trace_tab.currentChanged.connect(lambda: cu._trace_tab_change(self))
            
        self.control_widgets.append(self._trace_tab)
        first_row.addWidget(trace_tab)
        
        # second row contains the tab attributes
        second_row = QtGui.QHBoxLayout()
        max_hold, min_hold, write, store, blank  = self._trace_items()
        second_row.addWidget(max_hold)
        second_row.addWidget(min_hold)
        second_row.addWidget(write)
        second_row.addWidget(blank)
        second_row.addWidget(store)
        trace_controls_layout.addLayout(first_row)
        trace_controls_layout.addLayout(second_row) 
        trace_group.setLayout(trace_controls_layout)
        return trace_group
        
    def _trace_items(self):
    
        trace_attr = {}
        store = QtGui.QCheckBox('Store')
        store.clicked.connect(lambda: cu._store_trace(self))
        store.setEnabled(False)
        trace_attr['store'] = store

        max_hold = QtGui.QRadioButton('Max Hold')
        max_hold.clicked.connect(lambda: cu._max_hold(self))
        trace_attr['max_hold'] = max_hold
        
        min_hold = QtGui.QRadioButton('Min Hold')
        min_hold.clicked.connect(lambda: cu._min_hold(self))
        trace_attr['min_hold'] = min_hold
        
        write = QtGui.QRadioButton('Write')
        write.clicked.connect(lambda: cu._trace_write(self))
        trace_attr['write'] = write
         
        blank = QtGui.QRadioButton('Blank')
        blank.clicked.connect(lambda: cu._blank_trace(self))
        trace_attr['blank'] = blank
                
        self._trace_attr = trace_attr
        self._trace_attr['write'].click()
        return max_hold, min_hold, write, store, blank

    def _device_controls(self):
        self._dev_group = DeviceControlsWidget()
        self._connect_device_controls()
        self.control_widgets.append(self._dev_group)
        return self._dev_group

    def _connect_device_controls(self):

        def new_antenna():
            self.plot_state.dev_set['antenna'] = (int(self._dev_group._antenna_box.currentText().split()[-1]))
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_dec():
            self.plot_state.dev_set['decimation'] = int(
                self._dev_group._dec_box.currentText().split(' ')[-1])
            self.cap_dut.configure_device(self.plot_state.dev_set)
            self.update_freq()

        def new_freq_shift():
            rfe_mode = 'ZIF'
            prop = self.dut.properties
            max_fshift = prop.MAX_FSHIFT[rfe_mode]
            try:
                if float(self._dev_group._freq_shift_edit.text()) * M < max_fshift:
                    self.plot_state.dev_set['fshift'] = float(self._dev_group._freq_shift_edit.text()) * M
                else:
                    self._dev_group._freq_shift_edit.setText(str(self.plot_state.dev_set['fshift'] / M))
            except ValueError:
                self._dev_group._freq_shift_edit.setText(str(self.plot_state.dev_set['fshift'] / M))
                return
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_gain():
            self.plot_state.dev_set['gain'] = self._dev_group._gain_box.currentText().split()[-1].lower().encode('ascii')
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_ifgain():
            self.plot_state.dev_set['ifgain'] = self._dev_group._ifgain_box.value()
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_attenuator():
            self.plot_state.dev_set['attenuator'] = self._dev_group._attenuator_box.isChecked()
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_iq_path():
            self.plot_state.dev_set['iq_output_path'] = str(self._dev_group._iq_output_box.currentText().split()[-1])
            if self._dev_group._mode.currentIndex() > 2:
                self._dev_group._mode.setCurrentIndex(0)

            if self.plot_state.dev_set['iq_output_path'] == 'CONNECTOR':
                # disable unwanted controls
                cu._external_digitizer_mode(self)
            else:
                cu._internal_digitizer_mode(self)
            self.cap_dut.configure_device(self.plot_state.dev_set)

        def new_input_mode():
            m = self._dev_group._mode.currentText()
            if not m:
                return
            if m.startswith('Sweep '):
                self._plot.const_window.hide()
                self._plot.iq_window.hide()
                self.plot_state.disable_block_mode(self)
                self._dev_group._dec_box.setEnabled(False)
                self._dev_group._freq_shift_edit.setEnabled(False)
                self._sweep_mode = m.split(' ',1)[1]
                return

            self._plot.const_window.show()
            self._plot.iq_window.show()
            self.plot_state.enable_block_mode(self)

            self.plot_state.dev_set['rfe_mode'] = str(m)
            cu._update_rbw_values(self)
            self.plot_state.bandwidth = self.dut_prop.FULL_BW[m]
            self.plot_state.update_freq_set(
                fcenter=float(self._freq_edit.text()) * M)
            cu._center_plot_view(self)
            self.cap_dut.configure_device(self.plot_state.dev_set)

            self._rbw_box.setCurrentIndex(4 if m == 'SH' else 3)
            cu._center_plot_view(self)
            if m == 'HDR':
                self._dev_group._dec_box.setEnabled(False)
                self._dev_group._freq_shift_edit.setEnabled(False)
            else:
                self._dev_group._dec_box.setEnabled(True)
                self._dev_group._freq_shift_edit.setEnabled(True)

        self._dev_group._antenna_box.currentIndexChanged.connect(new_antenna)
        self._dev_group._gain_box.currentIndexChanged.connect(new_gain)
        self._dev_group._dec_box.currentIndexChanged.connect(new_dec)
        self._dev_group._freq_shift_edit.returnPressed.connect(new_freq_shift) 
        self._dev_group._ifgain_box.valueChanged.connect(new_ifgain)
        self._dev_group._attenuator_box.clicked.connect(new_attenuator)
        self._dev_group._mode.currentIndexChanged.connect(new_input_mode)
        self._dev_group._iq_output_box.currentIndexChanged.connect(new_iq_path)
    
    def _trigger_control(self):
        trigger = QtGui.QCheckBox("Trigger")
        trigger.setToolTip("[T]\nTurn the Triggers on/off") 
        trigger.clicked.connect(lambda: cu._trigger_control(self))
        self._trigger = trigger
        self.control_widgets.append(self._trigger)
        return trigger

    def _attenuator_control(self):
        attenuator = QtGui.QCheckBox("Attenuator")
        attenuator.setChecked(True)
        def new_attenuator():
            self.plot_state.dev_set['attenuator'] = attenuator.isChecked()
        attenuator.clicked.connect(new_attenuator)
        self._attenuator_box = attenuator
        self.control_widgets.append(attenuator)
        return attenuator
    
    def _freq_controls(self):
        freq_group = QtGui.QGroupBox("Frequency Control")
        self._freq_group = freq_group

        freq_layout = QtGui.QVBoxLayout()

        fstart_hbox = QtGui.QHBoxLayout()
        fstart_bt, fstart_txt = self._fstart_controls()
        fstart_hbox.addWidget(fstart_bt)
        fstart_hbox.addWidget(fstart_txt)
        fstart_hbox.addWidget(QtGui.QLabel('MHz'))
        self._fstart_hbox = fstart_hbox

        cfreq_hbox = QtGui.QHBoxLayout()
        cfreq_bt, cfreq_txt = self._center_freq()
        cfreq_hbox.addWidget(cfreq_bt)
        cfreq_hbox.addWidget(cfreq_txt)
        cfreq_hbox.addWidget(QtGui.QLabel('MHz'))
        self._cfreq_hbox = cfreq_hbox

        bw_hbox = QtGui.QHBoxLayout()
        bw_bt, bw_txt = self._bw_controls()
        bw_hbox.addWidget(bw_bt)
        bw_hbox.addWidget(bw_txt)
        bw_hbox.addWidget(QtGui.QLabel('MHz'))
        self._bw_hbox = bw_hbox

        fstop_hbox = QtGui.QHBoxLayout()
        fstop_bt, fstop_txt = self._fstop_controls()
        fstop_hbox.addWidget(fstop_bt)
        fstop_hbox.addWidget(fstop_txt)
        fstop_hbox.addWidget(QtGui.QLabel('MHz'))
        self._fstop_hbox = fstop_hbox

        freq_inc_hbox = QtGui.QHBoxLayout()
        freq_inc_steps, freq_inc_plus, freq_inc_minus = self._freq_incr()
        freq_inc_hbox.addWidget(freq_inc_minus)
        freq_inc_hbox.addWidget(freq_inc_steps)
        freq_inc_hbox.addWidget(freq_inc_plus)
        self._freq_inc_hbox = freq_inc_hbox

        rbw_hbox = QtGui.QHBoxLayout()
        rbw = self._rbw_controls()
        rbw_hbox.addWidget(QtGui.QLabel('Resolution Bandwidth:'))
        rbw_hbox.addWidget(rbw)
        self._rbw_hbox = rbw_hbox

        freq_layout.addLayout(self._fstart_hbox)
        freq_layout.addLayout(self._cfreq_hbox)
        freq_layout.addLayout(self._bw_hbox)
        freq_layout.addLayout(self._fstop_hbox)
        freq_layout.addLayout(self._freq_inc_hbox)
        freq_layout.addLayout(self._rbw_hbox)
        freq_group.setLayout(freq_layout)
        self._freq_layout = freq_layout
        return freq_group

    def _center_freq(self):
        cfreq = QtGui.QPushButton('Center')
        cfreq.setToolTip("[2]\nTune the center frequency") 
        self._cfreq = cfreq
        cfreq.clicked.connect(lambda: cu._select_center_freq(self))
        freq_edit = QtGui.QLineEdit(str(gui_config.INIT_CENTER_FREQ / float(M)))
        self._freq_edit = freq_edit
        self.control_widgets.append(self._cfreq)
        self.control_widgets.append(self._freq_edit)
        def freq_change():
            cu._select_center_freq(self)
            self.update_freq()
            self.update_freq_edit()

        freq_edit.returnPressed.connect(lambda: freq_change())
        return cfreq, freq_edit

    def _freq_incr(self):
        steps = QtGui.QComboBox(self)
        steps.addItem("Adjust: 1 MHz")
        steps.addItem("Adjust: 2.5 MHz")
        steps.addItem("Adjust: 10 MHz")
        steps.addItem("Adjust: 25 MHz")
        steps.addItem("Adjust: 100 MHz")
        self.fstep = float(steps.currentText().split()[1])
        def freq_step_change():
            self.fstep = float(steps.currentText().split()[1])
        steps.currentIndexChanged.connect(freq_step_change)
        steps.setCurrentIndex(2)
        self._fstep_box = steps
        def freq_step(factor):
            try:
                f = float(self._freq_edit.text())
            except ValueError:
                return
            delta = float(steps.currentText().split()[1]) * factor
            self.update_freq(delta)
            self.update_freq_edit()   
        freq_minus = QtGui.QPushButton('-')
        freq_minus.clicked.connect(lambda: freq_step(-1))
        self._freq_minus = freq_minus
        freq_plus = QtGui.QPushButton('+')
        freq_plus.clicked.connect(lambda: freq_step(1))
        self._freq_plus = freq_plus
        self.control_widgets.append(self._freq_plus)
        self.control_widgets.append(self._freq_minus)
        self.control_widgets.append(self._fstep_box)
        return  steps, freq_plus, freq_minus

    def _bw_controls(self):
        bw = QtGui.QPushButton('Span')
        bw.setToolTip("[3]\nChange the bandwidth of the current plot")
        self._bw = bw
        bw.clicked.connect(lambda: cu._select_bw(self))
        bw_edit = QtGui.QLineEdit(str(gui_config.INIT_BANDWIDTH / float(M)))
        def freq_change():
            cu._select_bw(self)
            self.update_freq()
            self.update_freq_edit()   
        bw_edit.returnPressed.connect(lambda: freq_change())
        self._bw_edit = bw_edit
        self.control_widgets.append(self._bw_edit)
        self.control_widgets.append(self._bw)
        return bw, bw_edit

    def _fstart_controls(self):
        fstart = QtGui.QPushButton('Start')
        fstart.setToolTip("[1]\nTune the start frequency")
        self._fstart = fstart
        fstart.clicked.connect(lambda: cu._select_fstart(self))
        f = gui_config.INIT_CENTER_FREQ - (gui_config.INIT_BANDWIDTH / 2)
        freq = QtGui.QLineEdit(str(f / float(M)))
        def freq_change():
            cu._select_fstart(self)
            self.update_freq()
            self.update_freq_edit()

        freq.returnPressed.connect(lambda: freq_change())
        self._fstart_edit = freq
        self.control_widgets.append(self._fstart)
        self.control_widgets.append(self._fstart_edit)
        return fstart, freq

    def _fstop_controls(self):
        fstop = QtGui.QPushButton('Stop')
        fstop.setToolTip("[4]Tune the stop frequency") 
        self._fstop = fstop
        fstop.clicked.connect(lambda: cu._select_fstop(self))
        f = gui_config.INIT_CENTER_FREQ + (gui_config.INIT_BANDWIDTH / 2)
        freq = QtGui.QLineEdit(str(f / float(M)))
        def freq_change():
            cu._select_fstop(self)   
            self.update_freq()
            self.update_freq_edit()            
        freq.returnPressed.connect(lambda: freq_change())
        self._fstop_edit = freq
        self.control_widgets.append(self._fstop)
        self.control_widgets.append(self._fstop_edit)
        return fstop, freq

    def _rbw_controls(self):
        rbw = QtGui.QComboBox(self)
        rbw.setToolTip("Change the RBW of the FFT plot")
        self._points_values = RBW_VALUES
        self._hdr_points_values = HDR_RBW_VALUES
        self._rbw_box = rbw
        rbw.addItems([str(p) + ' KHz' for p in self._points_values])

        def new_rbw():
        
            if not self.plot_state.dev_set['rfe_mode'] == 'HDR':    
                self.plot_state.update_freq_set(rbw = 1e3 * self._points_values[rbw.currentIndex()])
            else:
                self.plot_state.update_freq_set(rbw = self._hdr_points_values[rbw.currentIndex()])

        rbw.setCurrentIndex(0)
        rbw.currentIndexChanged.connect(new_rbw)
        self.control_widgets.append(self._rbw_box)
        return rbw

    def update_freq(self, delta=None):
        if not self.dut:
            return
        prop = self.dut.properties
        rfe_mode = self.plot_state.dev_set['rfe_mode']
        min_tunable = prop.MIN_TUNABLE[rfe_mode]
        max_tunable = prop.MAX_TUNABLE[rfe_mode]
        if delta == None:
            delta = 0    
        try:
            if self.plot_state.freq_sel == 'CENT':
                f = (float(self._freq_edit.text()) + delta) * M
                if f > max_tunable or f < min_tunable:
                    return
                self.plot_state.update_freq_set(fcenter = f)
                self.cap_dut.configure_device(self.plot_state.dev_set)
            elif self.plot_state.freq_sel == 'FSTART':
                f = (float(self._fstart_edit.text()) + delta) * M
                if f > max_tunable or f <min_tunable or f > self.plot_state.fstop:
                    return
                self.plot_state.update_freq_set(fstart = f)
            
            elif self.plot_state.freq_sel == 'FSTOP': 
                f = (float(self._fstop_edit.text()) + delta) * M

                if f > max_tunable or f < min_tunable or f < self.plot_state.fstart:
                    return
                self.plot_state.update_freq_set(fstop = f)
            
            elif self.plot_state.freq_sel == 'BW':
                f = (float(self._bw_edit.text()) + delta) * M
                if f < 0:
                    return
                self.plot_state.update_freq_set(bw = f)
            for trace in self._plot.traces:
                try:
                    trace.data = self.pow_data
                except AttributeError:
                    break

        except ValueError:
            return
        if self.plot_state.trig:
            freq_region = self._plot.freqtrig_lines.getRegion()
            if (freq_region[0] < self.plot_state.fstart and freq_region[1] < self.plot_state.fstart) or (freq_region[0] > self.plot_state.fstop and freq_region[1] > self.plot_state.fstop):
                self._plot.freqtrig_lines.setRegion([self.plot_state.fstart,self.plot_state. fstop]) 

    def update_freq_edit(self):
        self._fstop_edit.setText("%0.1f" % (self.plot_state.fstop/ 1e6))
        self._fstart_edit.setText("%0.1f" % (self.plot_state.fstart/ 1e6))
        self._freq_edit.setText("%0.1f" % (self.plot_state.center_freq / 1e6))
        self._bw_edit.setText("%0.1f" % (self.plot_state.bandwidth / 1e6))
        self._center_bt.click()

    def _plot_controls(self):

        plot_group = QtGui.QGroupBox("Plot Control")
        self._plot_group = plot_group
        
        plot_controls_layout = QtGui.QVBoxLayout()
        
        first_row = QtGui.QHBoxLayout()
        marker_tab = QtGui.QTabBar()
        for marker in labels.MARKERS:
            marker_tab.addTab(marker)
        marker_tab.currentChanged.connect(lambda: cu._marker_tab_change(self))
        first_row.addWidget(marker_tab)
        
        self._marker_tab = marker_tab
        self.control_widgets.append(self._marker_tab)
        marker_check, marker_trace = self._marker_control()
        
        second_row = QtGui.QHBoxLayout()
        second_row.addWidget(marker_trace)
        second_row.addWidget(marker_check)
                
        third_row = QtGui.QHBoxLayout()
        third_row.addWidget(self._peak_control())
        third_row.addWidget(self._center_control())
        
        fourth_row = QtGui.QHBoxLayout()
        ref_level, ref_label, min_level, min_label = self._ref_controls()
        
        fourth_row.addWidget(ref_label)
        fourth_row.addWidget(ref_level)
        fourth_row.addWidget(min_label)
        fourth_row.addWidget(min_level)

        plot_controls_layout.addLayout(first_row)
        plot_controls_layout.addLayout(second_row)
        plot_controls_layout.addLayout(third_row)
        plot_controls_layout.addLayout(fourth_row)
        plot_group.setLayout(plot_controls_layout)
        
        return plot_group
        
    def _marker_control(self):
        marker_trace = QtGui.QComboBox()
        marker_trace.setEnabled(False)
        marker_trace.setMaximumWidth(50)
        marker_trace.currentIndexChanged.connect(lambda: cu._marker_trace_control(self))
        update_marker_traces(marker_trace, self._plot.traces)
        
        self._marker_trace = marker_trace
        marker_check = QtGui.QCheckBox('Enabled')
        marker_check.clicked.connect(lambda: cu._marker_control(self))
        self._marker_check = marker_check

        self.control_widgets.append(self._marker_check)
        return marker_check,marker_trace
            
    def _peak_control(self):
        peak = QtGui.QPushButton('Peak')
        peak.setToolTip("[P]\nFind peak of the selected spectrum") 
        peak.clicked.connect(lambda: cu._find_peak(self))
        self._peak = peak
        self.control_widgets.append(self._peak)
        return peak
                
    def _center_control(self):
        center = QtGui.QPushButton('Recenter')
        center.setToolTip("[C]\nCenter the Plot View around the available spectrum") 
        center.clicked.connect(lambda: cu._center_plot_view(self))
        self._center_bt = center
        self.control_widgets.append(self._center_bt)
        return center
    
    def _ref_controls(self):
        ref_level = QtGui.QLineEdit(str(PLOT_YMAX))
        ref_level.returnPressed.connect(lambda: cu._change_ref_level(self))
        self._ref_level = ref_level
        self.control_widgets.append(self._ref_level)
        ref_label = QtGui.QLabel('Reference Level: ')
        
        min_level = QtGui.QLineEdit(str(PLOT_YMIN)) 
        min_level.returnPressed.connect(lambda: cu._change_min_level(self))
        min_label = QtGui.QLabel('Minimum Level: ')
        self._min_level = min_level
        self.control_widgets.append(self._min_level)
        return ref_level, ref_label, min_level, min_label

    def _marker_labels(self):
        marker_label = QtGui.QLabel('')
        marker_label.setStyleSheet('color: %s;' % colors.TEAL)
        marker_label.setMinimumHeight(25)
        
        delta_label = QtGui.QLabel('')
        delta_label.setStyleSheet('color: %s;' % colors.TEAL)
        delta_label.setMinimumHeight(25)
        
        diff_label = QtGui.QLabel('')
        diff_label.setStyleSheet('color: %s;' % colors.WHITE)
        diff_label.setMinimumHeight(25)
        self._diff_lab = diff_label
        return marker_label,delta_label, diff_label
       
    def update_plot(self):
       
        self.plot_state.update_freq_range(self.plot_state.fstart,
                                              self.plot_state.fstop, 
                                              len(self.pow_data))
        self.update_trace()
        self.update_iq()
        self.update_marker()
        self.update_diff()

    def update_trace(self):
        for trace in self._plot.traces:
            trace.update_curve(
                self.plot_state.freq_range,
                self.pow_data,
                self.usable_bins,
                self.sweep_segments)


    def update_iq(self):

        if self.raw_data.stream_id == VRT_IFDATA_I14Q14:    
            data = self.raw_data.data.numpy_array()
            i_data = np.array(data[:,0], dtype=float)/ZIF_BITS
            q_data = np.array(data[:,1], dtype=float)/ZIF_BITS
            self._plot.i_curve.setData(i_data)
            self._plot.q_curve.setData(q_data)
            self._plot.const_plot.clear()
            self._plot.const_plot.addPoints(
                x = i_data[0:CONST_POINTS],
                y = q_data[0:CONST_POINTS],
                symbol = 'o',
                size = 1, pen = 'y',
                brush = 'y')
        
        else:
            data = self.raw_data.data.numpy_array()
            i_data = np.array(data, dtype=float)
            if self.raw_data.stream_id == VRT_IFDATA_I14:
                i_data = i_data /ZIF_BITS
            self._plot.i_curve.setData(i_data)
            
            self._plot.q_curve.clear()
            self._plot.const_plot.clear()
            
    
    def update_trig(self):
            if self.plot_state.trig_set:
                freq_region = self._plot.freqtrig_lines.getRegion()
                self.plot_state.trig_set = TriggerSettings(TRIGGER_TYPE_LEVEL,
                                                        min(freq_region), 
                                                        max(freq_region),
                                                        self._plot.amptrig_line.value())

                self.dut.trigger(self.plot_state.trig_set)
    def update_marker(self):        
            
            for marker, marker_label in zip(self._plot.markers, self.marker_labels):
                if marker.enabled:
                    trace = self._plot.traces[marker.trace_index]

                    if not trace.blank:
                        marker_label.setStyleSheet('color: rgb(%s, %s, %s);' % (trace.color[0],
                                                                             trace.color[1],
                                                                            trace.color[2]))
                        
                        marker.update_pos(trace.freq_range, trace.data)
                        marker_text = 'Frequency: %0.2f MHz \n Power %0.2f dBm' % (trace.freq_range[marker.data_index]/1e6, 
                                                                                   trace.data[marker.data_index])
                        marker_label.setText(marker_text)

                else:
                    marker_label.setText('')

    def update_diff(self):

        num_markers = 0
        traces = []
        data_indices = []
        for marker in self._plot.markers:

            if marker.enabled == True:
                num_markers += 1
                traces.append(self._plot.traces[marker.trace_index])
                data_indices.append(marker.data_index)
                
        if num_markers == len(labels.MARKERS):
            freq_diff = np.abs((traces[0].freq_range[data_indices[0]]/1e6) - (traces[1].freq_range[data_indices[1]]/1e6))
            
            power_diff = np.abs((traces[0].data[data_indices[0]]) - (traces[1].data[data_indices[1]]))
            
            delta_text = 'Delta : %0.1f MHz \nDelta %0.2f dBm' % (freq_diff, power_diff )
            self._diff_lab.setText(delta_text)
        else:
            self._diff_lab.setText('')

    def enable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(True)
            
        
        for key in self._trace_attr:
            self._trace_attr[key].setEnabled(True)
        
    def disable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(False)
            
        for key in self._trace_attr:
            self._trace_attr[key].setEnabled(False)
Beispiel #9
0
class MainPanel(QtGui.QWidget):
    """
    The spectrum view and controls
    """
    def __init__(self, main_window):
        self._main_window = main_window
        self.ref_level = 0
        self.dut = None
        self.control_widgets = []
        super(MainPanel, self).__init__()
        screen = QtGui.QDesktopWidget().screenGeometry()
        self.setMinimumWidth(WINDOW_WIDTH)
        self.setMinimumHeight(WINDOW_HEIGHT)
        self._vrt_context = {}
        self._reactor = self._get_reactor()

    def _get_reactor(self):
        # late import because installReactor is being used
        from twisted.internet import reactor
        return reactor

    @inlineCallbacks
    def open_device(self, name, ok):
        if not ok:
            self._main_window.show()
            return

        if self.dut:
            self.dut.disconnect()
        self._main_window.show()
        dut = WSA(connector=TwistedConnector(self._reactor))
        yield dut.connect(name)
        self.dev_set = {
            'attenuator': 0,
            'freq': 2450e6,
            'decimation': 1,
            'fshift': 0,
            'rfe_mode': 'SH',
            'iq_output_path': 'DIGITIZER'
        }
        self.dut = dut
        self.dut_prop = self.dut.properties
        self.bandwidth = self.dut_prop.FULL_BW[self.dev_set['rfe_mode']]
        self.rbw = 125000000 / SAMPLE_VALUES[3]
        self.enable_mhold = False
        self.mhold = []
        self.cap_dut = CaptureDevice(dut,
                                     async_callback=self.receive_capture,
                                     device_settings=self.dev_set)
        self.initUI()
        self.enable_controls()
        self.read_block()

    def read_block(self):
        rbw = self.rbw
        self.cap_dut.capture_time_domain(self.dev_set['rfe_mode'],
                                         self.dev_set['freq'], rbw)

    def receive_capture(self, fstart, fstop, data):
        # store usable bins before next call to capture_time_domain
        self.usable_bins = list(self.cap_dut.usable_bins)
        self.sweep_segments = None

        self.read_block()
        if 'reflevel' in data['context_pkt']:
            self.ref_level = data['context_pkt']['reflevel']
        self.pow_data = compute_fft(self.dut,
                                    data['data_pkt'],
                                    data['context_pkt'],
                                    ref=self.ref_level)
        self.raw_data = data['data_pkt']
        self.freq_range = (fstart, fstop)
        self.update_trace()

    def initUI(self):
        grid = QtGui.QGridLayout()
        grid.setSpacing(5)
        plot_width = 4
        for x in range(plot_width):
            grid.setColumnMinimumWidth(x, 250)
        grid.setColumnMinimumWidth(plot_width + 1, 260)

        grid.addWidget(self._plot_layout(), 0, 0, 5, plot_width)

        controls_layout = QtGui.QVBoxLayout()
        controls_layout.setSpacing(1)
        controls_layout.addWidget(self._if_attenuator())
        controls_layout.addWidget(self._hdr_gain())
        controls_layout.addWidget(self._center_freq())
        controls_layout.addWidget(self._rbw_controls())

        controls_layout.addWidget(self._mode_controls())
        controls_layout.addWidget(self._maxh_controls())
        controls_layout.addStretch()
        grid.addLayout(controls_layout, 0, plot_width + 1, 0, 5)

        self._grid = grid
        self.setLayout(grid)

    def _plot_layout(self):
        # create spectral plot
        self.window = pg.PlotWidget(name='pyrf_plot')
        self.window.showGrid(True, True)
        self.window.setYRange(PLOT_YMIN, PLOT_YMAX)
        self.fft_curve = self.window.plot(pen='g')
        self.mhold_curve = self.window.plot(pen='y')

        # create IQ plot widget
        self.iq_window = pg.PlotWidget(name='IQ Plot')
        self.i_curve = self.iq_window.plot(pen='r')
        self.q_curve = self.iq_window.plot(pen='g')

        # create split
        vsplit = QtGui.QSplitter()
        vsplit.setOrientation(QtCore.Qt.Vertical)
        vsplit.addWidget(self.window)
        vsplit.addWidget(self.iq_window)
        self._plot_layout = vsplit
        return self._plot_layout

    def _center_freq(self):
        grid, widget = self.create_grid_and_widget('Frequency (MHz)')
        freq_edit = QtGui.QLineEdit(str(self.dev_set['freq'] / float(MHZ)))
        self._freq_edit = freq_edit
        self.control_widgets.append(self._freq_edit)

        def freq_change():
            self.dev_set['freq'] = float(freq_edit.text()) * MHZ
            self.dut.freq(int(self.dev_set['freq']))

        freq_edit.returnPressed.connect(lambda: freq_change())
        grid.addWidget(freq_edit, 0, 1, 0, 1)
        widget.setLayout(grid)
        return widget

    def _if_attenuator(self):
        grid, widget = self.create_grid_and_widget('Attenuation (dB)')
        if_attenuator = QtGui.QLineEdit('0')
        if_attenuator.setToolTip("Change the attenuation level")

        self.control_widgets.append(if_attenuator)

        def atten_change():
            self.dut.attenuator(int(if_attenuator.text()))

        if_attenuator.returnPressed.connect(lambda: atten_change())
        grid.addWidget(if_attenuator, 0, 1, 0, 1)
        widget.setLayout(grid)
        return widget

    def _hdr_gain(self):
        grid, widget = self.create_grid_and_widget('HDR Gain (dB)')
        hdr_attenuator = QtGui.QLineEdit('25')
        hdr_attenuator.setToolTip("Change the HDR mode's gain level")

        self.control_widgets.append(hdr_attenuator)

        def atten_change():
            self.dut.hdr_gain(int(hdr_attenuator.text()))

        hdr_attenuator.returnPressed.connect(lambda: atten_change())
        grid.addWidget(hdr_attenuator, 0, 1, 0, 1)
        widget.setLayout(grid)
        return widget

    def _mode_controls(self):
        grid, widget = self.create_grid_and_widget('Mode')
        mode = QtGui.QComboBox(self)
        mode.addItems(MODES)

        def new_mode():
            self.dev_set['rfe_mode'] = MODES[mode.currentIndex()]
            self.dut.rfe_mode(self.dev_set['rfe_mode'])
            self.bandwidth = self.dut_prop.FULL_BW[self.dev_set['rfe_mode']]

        mode.setCurrentIndex(1)
        mode.currentIndexChanged.connect(new_mode)
        grid.addWidget(mode, 0, 1, 0, 1)
        widget.setLayout(grid)
        self.control_widgets.append(mode)
        return widget

    def _rbw_controls(self):
        grid, widget = self.create_grid_and_widget('Sample Size')
        rbw = QtGui.QComboBox(self)
        rbw.setToolTip("Change the RBW of the FFT plot")

        self._rbw_box = rbw
        rbw.addItems([str(p) + ' ' for p in SAMPLE_VALUES])

        def new_rbw():
            if self.dev_set['rfe_mode'] != 'ZIF':
                self.rbw = self.bandwidth * 2 / SAMPLE_VALUES[
                    rbw.currentIndex()]
            else:
                self.rbw = self.bandwidth / SAMPLE_VALUES[rbw.currentIndex()]

        rbw.setCurrentIndex(3)
        rbw.currentIndexChanged.connect(new_rbw)
        grid.addWidget(rbw, 0, 1, 0, 1)
        widget.setLayout(grid)
        self.control_widgets.append(self._rbw_box)
        return widget

    def _maxh_controls(self):
        grid, widget = self.create_grid_and_widget('Max Hold')
        max_hold = QtGui.QCheckBox(self)

        def change_max_hold():
            self.enable_mhold = max_hold.isChecked()

        max_hold.clicked.connect(change_max_hold)
        grid.addWidget(max_hold, 0, 1, 0, 1)
        widget.setLayout(grid)
        return widget

    def create_grid_and_widget(self, name):
        grid = QtGui.QGridLayout()
        widget = QtGui.QWidget()
        if name is not None:
            grid.addWidget(QtGui.QLabel(name), 0, 0, 0, 1)
        widget.setSizePolicy(QtGui.QSizePolicy.Preferred,
                             QtGui.QSizePolicy.Preferred)
        return grid, widget

    def update_trace(self):
        freq_range = np.linspace(self.freq_range[0], self.freq_range[1],
                                 len(self.pow_data))
        self.fft_curve.clear()
        self.fft_curve.setData(freq_range, self.pow_data)
        if self.enable_mhold:
            if len(self.mhold) != len(self.pow_data):
                self.mhold = self.pow_data
            else:
                self.mhold = np.maximum(self.mhold, self.pow_data)
            self.mhold_curve.setData(freq_range, self.mhold)
        else:
            self.mhold_curve.setData([], [])
            self.mhold = []

        i_data, q_data, stream_id, spec_inv = _decode_data_pkts(self.raw_data)
        self.i_curve.clear()
        self.q_curve.clear()
        self.i_curve.setData(i_data)
        if q_data is not None:
            self.q_curve.setData(q_data)
        else:
            self.q_curve.setData([])

    def enable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(True)

    def disable_controls(self):
        for item in self.control_widgets:
            item.setEnabled(False)
Beispiel #10
0
class SpecAController(QtCore.QObject):
    """
    The controller for the rtsa-gui.

    Issues commands to device, stores and broadcasts changes to GUI state.
    """
    _dut = None
    _sweep_device = None
    _capture_device = None
    _plot_options = None
    _state = None
    _recording_file = None
    _csv_file = None
    _export_csv = False
    _playback_file = None
    _playback_sweep_data = None
    _pending_user_xrange = None
    _applying_user_xrange = False
    _user_xrange_control_enabled = True
    _single_capture = False
    device_change = QtCore.Signal(object)
    state_change = QtCore.Signal(SpecAState, list)
    capture_receive = QtCore.Signal(SpecAState, float, float, object, object,
                                    object, object)
    options_change = QtCore.Signal(dict, list)
    plot_change = QtCore.Signal(dict, list)

    def __init__(self, developer_mode=False):
        super(SpecAController, self).__init__()
        self._dsp_options = {}
        self._options = {}
        self._plot_options = {'cont_cap_mode': True, 'mouse_tune': True}
        self.developer_mode = developer_mode
        self.was_sweeping = False

    def set_device(self, dut=None, playback_filename=None):
        """
        Detach any currenly attached device and stop playback then
        optionally attach to a new device or playback file.

        :param dut: a :class:`pyrf.thinkrf.WSA` or None
        :param playback_filename: recorded VRT data filename or None
        :param playback_scheduler: function to schedule each playback capture
        """
        if self._playback_file:
            self._playback_file.close()
            self._playback_file = None

        if self._dut:
            self._dut.disconnect()

        if playback_filename:
            self._playback_file = open(playback_filename, 'rb')
            self._playback_started = False
            self._playback_context = {}
            vrt_packet = self._playback_vrt(auto_rewind=False)
            state_json = vrt_packet.fields['speca']
            # support old playback files
            if state_json['device_identifier'] == 'unknown':
                state_json['device_identifier'] = 'ThinkRF,WSA5000 v3,0,0'
            dut = Playback(state_json['device_class'],
                           state_json['device_identifier'])
            self._sweep_device = SweepDevice(dut)
            self._capture_device = CaptureDevice(dut)
        elif dut:
            dut.reset()
            self._sweep_device = SweepDevice(dut, self.process_sweep)
            self._capture_device = CaptureDevice(dut, self.process_capture)
            state_json = dict(dut.properties.SPECA_DEFAULTS,
                              device_identifier=dut.device_id)

        self._dut = dut
        if not dut:
            return

        self.device_change.emit(dut)
        self._apply_complete_settings(state_json, bool(self._playback_file))
        self.start_capture()

    def start_recording(self, filename):
        """
        Start a new recording. Does nothing if we're
        currently playing a recording.
        """
        if self._playback_file:
            return
        self.stop_recording()
        self._recording_file = open(filename, 'wb')
        self._dut.set_recording_output(self._recording_file)
        self._dut.inject_recording_state(self._state.to_json_object())

    def stop_recording(self):
        """
        Stop recording or do nothing if not currently recording.
        """
        if not self._recording_file:
            return
        self._dut.set_recording_output(None)
        self._recording_file.close()
        self._recording_file = None

    def start_csv_export(self, filename):
        """
        Start exporting datainto CSV file
        """
        self._csv_file = open(filename, 'wb')
        self._csv_file.write('data,mode,fstart,fstop,size,timestamp\n')
        self._export_csv = True

    def stop_csv_export(self):
        """
        Stop exporting data into  CSV file
        """
        self._csv_file.close()
        self._export_csv = False

    def _export_csv_file(self, mode, fstart, fstop, data):
        """
        Save data to csv file
        """
        time = datetime.isoformat(datetime.utcnow()) + 'Z'
        self._csv_file.write(',%s,%0.2f,%0.2f,%d,%s\n' %
                             (mode, fstart, fstop, len(data), time))
        for d in data:
            self._csv_file.write('%0.2f\n' % d)

    def _apply_pending_user_xrange(self):
        if self._pending_user_xrange:
            self._applying_user_xrange = True
            start, stop = self._pending_user_xrange
            self._pending_user_xrange = None
            self.apply_settings(center=int(
                (start + stop) / 2.0 / self._dut.properties.TUNING_RESOLUTION)
                                * self._dut.properties.TUNING_RESOLUTION,
                                span=stop - start)
        else:
            self._applying_user_xrange = False

    def read_block(self):
        if not (self._plot_options['cont_cap_mode'] or self._single_capture):
            return
        self._apply_pending_user_xrange()
        device_set = dict(self._state.device_settings)
        device_set['decimation'] = self._state.decimation
        device_set['fshift'] = self._state.fshift
        device_set['rfe_mode'] = self._state.rfe_mode()
        device_set['freq'] = self._state.center
        self._capture_device.configure_device(device_set)

        self._capture_device.capture_time_domain(
            self._state.mode,
            self._state.center,
            self._state.rbw,
            force_change=self.was_sweeping)
        self.was_sweeping = False
        self._single_capture = False

    def read_sweep(self):
        if not (self._plot_options['cont_cap_mode'] or self._single_capture):
            return
        self._apply_pending_user_xrange()
        device_set = dict(self._state.device_settings)
        # device_set.pop('pll_reference')
        device_set.pop('iq_output_path')
        device_set.pop('trigger')
        self._dut.pll_reference(device_set['pll_reference'])
        device_set.pop('pll_reference')
        self._sweep_device.capture_power_spectrum(
            self._state.center - self._state.span / 2.0,
            self._state.center + self._state.span / 2.0,
            self._state.rbw,
            device_set,
            mode=self._state.rfe_mode())
        self.was_sweeping = True
        self._single_capture = False

    def start_capture(self, single=False):
        self._single_capture = single
        if self._playback_file:
            self.schedule_playback()
        elif self._state.sweeping():
            self.read_sweep()
        else:
            self.read_block()

    def schedule_playback(self):
        if self._single_capture:
            self._playback_started = False
        if not self._playback_started:
            QtCore.QTimer.singleShot(0, self._playback_step)
            self._playback_started = True

    def _playback_step(self, single=False):

        if not (self._plot_options['cont_cap_mode'] or self._single_capture):
            return
        if not self._playback_file:
            self._playback_started = False
            return

        QtCore.QTimer.singleShot(PLAYBACK_STEP_MSEC, self._playback_step)
        while True:

            pkt = self._playback_vrt()

            if pkt.is_context_packet():
                if 'speca' in pkt.fields:
                    self._playback_sweep_data = None
                    state_json = pkt.fields['speca']
                    self._apply_complete_settings(state_json, playback=True)
                else:
                    self._playback_context.update(pkt.fields)
                continue

            if self._state.sweeping():
                if not self._playback_sweep_step(pkt):
                    continue
                self._single_capture = False
                return
            break

        usable_bins = compute_usable_bins(self._dut.properties,
                                          self._state.rfe_mode(),
                                          len(pkt.data),
                                          self._state.decimation,
                                          self._state.fshift)

        usable_bins, fstart, fstop = adjust_usable_fstart_fstop(
            self._dut.properties, self._state.rfe_mode(), len(pkt.data),
            self._state.decimation, self._state.center, pkt.spec_inv,
            usable_bins)

        pow_data = compute_fft(self._dut, pkt, self._playback_context,
                               **self._dsp_options)

        if not self._options.get('show_attenuated_edges'):
            pow_data, usable_bins, fstart, fstop = (
                trim_to_usable_fstart_fstop(pow_data, usable_bins, fstart,
                                            fstop))
        if self._export_csv:
            self._export_csv_file(self._state.rfe_mode(), fstart, fstop,
                                  pow_data)
        self.capture_receive.emit(self._state, fstart, fstop, pkt, pow_data,
                                  usable_bins, None)

    def _playback_sweep_start(self):
        """
        ready a new playback sweep data array
        """
        nans = np.ones(int(self._state.span / self._state.rbw)) * np.nan
        self._playback_sweep_data = nans

    def _playback_sweep_step(self, pkt):
        """
        process one data packet from a recorded sweep and
        plot collected data after receiving complete sweep.

        returns True if data was plotted on this step.
        """
        if self._playback_sweep_data is None:
            self._playback_sweep_start()
            last_center = None
        else:
            last_center = self._playback_sweep_last_center

        sweep_start = float(self._state.center - self._state.span / 2)
        sweep_stop = float(self._state.center + self._state.span / 2)
        step_center = self._playback_context['rffreq']
        updated_plot = False
        if last_center is not None and last_center >= step_center:
            # starting a new sweep, plot the data we have
            self.capture_receive.emit(self._state, sweep_start, sweep_stop,
                                      None, self._playback_sweep_data, None,
                                      None)
            updated_plot = True
            self._playback_sweep_start()
        self._playback_sweep_last_center = step_center

        usable_bins = compute_usable_bins(self._dut.properties,
                                          self._state.rfe_mode(),
                                          len(pkt.data),
                                          self._state.decimation,
                                          self._state.fshift)

        usable_bins, fstart, fstop = adjust_usable_fstart_fstop(
            self._dut.properties, self._state.rfe_mode(), len(pkt.data),
            self._state.decimation, step_center, pkt.spec_inv, usable_bins)

        pow_data = compute_fft(self._dut, pkt, self._playback_context,
                               **self._dsp_options)

        pow_data, usable_bins, fstart, fstop = (trim_to_usable_fstart_fstop(
            pow_data, usable_bins, fstart, fstop))

        clip_left = max(sweep_start, fstart)
        clip_right = min(sweep_stop, fstop)
        sweep_points = len(self._playback_sweep_data)
        point_left = int((clip_left - sweep_start) * sweep_points /
                         (sweep_stop - sweep_start))
        point_right = int((clip_right - sweep_start) * sweep_points /
                          (sweep_stop - sweep_start))
        xvalues = np.linspace(clip_left, clip_right, point_right - point_left)

        if point_left >= point_right:
            logger.info('received sweep step outside sweep: %r, %r' %
                        ((fstart, fstop), (sweep_start, sweep_stop)))
        else:
            self._playback_sweep_data[point_left:point_right] = np.interp(
                xvalues, np.linspace(fstart, fstop, len(pow_data)), pow_data)

        return updated_plot

    def _playback_vrt(self, auto_rewind=True):
        """
        Return the next VRT packet in the playback file
        """
        reader = vrt_packet_reader(self._playback_file.read)
        data = None
        try:
            while True:
                data = reader.send(data)
        except StopIteration:
            pass
        except ValueError:
            return None

        if data == '' and auto_rewind:
            self._playback_file.seek(0)
            data = self._playback_vrt(auto_rewind=False)

        return None if data == '' else data

    def process_capture(self, fstart, fstop, data):
        # store usable bins before next call to capture_time_domain
        usable_bins = list(self._capture_device.usable_bins)

        # only read data if WSA digitizer is used
        if 'DIGITIZER' in self._state.device_settings['iq_output_path']:
            if self._state.sweeping():
                self.read_sweep()
                return
            self.read_block()
            if 'reflevel' in data['context_pkt']:
                self._ref_level = data['context_pkt']['reflevel']

            pow_data = compute_fft(self._dut,
                                   data['data_pkt'],
                                   data['context_pkt'],
                                   ref=self._ref_level,
                                   **self._dsp_options)

            if not self._options.get('show_attenuated_edges'):
                pow_data, usable_bins, fstart, fstop = (
                    trim_to_usable_fstart_fstop(pow_data, usable_bins, fstart,
                                                fstop))
            #FIXME: Find out why there is a case where pow_data may be empty

            if pow_data.any():
                if self._plot_options.get('reference_offset_value'):
                    pow_data += self._plot_options['reference_offset_value']
                if self._export_csv:
                    self._export_csv_file(self._state.rfe_mode(), fstart,
                                          fstop, pow_data)
                self.capture_receive.emit(self._state, fstart, fstop,
                                          data['data_pkt'], pow_data,
                                          usable_bins, None)

    def process_sweep(self, fstart, fstop, data):
        sweep_segments = list(self._sweep_device.sweep_segments)
        if not self._state.sweeping():
            self.read_block()
            return
        self.read_sweep()

        self.pow_data = data
        self.iq_data = None

        if not self._options.get('show_sweep_steps'):
            sweep_segments = None
        if self._plot_options.get('reference_offset_value'):
            self.pow_data += self._plot_options['reference_offset_value']
        if self._export_csv:
            self._export_csv_file(self._state.rfe_mode(), fstart, fstop,
                                  self.pow_data)
        self.capture_receive.emit(self._state, fstart, fstop, None,
                                  self.pow_data, None, sweep_segments)

    def _state_changed(self, state, changed):
        """
        Emit signal and handle special cases where extra work is needed in
        response to a state change.
        """
        # make sure resolution of center are the same as the device's tunning resolution
        center = float(
            np.round(
                state.center,
                -1 * int(np.log10(self._dut.properties.TUNING_RESOLUTION))))
        state = SpecAState(state, center=center)

        if not state.sweeping():
            # force span to correct value for the mode given
            if state.decimation > 1:
                span = (float(self._dut.properties.FULL_BW[state.rfe_mode()]) /
                        state.decimation *
                        self._dut.properties.DECIMATED_USABLE)
            else:
                span = self._dut.properties.USABLE_BW[state.rfe_mode()]
            state = SpecAState(state, span=span)
            changed = [x for x in changed if x != 'span']
            if not self._state or span != self._state.span:
                changed.append('span')
        elif 'mode' in changed and 'span' not in changed:
            span = self._dut.properties.DEFAULT_SPECA_SPAN
            state = SpecAState(state, span=span)
            changed.append('span')
        self._state = state

        # start capture loop again when user switches output path
        # back to the internal digitizer XXX: very WSA5000-specific
        if 'device_settings.iq_output_path' in changed:
            if state.device_settings.get('iq_output_path') == 'DIGITIZER':
                self.start_capture()
            elif state.device_settings.get('iq_output_path') == 'CONNECTOR':
                if state.sweeping():
                    state.mode = self._dut.properties.RFE_MODES[0]

        if self._recording_file:
            self._dut.inject_recording_state(state.to_json_object())

        self.state_change.emit(state, changed)

    def apply_device_settings(self, **kwargs):
        """
        Apply device-specific settings and trigger a state change event.
        :param kwargs: keyword arguments of SpecAState.device_settings
        """
        device_settings = dict(self._state.device_settings, **kwargs)
        state = SpecAState(self._state, device_settings=device_settings)

        if device_settings.get(
                'iq_output_path') == 'CONNECTOR' or 'trigger' in kwargs:
            self._capture_device.configure_device(device_settings)

        changed = ['device_settings.%s' % s for s in kwargs]

        self._state_changed(state, changed)

    def apply_settings(self, **kwargs):
        """
        Apply state settings and trigger a state change event.

        :param kwargs: keyword arguments of SpecAState attributes
        """
        if self._state is None:
            logger.warn('apply_settings with _state == None: %r' % kwargs)
            return

        state = SpecAState(self._state, **kwargs)
        self._state_changed(state, kwargs.keys())

    def _apply_complete_settings(self, state_json, playback):
        """
        Apply state setting changes from a complete JSON object. Used for
        initial settings and applying settings from a recording.
        """
        if self._state:
            old = self._state.to_json_object()
            old['playback'] = self._state.playback
        else:
            old = {}

        changed = [
            key for key, value in state_json.iteritems()
            if old.get(key) != value
        ]
        if old.get('playback') != playback:
            changed.append('playback')

        if 'device_settings' in changed:
            changed.remove('device_settings')
            oset = old.get('device_settings', {})
            dset = state_json['device_settings']
            changed.extend([
                'device_settings.%s' % key for key, value in dset.iteritems()
                if oset.get(key) != value
            ])

        state = SpecAState.from_json_object(state_json, playback)
        self._state_changed(state, changed)

    def apply_options(self, **kwargs):
        """
        Apply menu options and signal the change

        :param kwargs: keyword arguments of the dsp options
        """
        self._options.update(kwargs)
        self.options_change.emit(dict(self._options), kwargs.keys())

        for key, value in kwargs.iteritems():
            if key.startswith('dsp.'):
                self._dsp_options[key[4:]] = value

    def apply_plot_options(self, **kwargs):
        """
        Apply plot option changes and signal the change

        :param kwargs: keyword arguments of the plot options
        """
        self._plot_options.update(kwargs)
        self.plot_change.emit(dict(self._plot_options), kwargs.keys())

    def get_options(self):
        return dict(self._options)

    def enable_user_xrange_control(self, enable):
        self._user_xrange_control_enabled = enable
        if not enable:
            self._pending_user_xrange = None

    def user_xrange_changed(self, start, stop):
        if self._user_xrange_control_enabled:
            self._pending_user_xrange = start, stop

    def applying_user_xrange(self):
        return self._applying_user_xrange