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 *
33 ##################################################
35 ##################################################
36 DEFAULT_FRAME_RATE = 30
37 DEFAULT_WIN_SIZE = (600, 300)
38 DEFAULT_V_SCALE = 1000
54 CHANNEL_COLOR_SPECS = (
60 AUTORANGE_UPDATE_RATE = 0.5 #sec
67 DEFAULT_MARKER_TYPE = None
69 ##################################################
70 # Scope window control panel
71 ##################################################
72 class control_panel(wx.Panel):
74 A control panel with wx widgits to control the plotter and scope block.
76 def __init__(self, parent):
78 Create a new control panel.
79 @param parent the wx parent window
82 wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
83 self.control_box = control_box = wx.BoxSizer(wx.VERTICAL)
85 control_box.AddStretchSpacer()
86 control_box.Add(common.LabelText(self, 'Trigger Options'), 0, wx.ALIGN_CENTER)
87 control_box.AddSpacer(2)
89 self.trigger_mode_chooser = common.DropDownController(self, 'Mode', TRIGGER_MODES, parent, TRIGGER_MODE_KEY)
90 control_box.Add(self.trigger_mode_chooser, 0, wx.EXPAND)
92 self.trigger_level_chooser = common.DropDownController(self, 'Level', TRIGGER_LEVELS, parent, TRIGGER_LEVEL_KEY)
93 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_level_chooser.Disable(x==0))
94 control_box.Add(self.trigger_level_chooser, 0, wx.EXPAND)
96 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
97 self.trigger_channel_chooser = common.DropDownController(self, 'Channel', choices, parent, TRIGGER_CHANNEL_KEY)
98 parent.subscribe(TRIGGER_MODE_KEY, lambda x: self.trigger_channel_chooser.Disable(x==0))
99 control_box.Add(self.trigger_channel_chooser, 0, wx.EXPAND)
102 control_box.AddStretchSpacer()
103 control_box.Add(common.LabelText(self, 'Axes Options'), 0, wx.ALIGN_CENTER)
104 control_box.AddSpacer(2)
105 ##################################################
107 ##################################################
108 self.scope_mode_box = wx.BoxSizer(wx.VERTICAL)
109 control_box.Add(self.scope_mode_box, 0, wx.EXPAND)
111 hbox = wx.BoxSizer(wx.HORIZONTAL)
112 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
113 hbox.Add(wx.StaticText(self, -1, ' Secs/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
114 x_buttons = common.IncrDecrButtons(self, self._on_incr_t_divs, self._on_decr_t_divs)
115 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
116 hbox.AddSpacer(SPACING)
118 hbox = wx.BoxSizer(wx.HORIZONTAL)
119 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
120 hbox.Add(wx.StaticText(self, -1, ' Units/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
121 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
122 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
123 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
124 hbox.AddSpacer(SPACING)
126 hbox = wx.BoxSizer(wx.HORIZONTAL)
127 self.scope_mode_box.Add(hbox, 0, wx.EXPAND)
128 hbox.Add(wx.StaticText(self, -1, ' Y Offset '), 1, wx.ALIGN_CENTER_VERTICAL)
129 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
130 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
131 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
132 hbox.AddSpacer(SPACING)
133 ##################################################
135 ##################################################
136 self.xy_mode_box = wx.BoxSizer(wx.VERTICAL)
137 control_box.Add(self.xy_mode_box, 0, wx.EXPAND)
141 hbox = wx.BoxSizer(wx.HORIZONTAL)
142 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
143 choices = [('Ch%d'%(i+1), i) for i in range(parent.num_inputs)]
144 self.channel_x_chooser = common.DropDownController(self, 'X Ch', choices, parent, SCOPE_X_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
145 hbox.Add(self.channel_x_chooser, 0, wx.EXPAND)
146 hbox.AddSpacer(CENTER_SPACING)
147 self.channel_y_chooser = common.DropDownController(self, 'Y Ch', choices, parent, SCOPE_Y_CHANNEL_KEY, (CHOOSER_WIDTH, -1))
148 hbox.Add(self.channel_y_chooser, 0, wx.EXPAND)
150 hbox = wx.BoxSizer(wx.HORIZONTAL)
151 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
152 hbox.Add(wx.StaticText(self, -1, ' X/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
153 x_buttons = common.IncrDecrButtons(self, self._on_incr_x_divs, self._on_decr_x_divs)
154 parent.subscribe(AUTORANGE_KEY, x_buttons.Disable)
155 hbox.Add(x_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
156 hbox.AddSpacer(CENTER_SPACING)
157 hbox.Add(wx.StaticText(self, -1, ' Y/Div '), 1, wx.ALIGN_CENTER_VERTICAL)
158 y_buttons = common.IncrDecrButtons(self, self._on_incr_y_divs, self._on_decr_y_divs)
159 parent.subscribe(AUTORANGE_KEY, y_buttons.Disable)
160 hbox.Add(y_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
162 hbox = wx.BoxSizer(wx.HORIZONTAL)
163 self.xy_mode_box.Add(hbox, 0, wx.EXPAND)
164 hbox.Add(wx.StaticText(self, -1, ' X Off '), 1, wx.ALIGN_CENTER_VERTICAL)
165 x_off_buttons = common.IncrDecrButtons(self, self._on_incr_x_off, self._on_decr_x_off)
166 parent.subscribe(AUTORANGE_KEY, x_off_buttons.Disable)
167 hbox.Add(x_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
168 hbox.AddSpacer(CENTER_SPACING)
169 hbox.Add(wx.StaticText(self, -1, ' Y Off '), 1, wx.ALIGN_CENTER_VERTICAL)
170 y_off_buttons = common.IncrDecrButtons(self, self._on_incr_y_off, self._on_decr_y_off)
171 parent.subscribe(AUTORANGE_KEY, y_off_buttons.Disable)
172 hbox.Add(y_off_buttons, 0, wx.ALIGN_CENTER_VERTICAL)
173 ##################################################
175 ##################################################
177 control_box.AddStretchSpacer()
178 control_box.Add(common.LabelText(self, 'Range Options'), 0, wx.ALIGN_CENTER)
180 self.ac_couple_check_box = common.CheckBoxController(self, 'AC Couple', parent, AC_COUPLE_KEY)
181 control_box.Add(self.ac_couple_check_box, 0, wx.ALIGN_LEFT)
183 self.autorange_check_box = common.CheckBoxController(self, 'Autorange', parent, AUTORANGE_KEY)
184 control_box.Add(self.autorange_check_box, 0, wx.ALIGN_LEFT)
186 control_box.AddStretchSpacer()
187 self.marker_chooser = common.DropDownController(self, 'Marker', MARKER_TYPES, parent, MARKER_KEY)
188 control_box.Add(self.marker_chooser, 0, wx.EXPAND)
190 control_box.AddStretchSpacer()
191 self.scope_xy_mode_button = common.ToggleButtonController(self, parent, SCOPE_XY_MODE_KEY, 'Scope Mode', 'X:Y Mode')
192 parent.subscribe(SCOPE_XY_MODE_KEY, self._on_scope_xy_mode)
193 control_box.Add(self.scope_xy_mode_button, 0, wx.EXPAND)
195 self.run_button = common.ToggleButtonController(self, parent, RUNNING_KEY, 'Stop', 'Run')
196 control_box.Add(self.run_button, 0, wx.EXPAND)
198 self.SetSizerAndFit(control_box)
200 ##################################################
202 ##################################################
203 def _on_scope_xy_mode(self, mode):
204 self.scope_mode_box.ShowItems(not mode)
205 self.xy_mode_box.ShowItems(mode)
206 self.control_box.Layout()
208 def _on_incr_t_divs(self, event):
209 self.parent.set_t_per_div(
210 common.get_clean_incr(self.parent[T_PER_DIV_KEY]))
211 def _on_decr_t_divs(self, event):
212 self.parent.set_t_per_div(
213 common.get_clean_decr(self.parent[T_PER_DIV_KEY]))
214 def _on_incr_x_divs(self, event):
215 self.parent.set_x_per_div(
216 common.get_clean_incr(self.parent[X_PER_DIV_KEY]))
217 def _on_decr_x_divs(self, event):
218 self.parent.set_x_per_div(
219 common.get_clean_decr(self.parent[X_PER_DIV_KEY]))
220 def _on_incr_y_divs(self, event):
221 self.parent.set_y_per_div(
222 common.get_clean_incr(self.parent[Y_PER_DIV_KEY]))
223 def _on_decr_y_divs(self, event):
224 self.parent.set_y_per_div(
225 common.get_clean_decr(self.parent[Y_PER_DIV_KEY]))
227 def _on_incr_t_off(self, event):
228 self.parent.set_t_off(
229 self.parent[T_OFF_KEY] + self.parent[T_PER_DIV_KEY])
230 def _on_decr_t_off(self, event):
231 self.parent.set_t_off(
232 self.parent[T_OFF_KEY] - self.parent[T_PER_DIV_KEY])
233 def _on_incr_x_off(self, event):
234 self.parent.set_x_off(
235 self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY])
236 def _on_decr_x_off(self, event):
237 self.parent.set_x_off(
238 self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY])
239 def _on_incr_y_off(self, event):
240 self.parent.set_y_off(
241 self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY])
242 def _on_decr_y_off(self, event):
243 self.parent.set_y_off(
244 self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY])
246 ##################################################
247 # Scope window with plotter and control panel
248 ##################################################
249 class scope_window(wx.Panel, pubsub.pubsub, common.prop_setter):
263 scope_trigger_level_key,
264 scope_trigger_mode_key,
265 scope_trigger_channel_key,
268 pubsub.pubsub.__init__(self)
270 assert num_inputs <= len(CHANNEL_COLOR_SPECS)
272 self.sampleses = None
273 self.ext_controller = controller
274 self.num_inputs = num_inputs
275 self.sample_rate_key = sample_rate_key
276 autorange = v_scale is None
277 self.autorange_ts = 0
278 if v_scale is None: v_scale = 1
279 self.frame_rate_ts = 0
280 self._init = False #HACK
282 self.scope_trigger_level_key = scope_trigger_level_key
283 self.scope_trigger_mode_key = scope_trigger_mode_key
284 self.scope_trigger_channel_key = scope_trigger_channel_key
286 wx.Panel.__init__(self, parent, -1, style=wx.SIMPLE_BORDER)
287 self.plotter = plotter.channel_plotter(self)
288 self.plotter.SetSize(wx.Size(*size))
289 self.plotter.set_title(title)
290 self.plotter.enable_legend(True)
291 self.plotter.enable_point_label(True)
292 #setup the box with plot and controls
293 self.control_panel = control_panel(self)
294 main_box = wx.BoxSizer(wx.HORIZONTAL)
295 main_box.Add(self.plotter, 1, wx.EXPAND)
296 main_box.Add(self.control_panel, 0, wx.EXPAND)
297 self.SetSizerAndFit(main_box)
299 self._register_set_prop(self, RUNNING_KEY, True)
300 self._register_set_prop(self, AC_COUPLE_KEY, ac_couple)
301 self._register_set_prop(self, SCOPE_XY_MODE_KEY, xy_mode)
302 self._register_set_prop(self, AUTORANGE_KEY, autorange)
303 self._register_set_prop(self, T_PER_DIV_KEY, t_scale)
304 self._register_set_prop(self, X_PER_DIV_KEY, v_scale)
305 self._register_set_prop(self, Y_PER_DIV_KEY, v_scale)
306 self._register_set_prop(self, T_OFF_KEY, 0)
307 self._register_set_prop(self, X_OFF_KEY, 0)
308 self._register_set_prop(self, Y_OFF_KEY, 0)
309 self._register_set_prop(self, T_DIVS_KEY, 8)
310 self._register_set_prop(self, X_DIVS_KEY, 8)
311 self._register_set_prop(self, Y_DIVS_KEY, 8)
312 self._register_set_prop(self, SCOPE_X_CHANNEL_KEY, 0)
313 self._register_set_prop(self, SCOPE_Y_CHANNEL_KEY, num_inputs-1)
314 self._register_set_prop(self, FRAME_RATE_KEY, frame_rate)
315 self._register_set_prop(self, TRIGGER_CHANNEL_KEY, 0)
316 self._register_set_prop(self, TRIGGER_MODE_KEY, 1)
317 self._register_set_prop(self, TRIGGER_LEVEL_KEY, None)
318 self._register_set_prop(self, MARKER_KEY, DEFAULT_MARKER_TYPE)
320 self.ext_controller.subscribe(msg_key, self.handle_msg)
322 T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
323 T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
324 T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
331 ): self.subscribe(key, self.update_grid)
332 #initial update, dont do this here, wait for handle_msg #HACK
335 def handle_msg(self, msg):
337 Handle the message from the scope sink message queue.
338 Plot the list of arrays of samples onto the grid.
339 Each samples array gets its own channel.
340 @param msg the time domain data as a character array
342 if not self[RUNNING_KEY]: return
344 if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
345 #convert to floating point numbers
346 samples = numpy.fromstring(msg, numpy.float32)
347 samps_per_ch = len(samples)/self.num_inputs
348 self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
349 if not self._init: #HACK
353 self.handle_samples()
354 self.frame_rate_ts = time.time()
356 def handle_samples(self):
358 Handle the cached samples from the scope input.
359 Perform ac coupling, triggering, and auto ranging.
361 if not self.sampleses: return
362 sampleses = self.sampleses
363 #trigger level (must do before ac coupling)
364 self.ext_controller[self.scope_trigger_channel_key] = self[TRIGGER_CHANNEL_KEY]
365 self.ext_controller[self.scope_trigger_mode_key] = self[TRIGGER_MODE_KEY]
366 trigger_level = self[TRIGGER_LEVEL_KEY]
367 if trigger_level is None: self.ext_controller[self.scope_trigger_level_key] = ''
369 samples = sampleses[self[TRIGGER_CHANNEL_KEY]]
370 self.ext_controller[self.scope_trigger_level_key] = \
371 trigger_level*(numpy.max(samples)-numpy.min(samples))/2 + numpy.average(samples)
373 if self[AC_COUPLE_KEY]:
374 sampleses = [samples - numpy.average(samples) for samples in sampleses]
375 if self[SCOPE_XY_MODE_KEY]:
376 x_samples = sampleses[self[SCOPE_X_CHANNEL_KEY]]
377 y_samples = sampleses[self[SCOPE_Y_CHANNEL_KEY]]
379 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
380 x_min, x_max = common.get_min_max(x_samples)
381 y_min, y_max = common.get_min_max(y_samples)
382 #adjust the x per div
383 x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
384 if x_per_div != self[X_PER_DIV_KEY]: self.set_x_per_div(x_per_div)
386 x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
387 if x_off != self[X_OFF_KEY]: self.set_x_off(x_off)
388 #adjust the y per div
389 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
390 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
392 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
393 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
394 self.autorange_ts = time.time()
396 self.plotter.set_waveform(
398 samples=(x_samples, y_samples),
399 color_spec=CHANNEL_COLOR_SPECS[0],
400 marker=self[MARKER_KEY],
402 #turn off each waveform
403 for i, samples in enumerate(sampleses):
404 self.plotter.set_waveform(
405 channel='Ch%d'%(i+1),
407 color_spec=CHANNEL_COLOR_SPECS[i],
411 if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
412 bounds = [common.get_min_max(samples) for samples in sampleses]
413 y_min = numpy.min(*[bound[0] for bound in bounds])
414 y_max = numpy.max(*[bound[1] for bound in bounds])
415 #adjust the y per div
416 y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
417 if y_per_div != self[Y_PER_DIV_KEY]: self.set_y_per_div(y_per_div)
419 y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
420 if y_off != self[Y_OFF_KEY]: self.set_y_off(y_off)
421 self.autorange_ts = time.time()
423 for i, samples in enumerate(sampleses):
424 #number of samples to scale to the screen
425 num_samps = int(self[T_PER_DIV_KEY]*self[T_DIVS_KEY]*self.ext_controller[self.sample_rate_key])
426 #handle num samps out of bounds
427 if num_samps > len(samples):
429 common.get_clean_decr(self[T_PER_DIV_KEY]))
432 common.get_clean_incr(self[T_PER_DIV_KEY]))
436 self.plotter.set_waveform(
437 channel='Ch%d'%(i+1),
438 samples=samples[:num_samps],
439 color_spec=CHANNEL_COLOR_SPECS[i],
440 marker=self[MARKER_KEY],
443 self.plotter.set_waveform(
446 color_spec=CHANNEL_COLOR_SPECS[0],
449 self.plotter.update()
451 def update_grid(self, *args):
453 Update the grid to reflect the current settings:
454 xy divisions, xy offset, xy mode setting
457 t_per_div = self[T_PER_DIV_KEY]
458 x_per_div = self[X_PER_DIV_KEY]
459 y_per_div = self[Y_PER_DIV_KEY]
460 t_off = self[T_OFF_KEY]
461 x_off = self[X_OFF_KEY]
462 y_off = self[Y_OFF_KEY]
463 t_divs = self[T_DIVS_KEY]
464 x_divs = self[X_DIVS_KEY]
465 y_divs = self[Y_DIVS_KEY]
466 if self[SCOPE_XY_MODE_KEY]:
468 self.plotter.set_x_label('Ch%d'%(self[SCOPE_X_CHANNEL_KEY]+1))
469 self.plotter.set_x_grid(
470 -1*x_per_div*x_divs/2.0 + x_off,
471 x_per_div*x_divs/2.0 + x_off,
475 self.plotter.set_y_label('Ch%d'%(self[SCOPE_Y_CHANNEL_KEY]+1))
476 self.plotter.set_y_grid(
477 -1*y_per_div*y_divs/2.0 + y_off,
478 y_per_div*y_divs/2.0 + y_off,
483 coeff, exp, prefix = common.get_si_components(t_per_div*t_divs + t_off)
484 self.plotter.set_x_label('Time', prefix+'s')
485 self.plotter.set_x_grid(
487 t_per_div*t_divs + t_off,
492 self.plotter.set_y_label('Counts')
493 self.plotter.set_y_grid(
494 -1*y_per_div*y_divs/2.0 + y_off,
495 y_per_div*y_divs/2.0 + y_off,
498 #redraw current sample
499 self.handle_samples()