def __init__(self, ip_address): super(MyWifiWSA, self).__init__() self._init_hardware(ip_address) self._numpts = 4096 self._center_MHz = 2447. self._span_MHz = 125. self._start_MHz = self._center_MHz - (self._span_MHz / 2) self._stop_MHz = self._center_MHz + (self._span_MHz / 2) self._frequencies_MHz = np.linspace(self._start_MHz, self._stop_MHz, self._numpts) self._last_power_spectrum_dBm = None self._capture_history_len = 2000 self.capture_history = WaterfallModel(self._frequencies_MHz, self._capture_history_len) self._capture_count = 0 self._capture_timer = QtCore.QTimer( ) #for app-side acquisition control self._capture_timer.timeout.connect(self._on_capture_timer) self.capturing = False self.reset()
def __init__(self, ip_address): super(MyWifiWSA, self).__init__() self._init_hardware(ip_address) self._numpts = 4096 self._center_MHz = 2447. self._span_MHz = 125. self._start_MHz = self._center_MHz - (self._span_MHz / 2) self._stop_MHz = self._center_MHz + (self._span_MHz / 2) self._frequencies_MHz = np.linspace(self._start_MHz, self._stop_MHz, self._numpts) self._last_power_spectrum_dBm = None self._capture_history_len = 2000 self.capture_history = WaterfallModel(self._frequencies_MHz, self._capture_history_len) self._capture_count = 0 self._capture_timer = QtCore.QTimer() #for app-side acquisition control self._capture_timer.timeout.connect(self._on_capture_timer) self.capturing = False self.reset()
class MyWifiWSA(object): """A cheap/simple/incomplete WSA instrument abstraction. Hides driver internals that people using the device as a spectrum analyzer may not care about. """ def __init__(self, ip_address): super(MyWifiWSA, self).__init__() self._init_hardware(ip_address) self._numpts = 4096 self._center_MHz = 2447. self._span_MHz = 125. self._start_MHz = self._center_MHz - (self._span_MHz / 2) self._stop_MHz = self._center_MHz + (self._span_MHz / 2) self._frequencies_MHz = np.linspace(self._start_MHz, self._stop_MHz, self._numpts) self._last_power_spectrum_dBm = None self._capture_history_len = 2000 self.capture_history = WaterfallModel(self._frequencies_MHz, self._capture_history_len) self._capture_count = 0 self._capture_timer = QtCore.QTimer() #for app-side acquisition control self._capture_timer.timeout.connect(self._on_capture_timer) self.capturing = False self.reset() def _init_hardware(self, ip_address): self._driver = WSA() self._driver.connect(ip_address) def reset(self): """Sets a known start state.""" #Set our app-specific reset state (802.11 range) center_frequency_MHz = 2447 self._reset_hardware() def _reset_hardware(self): drv = self._driver drv.reset() drv.request_read_perm() drv.freq(self._center_MHz) drv.decimation(1) # do decimate drv.attenuator(0) # don't attenuate def start_continuous_capture(self, capture_interval_s): #It would be nice to be able to set the capture interval on the #device itself so that we aren't slave to lagging issues on the OS. #We could also just calculate the number of pts required to achieve #the required capture interval (or close enough), and have the #resulting frequiency grid, res bw, et al adjust accordingly. But... #clearly out of scope for this cheap abstraction. #Timing will be managed by a QTimer in this case. As a result, #capture_interval_s is really done on best effort (although the #QTimer drivign it does make reasonable efforts to sync timing). self._capture_timer.stop() self._capture_timer.start(1000 * capture_interval_s) self.capturing = True def stop_continuous_capture(self): self.capturing = False self._capture_timer.stop() def acquire_single_power_spectrum(self): STAIRS_MODE = False self._capture_count += 1 if STAIRS_MODE: STAIR_WIDTH = 50 stair_start = self._capture_count % self._numpts stair_start = np.floor(stair_start / STAIR_WIDTH) * STAIR_WIDTH stair_stop = stair_start + STAIR_WIDTH ps_dBm = np.zeros(self._numpts) - 80 ps_dBm[stair_start:stair_stop] = -20 timestamp_s = self._capture_count else: drv = self._driver vrt_data, context = read_data_and_context(drv, self._numpts) assert isinstance(vrt_data, pyrf.vrt.DataPacket) ps_dBm = compute_fft(drv, vrt_data, context, convert_to_dbm=True) #Accumulate history... timestamp_s = calc_vrt_sample_time_s(vrt_data) self.capture_history.add_row(ps_dBm, timestamp_s = timestamp_s) self._last_power_spectrum_dBm = ps_dBm return ps_dBm def _on_capture_timer(self): #grab a spectrum! The historical model will be updated and events can #be driven from that directly. self.acquire_single_power_spectrum()
def __init__(self, controller, layout): super(Plot, self).__init__() self.controller = controller controller.device_change.connect(self.device_changed) controller.state_change.connect(self.state_changed) controller.plot_change.connect(self.plot_changed) self.plot_state = {} # initialize main fft window self.spectral_window = SpectralWidget(controller) self.window = self.spectral_window.window self.window.setMenuEnabled(False) def widget_range_changed(widget, ranges): if hasattr(self, 'gui_state') and hasattr(self, 'plot_state'): # HDR mode has a tuning resolution almost the same as its usable bandwidth, making the tuning mouse tuning annoying to use if self.gui_state.mode == 'HDR' or not self.plot_state[ 'mouse_tune']: return if not hasattr(ranges, '__getitem__'): return # we're not intereted in QRectF updates self.user_xrange_change.emit(ranges[0][0], ranges[0][1]) self.window.sigRangeChanged.connect(widget_range_changed) self.view_box = self.window.plotItem.getViewBox() # initialize the y-axis of the plot self.window.setYRange(PLOT_BOTTOM, PLOT_TOP) labelStyle = fonts.AXIS_LABEL_FONT self.window.setLabel('left', 'Power', 'dBm', **labelStyle) self.window.setLabel('top') self.window.setLabel('bottom', 'Frequency', 'Hz', **labelStyle) # horizontal cursor line cursor_pen = pg.mkPen(color=colors.YELLOW_NUM, width=2) self.cursor_line = pg.InfiniteLine(pos=-100, angle=0, movable=True, pen=cursor_pen) self.channel_power_region = pg.LinearRegionItem() self._trig_enable = False self.grid(True) # IQ constellation window self.const_window = pg.PlotWidget(name='const_plot') self.const_plot = pg.ScatterPlotItem(pen='y') self.const_window.setMenuEnabled(False) self.const_window.addItem(self.const_plot) self.const_window.setYRange(IQ_PLOT_YMIN, IQ_PLOT_YMAX) self.const_window.setXRange(IQ_PLOT_YMIN, IQ_PLOT_YMAX) # IQ time domain window self.iq_window = pg.PlotWidget(name='const_plot') self.iq_window.setYRange(IQ_PLOT_YMIN, IQ_PLOT_YMAX) self.iq_window.setMenuEnabled(False) self.update_iq_range = True self.i_curve = self.iq_window.plot(pen='g') self.q_curve = self.iq_window.plot(pen='r') # add traces self.traces = [] first_trace = labels.TRACES[0] for trace_name, trace_color in zip(labels.TRACES, colors.TRACE_COLORS): trace = Trace(self, trace_name, trace_color, blank=True, write=False) self.traces.append(trace) self.traces[0].blank = False self.traces[0].write = True self.markers = [] for name in labels.MARKERS: self.markers.append( Marker(self, name, colors.WHITE_NUM, self.controller)) self.waterfall_data = WaterfallModel(max_len=600) self.waterfall_window = ThreadedWaterfallPlotWidget( self.waterfall_data, scale_limits=(PLOT_YMIN, PLOT_YMAX), max_frame_rate_fps=30, mouse_move_crosshair=False, ) self.persistence_window = PersistencePlotWidget( decay_fn=decay_fn_EXPONENTIAL, data_model=self.waterfall_data) self.persistence_window.getAxis('bottom').setScale(1e-9) self.persistence_window.showGrid(True, True) self.trigger_control = triggerControl() self.connect_plot_controls() self.update_waterfall_levels(PLOT_BOTTOM, PLOT_TOP)
class MyWifiWSA(object): """A cheap/simple/incomplete WSA instrument abstraction. Hides driver internals that people using the device as a spectrum analyzer may not care about. """ def __init__(self, ip_address): super(MyWifiWSA, self).__init__() self._init_hardware(ip_address) self._numpts = 4096 self._center_MHz = 2447. self._span_MHz = 125. self._start_MHz = self._center_MHz - (self._span_MHz / 2) self._stop_MHz = self._center_MHz + (self._span_MHz / 2) self._frequencies_MHz = np.linspace(self._start_MHz, self._stop_MHz, self._numpts) self._last_power_spectrum_dBm = None self._capture_history_len = 2000 self.capture_history = WaterfallModel(self._frequencies_MHz, self._capture_history_len) self._capture_count = 0 self._capture_timer = QtCore.QTimer( ) #for app-side acquisition control self._capture_timer.timeout.connect(self._on_capture_timer) self.capturing = False self.reset() def _init_hardware(self, ip_address): self._driver = WSA() self._driver.connect(ip_address) def reset(self): """Sets a known start state.""" #Set our app-specific reset state (802.11 range) center_frequency_MHz = 2447 self._reset_hardware() def _reset_hardware(self): drv = self._driver drv.reset() drv.request_read_perm() drv.freq(self._center_MHz) drv.decimation(1) # do decimate drv.attenuator(0) # don't attenuate def start_continuous_capture(self, capture_interval_s): #It would be nice to be able to set the capture interval on the #device itself so that we aren't slave to lagging issues on the OS. #We could also just calculate the number of pts required to achieve #the required capture interval (or close enough), and have the #resulting frequiency grid, res bw, et al adjust accordingly. But... #clearly out of scope for this cheap abstraction. #Timing will be managed by a QTimer in this case. As a result, #capture_interval_s is really done on best effort (although the #QTimer drivign it does make reasonable efforts to sync timing). self._capture_timer.stop() self._capture_timer.start(1000 * capture_interval_s) self.capturing = True def stop_continuous_capture(self): self.capturing = False self._capture_timer.stop() def acquire_single_power_spectrum(self): STAIRS_MODE = False self._capture_count += 1 if STAIRS_MODE: STAIR_WIDTH = 50 stair_start = self._capture_count % self._numpts stair_start = np.floor(stair_start / STAIR_WIDTH) * STAIR_WIDTH stair_stop = stair_start + STAIR_WIDTH ps_dBm = np.zeros(self._numpts) - 80 ps_dBm[stair_start:stair_stop] = -20 timestamp_s = self._capture_count else: drv = self._driver vrt_data, context = read_data_and_context(drv, self._numpts) assert isinstance(vrt_data, pyrf.vrt.DataPacket) ps_dBm = compute_fft(drv, vrt_data, context, convert_to_dbm=True) #Accumulate history... timestamp_s = calc_vrt_sample_time_s(vrt_data) self.capture_history.add_row(ps_dBm, timestamp_s=timestamp_s) self._last_power_spectrum_dBm = ps_dBm return ps_dBm def _on_capture_timer(self): #grab a spectrum! The historical model will be updated and events can #be driven from that directly. self.acquire_single_power_spectrum()