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 ##################################################
37 DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30)
38 DEFAULT_WIN_SIZE = (600, 300)
39 DEFAULT_V_SCALE = 1000
55 CHANNEL_COLOR_SPECS = (
61 AUTORANGE_UPDATE_RATE = 0.5 #sec
68 DEFAULT_MARKER_TYPE = None
70 ##################################################
71 # Scope window control panel
72 ##################################################
73 class control_panel(wx.Panel):
75 A control panel with wx widgits to control the plotter and scope block.
77 def __init__(self, parent):
79 Create a new control panel.
80 @param parent the wx parent window
83 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
84 self.control_box = control_box = wx.BoxSizer(wx.VERTICAL)
86 control_box.AddStretchSpacer()
87 control_box.Add(common.LabelText(self, 'Trigger Options'), 0, wx.ALIGN_CENTER)
88 control_box.AddSpacer(2)
90 self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY)
91 control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND)
93 self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY)
94 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0))
95 control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND)
97 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
98 self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY)
99 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0))
100 control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND)
103 control_box.AddStretchSpacer()
104 control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER)
105 control_box.AddSpacer(2)
106 ##################################################
108 ##################################################
109 self.scope_mode_box = wx.BoxSizer(wx.VERTICAL)
110 control_box.Add(self.scope_mode_box, 0, wx.EXPAND)
112 hbox = wx.BoxSizer(wx.HORIZONTAL)
113 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
114 hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
115 x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs)
116 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
117 hbox.AddSpacer(SPACING)
119 hbox = wx.BoxSizer(wx.HORIZONTAL)
120 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
121 hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
122 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
123 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
124 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
125 hbox.AddSpacer(SPACING)
127 hbox = wx.BoxSizer(wx.HORIZONTAL)
128 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
129 hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL)
130 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
131 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
132 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
133 hbox.AddSpacer(SPACING)
134 ##################################################
136 ##################################################
137 self.xy_mode_box = wx.BoxSizer(wx.VERTICAL)
138 control_box.Add(self.xy_mode_box, 0, wx.EXPAND)
142 hbox = wx.BoxSizer(wx.HORIZONTAL)
143 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
144 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
145 self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
146 hbox.Add(self.channel_x_chooser, 0, wx.EXPAND)
147 hbox.AddSpacer(CENTER_SPACING)
148 self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
149 hbox.Add(self.channel_y_chooser, 0, wx.EXPAND)
151 hbox = wx.BoxSizer(wx.HORIZONTAL)
152 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
153 hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
154 x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs)
155 parent.subscribe(AUTORANGE_KEY, x_buttons.Disable)
156 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
157 hbox.AddSpacer(CENTER_SPACING)
158 hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
159 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
160 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
161 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
163 hbox = wx.BoxSizer(wx.HORIZONTAL)
164 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
165 hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL)
166 x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off)
167 parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable)
168 hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
169 hbox.AddSpacer(CENTER_SPACING)
170 hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL)
171 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
172 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
173 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
174 ##################################################
176 ##################################################
178 control_box.AddStretchSpacer()
179 control_box.Add(common.LabelText(self, 'Range Options'), 0, wx.ALIGN_CENTER)
181 self.ac_couple_check_box = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY)
182 control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT)
184 self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY)
185 control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
187 control_box.AddStretchSpacer()
188 self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY)
189 control_box.Add(self.marker_chooser, 0, wx.EXPAND)
191 control_box.AddStretchSpacer()
192 self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode')
193 parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode)
194 control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND)
196 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
197 control_box.Add(self.run_button, 0, wx.EXPAND)
199 self.SetSizerAndFit(control_box)
201 ##################################################
203 ##################################################
204 def _on_scope_xy_mode(self, mode):
205 self.scope_mode_box.ShowItems(not mode)
206 self.xy_mode_box.ShowItems(mode)
207 self.control_box.Layout()
209 def _on_incr_t_divs(self, event):
210 self.parent.set_t_per_div(
211 common.get_clean_incr(self.parent[T_PER_DIV_KEY]))
212 def _on_decr_t_divs(self, event):
213 self.parent.set_t_per_div(
214 common.get_clean_decr(self.parent[T_PER_DIV_KEY]))
215 def _on_incr_x_divs(self, event):
216 self.parent.set_x_per_div(
217 common.get_clean_incr(self.parent[X_PER_DIV_KEY]))
218 def _on_decr_x_divs(self, event):
219 self.parent.set_x_per_div(
220 common.get_clean_decr(self.parent[X_PER_DIV_KEY]))
221 def _on_incr_y_divs(self, event):
222 self.parent.set_y_per_div(
223 common.get_clean_incr(self.parent[Y_PER_DIV_KEY]))
224 def _on_decr_y_divs(self, event):
225 self.parent.set_y_per_div(
226 common.get_clean_decr(self.parent[Y_PER_DIV_KEY]))
228 def _on_incr_t_off(self, event):
229 self.parent.set_t_off(
230 self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY])
231 def _on_decr_t_off(self, event):
232 self.parent.set_t_off(
233 self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY])
234 def _on_incr_x_off(self, event):
235 self.parent.set_x_off(
236 self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY])
237 def _on_decr_x_off(self, event):
238 self.parent.set_x_off(
239 self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY])
240 def _on_incr_y_off(self, event):
241 self.parent.set_y_off(
242 self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY])
243 def _on_decr_y_off(self, event):
244 self.parent.set_y_off(
245 self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY])
247 ##################################################
248 # Scope window with plotter and control panel
249 ##################################################
250 class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter):
264 scope_trigger_level_key,
265 scope_trigger_mode_key,
266 scope_trigger_channel_key,
269 pubsub.pubsub.__init__(self)
271 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
273 self.sampleses = None
274 self.ext_controller = controller
275 self.num_inputs = num_inputs
276 self.sample_rate_key = sample_rate_key
277 autorange = v_scale is None
278 self.autorange_ts = 0
279 if v_scale is None: v_scale = 1
280 self.frame_rate_ts = 0
281 self._init = False #HACK
283 self.scope_trigger_level_key = scope_trigger_level_key
284 self.scope_trigger_mode_key = scope_trigger_mode_key
285 self.scope_trigger_channel_key = scope_trigger_channel_key
287 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
288 self.plotter = plotter.channel_plotter(self)
289 self.plotter.SetSize(wx.Size(*size))
290 self.plotter.set_title(title)
291 self.plotter.enable_legend(True)
292 self.plotter.enable_point_label(True)
293 #setup the box with plot and controls
294 self.control_panel = control_panel(self)
295 main_box = wx.BoxSizer(wx.HORIZONTAL)
296 main_box.Add(self.plotter, 1, wx.EXPAND)
297 main_box.Add(self.control_panel, 0, wx.EXPAND)
298 self.SetSizerAndFit(main_box)
300 self._register_set_prop(self, RUNNING_KEY, True)
301 self._register_set_prop(self, AC_COUPLE_KEY, ac_couple)
302 self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode)
303 self._register_set_prop(self, AUTORANGE_KEY, autorange)
304 self._register_set_prop(self, T_PER_DIV_KEY, t_scale)
305 self._register_set_prop(self, X_PER_DIV_KEY, v_scale)
306 self._register_set_prop(self, Y_PER_DIV_KEY, v_scale)
307 self._register_set_prop(self, T_OFF_KEY, 0)
308 self._register_set_prop(self, X_OFF_KEY, 0)
309 self._register_set_prop(self, Y_OFF_KEY, 0)
310 self._register_set_prop(self, T_DIVS_KEY, 8)
311 self._register_set_prop(self, X_DIVS_KEY, 8)
312 self._register_set_prop(self, Y_DIVS_KEY, 8)
313 self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0)
314 self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1)
315 self._register_set_prop(self, FRAME_RATE_KEY, frame_rate)
316 self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0)
317 self._register_set_prop(self, TRIGGER_MODE_KEY, 1)
318 self._register_set_prop(self, TRIGGER_LEVEL_KEY, None)
319 self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE)
321 self.ext_controller.subscribe(msg_key, self.handle_msg)
323 T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
324 T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
325 T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
332 ): self.subscribe(key, self.update_grid)
333 #initial update, dont do this here, wait for handle_msg #HACK
336 def handle_msg(self, msg):
338 Handle the message from the scope sink message queue.
339 Plot the list of arrays of samples onto the grid.
340 Each samples array gets its own channel.
341 @param msg the time domain data as a character array
343 if not self[RUNNING_KEY]: return
345 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
346 #convert to floating point numbers
347 samples = numpy.fromstring(msg, numpy.float32)
348 samps_per_ch = len(samples)/self.num_inputs
349 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
350 if not self._init: #HACK
354 self.handle_samples()
355 self.frame_rate_ts = time.time()
357 def handle_samples(self):
359 Handle the cached samples from the scope input.
360 Perform ac coupling, triggering, and auto ranging.
362 if not self.sampleses: return
363 sampleses = self.sampleses
364 #trigger level (must do before ac coupling)
365 self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY]
366 self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY]
367 trigger_level = self[TRIGGER_LEVEL_KEY]
368 if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = ''
370 samples = sampleses[self[TRIGGER_CHANNEL_KEY]]
371 self.ext_controller[self.scope_trigger_level_key] = \
372 trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples)
374 if self[AC_COUPLE_KEY]:
375 sampleses = [samples - numpy.average(samples) for samples in sampleses]
376 if self[SCOPE_XY_MODE_KEY]:
377 x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]]
378 y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]]
380 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
381 x_min, x_max = common.get_min_max(x_samples)
382 y_min, y_max = common.get_min_max(y_samples)
383 #adjust the x per div
384 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
385 if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div)
387 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
388 if x_off != self[X_OFF_KEY]: self.set_x_off(x_off)
389 #adjust the y per div
390 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
391 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
393 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
394 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
395 self.autorange_ts = time.time()
397 self.plotter.set_waveform(
399 samples=(x_samples, y_samples),
400 color_spec=CHANNEL_COLOR_SPECS[0],
401 marker=self[MARKER_KEY],
403 #turn off each waveform
404 for i, samples in enumerate(sampleses):
405 self.plotter.set_waveform(
406 channel='Ch%d'%(i+1),
408 color_spec=CHANNEL_COLOR_SPECS[i],
412 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
413 bounds = [common.get_min_max(samples) for samples in sampleses]
414 y_min = numpy.min(*[bound[0] for bound in bounds])
415 y_max = numpy.max(*[bound[1] for bound in bounds])
416 #adjust the y per div
417 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
418 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
420 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
421 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
422 self.autorange_ts = time.time()
424 for i, samples in enumerate(sampleses):
425 #number of samples to scale to the screen
426 num_samps = int(self[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key])
427 #handle num samps out of bounds
428 if num_samps > len(samples):
430 common.get_clean_decr(self[T_PER_DIV_KEY]))
433 common.get_clean_incr(self[T_PER_DIV_KEY]))
437 self.plotter.set_waveform(
438 channel='Ch%d'%(i+1),
439 samples=samples[:num_samps],
440 color_spec=CHANNEL_COLOR_SPECS[i],
441 marker=self[MARKER_KEY],
444 self.plotter.set_waveform(
447 color_spec=CHANNEL_COLOR_SPECS[0],
450 self.plotter.update()
452 def update_grid(self, *args):
454 Update the grid to reflect the current settings:
455 xy divisions, xy offset, xy mode setting
458 t_per_div = self[T_PER_DIV_KEY]
459 x_per_div = self[X_PER_DIV_KEY]
460 y_per_div = self[Y_PER_DIV_KEY]
461 t_off = self[T_OFF_KEY]
462 x_off = self[X_OFF_KEY]
463 y_off = self[Y_OFF_KEY]
464 t_divs = self[T_DIVS_KEY]
465 x_divs = self[X_DIVS_KEY]
466 y_divs = self[Y_DIVS_KEY]
467 if self[SCOPE_XY_MODE_KEY]:
469 self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1))
470 self.plotter.set_x_grid(
471 -1*x_per_div*x_divs/2.0 + x_off,
472 x_per_div*x_divs/2.0 + x_off,
476 self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1))
477 self.plotter.set_y_grid(
478 -1*y_per_div*y_divs/2.0 + y_off,
479 y_per_div*y_divs/2.0 + y_off,
484 coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off)
485 self.plotter.set_x_label('Time', prefix+'s')
486 self.plotter.set_x_grid(
488 t_per_div*t_divs + t_off,
493 self.plotter.set_y_label('Counts')
494 self.plotter.set_y_grid(
495 -1*y_per_div*y_divs/2.0 + y_off,
496 y_per_div*y_divs/2.0 + y_off,
499 #redraw current sample
500 self.handle_samples()