2 # Copyright 2008 Free Software Foundation, Inc.
4 # This file is part of GNU Radio
6 # GNU Radio is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3, or (at your option)
11 # GNU Radio is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with GNU Radio; see the file COPYING. If not, write to
18 # the Free Software Foundation, Inc., 51 Franklin Street,
19 # Boston, MA 02110-1301, USA.
22 ##################################################
24 ##################################################
31 from constants import *
32 from gnuradio import gr #for gr.prefs
34 ##################################################
36 ##################################################
38 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP = -3, 0
39 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'waterfall_rate', 30)
40 DEFAULT_WIN_SIZE = (600, 300)
41 DIV_LEVELS = (1, 2, 5, 10, 20)
42 MIN_DYNAMIC_RANGE, MAX_DYNAMIC_RANGE = 10, 200
50 ##################################################
51 # Waterfall window control panel
52 ##################################################
53 class control_panel(wx.Panel):
55 A control panel with wx widgits to control the plotter and fft block chain.
58 def __init__(self, parent):
60 Create a new control panel.
61 @param parent the wx parent window
64 wx.Panel.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
65 control_box = wx.BoxSizer(wx.VERTICAL)
66 control_box.AddStretchSpacer()
67 control_box.Add(common.LabelText(self, 'Options'), 0, wx.ALIGN_CENTER)
69 control_box.AddStretchSpacer()
70 self.color_mode_chooser = common.DropDownController(self, 'Color', COLOR_MODES, parent, COLOR_MODE_KEY)
71 control_box.Add(self.color_mode_chooser, 0, wx.EXPAND)
73 control_box.AddStretchSpacer()
74 self.average_check_box = common.CheckBoxController(self, 'Average', parent.ext_controller, parent.average_key)
75 control_box.Add(self.average_check_box, 0, wx.EXPAND)
76 control_box.AddSpacer(2)
77 self.avg_alpha_slider = common.LogSliderController(
79 AVG_ALPHA_MIN_EXP, AVG_ALPHA_MAX_EXP, SLIDER_STEPS,
80 parent.ext_controller, parent.avg_alpha_key,
81 formatter=lambda x: ': %.4f'%x,
83 parent.ext_controller.subscribe(parent.average_key, self.avg_alpha_slider.Enable)
84 control_box.Add(self.avg_alpha_slider, 0, wx.EXPAND)
85 #dyanmic range buttons
86 control_box.AddStretchSpacer()
87 control_box.Add(common.LabelText(self, 'Dynamic Range'), 0, wx.ALIGN_CENTER)
88 control_box.AddSpacer(2)
89 self._dynamic_range_buttons = common.IncrDecrButtons(self, self._on_incr_dynamic_range, self._on_decr_dynamic_range)
90 control_box.Add(self._dynamic_range_buttons, 0, wx.ALIGN_CENTER)
92 control_box.AddStretchSpacer()
93 control_box.Add(common.LabelText(self, 'Set Ref Level'), 0, wx.ALIGN_CENTER)
94 control_box.AddSpacer(2)
95 self._ref_lvl_buttons = common.IncrDecrButtons(self, self._on_incr_ref_level, self._on_decr_ref_level)
96 control_box.Add(self._ref_lvl_buttons, 0, wx.ALIGN_CENTER)
98 control_box.AddStretchSpacer()
99 control_box.Add(common.LabelText(self, 'Set Time Scale'), 0, wx.ALIGN_CENTER)
100 control_box.AddSpacer(2)
101 self._time_scale_buttons = common.IncrDecrButtons(self, self._on_incr_time_scale, self._on_decr_time_scale)
102 control_box.Add(self._time_scale_buttons, 0, wx.ALIGN_CENTER)
104 control_box.AddStretchSpacer()
105 self.autoscale_button = wx.Button(self, label='Autoscale', style=wx.BU_EXACTFIT)
106 self.autoscale_button.Bind(wx.EVT_BUTTON, self.parent.autoscale)
107 control_box.Add(self.autoscale_button, 0, wx.EXPAND)
109 self.clear_button = wx.Button(self, label='Clear', style=wx.BU_EXACTFIT)
110 self.clear_button.Bind(wx.EVT_BUTTON, self._on_clear_button)
111 control_box.Add(self.clear_button, 0, wx.EXPAND)
113 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
114 control_box.Add(self.run_button, 0, wx.EXPAND)
116 self.SetSizerAndFit(control_box)
118 ##################################################
120 ##################################################
121 def _on_clear_button(self, event):
122 self.parent.set_num_lines(self.parent[NUM_LINES_KEY])
123 def _on_incr_dynamic_range(self, event):
124 self.parent.set_dynamic_range(
125 min(self.parent[DYNAMIC_RANGE_KEY] + 10, MAX_DYNAMIC_RANGE))
126 def _on_decr_dynamic_range(self, event):
127 self.parent.set_dynamic_range(
128 max(self.parent[DYNAMIC_RANGE_KEY] - 10, MIN_DYNAMIC_RANGE))
129 def _on_incr_ref_level(self, event):
130 self.parent.set_ref_level(
131 self.parent[REF_LEVEL_KEY] + self.parent[DYNAMIC_RANGE_KEY]*.1)
132 def _on_decr_ref_level(self, event):
133 self.parent.set_ref_level(
134 self.parent[REF_LEVEL_KEY] - self.parent[DYNAMIC_RANGE_KEY]*.1)
135 def _on_incr_time_scale(self, event):
136 old_rate = self.parent.ext_controller[self.parent.frame_rate_key]
137 self.parent.ext_controller[self.parent.frame_rate_key] *= 0.75
138 if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate:
139 self.parent.ext_controller[self.parent.decimation_key] += 1
140 def _on_decr_time_scale(self, event):
141 old_rate = self.parent.ext_controller[self.parent.frame_rate_key]
142 self.parent.ext_controller[self.parent.frame_rate_key] *= 1.25
143 if self.parent.ext_controller[self.parent.frame_rate_key] == old_rate:
144 self.parent.ext_controller[self.parent.decimation_key] -= 1
146 ##################################################
147 # Waterfall window with plotter and control panel
148 ##################################################
149 class waterfall_window(wx.Panel, pubsub.pubsub, common.prop_setter):
169 pubsub.pubsub.__init__(self)
171 self.ext_controller = controller
173 self.fft_size = fft_size
174 self.decimation_key = decimation_key
175 self.sample_rate_key = sample_rate_key
176 self.frame_rate_key = frame_rate_key
177 self.average_key = average_key
178 self.avg_alpha_key = avg_alpha_key
180 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
181 self.plotter = plotter.waterfall_plotter(self)
182 self.plotter.SetSize(wx.Size(*size))
183 self.plotter.set_title(title)
184 self.plotter.enable_point_label(True)
185 #setup the box with plot and controls
186 self.control_panel = control_panel(self)
187 main_box = wx.BoxSizer(wx.HORIZONTAL)
188 main_box.Add(self.plotter, 1, wx.EXPAND)
189 main_box.Add(self.control_panel, 0, wx.EXPAND)
190 self.SetSizerAndFit(main_box)
192 self.subscribe(COLOR_MODE_KEY, self.plotter.set_color_mode)
193 self.subscribe(NUM_LINES_KEY, self.plotter.set_num_lines)
195 self.ext_controller[self.average_key] = self.ext_controller[self.average_key]
196 self.ext_controller[self.avg_alpha_key] = self.ext_controller[self.avg_alpha_key]
197 self._register_set_prop(self, DYNAMIC_RANGE_KEY, dynamic_range)
198 self._register_set_prop(self, NUM_LINES_KEY, num_lines)
199 self._register_set_prop(self, Y_DIVS_KEY, 8)
200 self._register_set_prop(self, X_DIVS_KEY, 8) #approximate
201 self._register_set_prop(self, REF_LEVEL_KEY, ref_level)
202 self._register_set_prop(self, BASEBAND_FREQ_KEY, baseband_freq)
203 self._register_set_prop(self, COLOR_MODE_KEY, COLOR_MODES[0][1])
204 self._register_set_prop(self, RUNNING_KEY, True)
206 self.ext_controller.subscribe(msg_key, self.handle_msg)
207 self.ext_controller.subscribe(self.decimation_key, self.update_grid)
208 self.ext_controller.subscribe(self.sample_rate_key, self.update_grid)
209 self.ext_controller.subscribe(self.frame_rate_key, self.update_grid)
210 self.subscribe(BASEBAND_FREQ_KEY, self.update_grid)
211 self.subscribe(NUM_LINES_KEY, self.update_grid)
212 self.subscribe(Y_DIVS_KEY, self.update_grid)
213 self.subscribe(X_DIVS_KEY, self.update_grid)
217 def autoscale(self, *args):
219 Autoscale the waterfall plot to the last frame.
220 Set the dynamic range and reference level.
221 Does not affect the current data in the waterfall.
223 #get the peak level (max of the samples)
224 peak_level = numpy.max(self.samples)
225 #get the noise floor (averge the smallest samples)
226 noise_floor = numpy.average(numpy.sort(self.samples)[:len(self.samples)/4])
228 noise_floor -= abs(noise_floor)*.5
229 peak_level += abs(peak_level)*.1
230 #set the range and level
231 self.set_ref_level(peak_level)
232 self.set_dynamic_range(peak_level - noise_floor)
234 def handle_msg(self, msg):
236 Handle the message from the fft sink message queue.
237 If complex, reorder the fft samples so the negative bins come first.
238 If real, keep take only the positive bins.
239 Send the data to the plotter.
240 @param msg the fft array as a character array
242 if not self[RUNNING_KEY]: return
243 #convert to floating point numbers
244 self.samples = samples = numpy.fromstring(msg, numpy.float32)[:self.fft_size] #only take first frame
245 num_samps = len(samples)
247 if self.real: samples = samples[:num_samps/2]
248 else: samples = numpy.concatenate((samples[num_samps/2:], samples[:num_samps/2]))
250 self.plotter.set_samples(
252 minimum=self[REF_LEVEL_KEY] - self[DYNAMIC_RANGE_KEY],
253 maximum=self[REF_LEVEL_KEY],
256 self.plotter.update()
258 def update_grid(self, *args):
260 Update the plotter grid.
261 This update method is dependent on the variables below.
262 Determine the x and y axis grid parameters.
263 The x axis depends on sample rate, baseband freq, and x divs.
264 The y axis depends on y per div, y divs, and ref level.
267 sample_rate = self.ext_controller[self.sample_rate_key]
268 frame_rate = self.ext_controller[self.frame_rate_key]
269 baseband_freq = self[BASEBAND_FREQ_KEY]
270 num_lines = self[NUM_LINES_KEY]
271 y_divs = self[Y_DIVS_KEY]
272 x_divs = self[X_DIVS_KEY]
273 #determine best fitting x_per_div
274 if self.real: x_width = sample_rate/2.0
275 else: x_width = sample_rate/1.0
276 x_per_div = common.get_clean_num(x_width/x_divs)
277 coeff, exp, prefix = common.get_si_components(abs(baseband_freq) + abs(sample_rate/2.0))
280 self.plotter.set_x_grid(
282 baseband_freq + sample_rate/2.0,
287 self.plotter.set_x_grid(
288 baseband_freq - sample_rate/2.0,
289 baseband_freq + sample_rate/2.0,
294 self.plotter.set_x_label('Frequency', prefix+'Hz')
296 duration = float(num_lines)/frame_rate
297 y_per_div = common.get_clean_num(duration/y_divs)
298 self.plotter.set_y_grid(0, duration, y_per_div)
300 self.plotter.set_y_label('Time', 's')
302 self.plotter.update()