~ubuntu-branches/ubuntu/trusty/gnuradio/trusty

« back to all changes in this revision

Viewing changes to gr-wxgui/src/python/scope_window.py

  • Committer: Bazaar Package Importer
  • Author(s): Kamal Mostafa
  • Date: 2010-03-13 07:46:01 UTC
  • mfrom: (2.1.2 sid)
  • Revision ID: james.westby@ubuntu.com-20100313074601-zjsa893a87bozyh7
Tags: 3.2.2.dfsg-1ubuntu1
* Fix build for Ubuntu lucid (LP: #260406)
  - add binary package dep for libusrp0, libusrp2-0: adduser
  - debian/rules clean: remove pre-built Qt moc files

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright 2008 Free Software Foundation, Inc.
 
3
#
 
4
# This file is part of GNU Radio
 
5
#
 
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)
 
9
# any later version.
 
10
#
 
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.
 
15
#
 
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.
 
20
#
 
21
 
 
22
##################################################
 
23
# Imports
 
24
##################################################
 
25
import plotter
 
26
import common
 
27
import wx
 
28
import numpy
 
29
import time
 
30
import pubsub
 
31
from constants import *
 
32
from gnuradio import gr #for gr.prefs, trigger modes
 
33
import forms
 
34
 
 
35
##################################################
 
36
# Constants
 
37
##################################################
 
38
DEFAULT_FRAME_RATE = gr.prefs().get_long('wxgui', 'scope_rate', 30)
 
39
DEFAULT_WIN_SIZE = (600, 300)
 
40
COUPLING_MODES = (
 
41
        ('DC', False),
 
42
        ('AC', True),
 
43
)
 
44
TRIGGER_MODES = (
 
45
        ('Freerun', gr.gr_TRIG_MODE_FREE),
 
46
        ('Auto', gr.gr_TRIG_MODE_AUTO),
 
47
        ('Normal', gr.gr_TRIG_MODE_NORM),
 
48
)
 
49
TRIGGER_SLOPES = (
 
50
        ('Pos +', gr.gr_TRIG_SLOPE_POS),
 
51
        ('Neg -', gr.gr_TRIG_SLOPE_NEG),
 
52
)
 
53
CHANNEL_COLOR_SPECS = (
 
54
        (0.3, 0.3, 1.0),
 
55
        (0.0, 0.8, 0.0),
 
56
        (1.0, 0.0, 0.0),
 
57
        (0.8, 0.0, 0.8),
 
58
)
 
59
TRIGGER_COLOR_SPEC = (1.0, 0.4, 0.0)
 
60
AUTORANGE_UPDATE_RATE = 0.5 #sec
 
61
MARKER_TYPES = (
 
62
        ('Line Link', None),
 
63
        ('Dot Large', 3.0),
 
64
        ('Dot Med', 2.0),
 
65
        ('Dot Small', 1.0),
 
66
        ('None', 0.0),
 
67
)
 
68
DEFAULT_MARKER_TYPE = None
 
69
 
 
70
##################################################
 
71
# Scope window control panel
 
72
##################################################
 
73
class control_panel(wx.Panel):
 
74
        """
 
75
        A control panel with wx widgits to control the plotter and scope block.
 
76
        """
 
77
        def __init__(self, parent):
 
78
                """
 
79
                Create a new control panel.
 
80
                @param parent the wx parent window
 
81
                """
 
82
                WIDTH = 90
 
83
                self.parent = parent
 
84
                wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
 
85
                control_box = wx.BoxSizer(wx.VERTICAL)
 
86
                ##################################################
 
87
                # Axes Options
 
88
                ##################################################
 
89
                control_box.AddStretchSpacer()
 
90
                axes_options_box = forms.static_box_sizer(
 
91
                        parent=self, sizer=control_box, label='Axes Options',
 
92
                        bold=True, orient=wx.VERTICAL,
 
93
                )
 
94
                ##################################################
 
95
                # Scope Mode Box
 
96
                ##################################################
 
97
                scope_mode_box = wx.BoxSizer(wx.VERTICAL)
 
98
                axes_options_box.Add(scope_mode_box, 0, wx.EXPAND)
 
99
                #x axis divs
 
100
                forms.incr_decr_buttons(
 
101
                        parent=self, sizer=scope_mode_box, label='Secs/Div',
 
102
                        on_incr=self._on_incr_t_divs, on_decr=self._on_decr_t_divs,
 
103
                )
 
104
                #y axis divs
 
105
                y_buttons_scope = forms.incr_decr_buttons(
 
106
                        parent=self, sizer=scope_mode_box, label='Counts/Div',
 
107
                        on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs,
 
108
                )
 
109
                #y axis ref lvl
 
110
                y_off_buttons_scope = forms.incr_decr_buttons(
 
111
                        parent=self, sizer=scope_mode_box, label='Y Offset',
 
112
                        on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off,
 
113
                )
 
114
                #t axis ref lvl
 
115
                scope_mode_box.AddSpacer(5)
 
116
                forms.slider(
 
117
                        parent=self, sizer=scope_mode_box,
 
118
                        ps=parent, key=T_FRAC_OFF_KEY, label='T Offset',
 
119
                        minimum=0, maximum=1, num_steps=1000,
 
120
                )
 
121
                scope_mode_box.AddSpacer(5)
 
122
                ##################################################
 
123
                # XY Mode Box
 
124
                ##################################################
 
125
                xy_mode_box = wx.BoxSizer(wx.VERTICAL)
 
126
                axes_options_box.Add(xy_mode_box, 0, wx.EXPAND)
 
127
                #x div controls
 
128
                x_buttons = forms.incr_decr_buttons(
 
129
                        parent=self, sizer=xy_mode_box, label='X/Div',
 
130
                        on_incr=self._on_incr_x_divs, on_decr=self._on_decr_x_divs,
 
131
                )
 
132
                #y div controls
 
133
                y_buttons = forms.incr_decr_buttons(
 
134
                        parent=self, sizer=xy_mode_box, label='Y/Div',
 
135
                        on_incr=self._on_incr_y_divs, on_decr=self._on_decr_y_divs,
 
136
                )
 
137
                #x offset controls
 
138
                x_off_buttons = forms.incr_decr_buttons(
 
139
                        parent=self, sizer=xy_mode_box, label='X Off',
 
140
                        on_incr=self._on_incr_x_off, on_decr=self._on_decr_x_off,
 
141
                )
 
142
                #y offset controls
 
143
                y_off_buttons = forms.incr_decr_buttons(
 
144
                        parent=self, sizer=xy_mode_box, label='Y Off',
 
145
                        on_incr=self._on_incr_y_off, on_decr=self._on_decr_y_off,
 
146
                )
 
147
                for widget in (y_buttons_scope, y_off_buttons_scope, x_buttons, y_buttons, x_off_buttons, y_off_buttons):
 
148
                        parent.subscribe(AUTORANGE_KEY, widget.Disable)
 
149
                        widget.Disable(parent[AUTORANGE_KEY])
 
150
                xy_mode_box.ShowItems(False)
 
151
                #autorange check box
 
152
                forms.check_box(
 
153
                        parent=self, sizer=axes_options_box, label='Autorange',
 
154
                        ps=parent, key=AUTORANGE_KEY,
 
155
                )
 
156
                ##################################################
 
157
                # Channel Options
 
158
                ##################################################
 
159
                TRIGGER_PAGE_INDEX = parent.num_inputs
 
160
                XY_PAGE_INDEX = parent.num_inputs+1
 
161
                control_box.AddStretchSpacer()
 
162
                chan_options_box = forms.static_box_sizer(
 
163
                        parent=self, sizer=control_box, label='Channel Options',
 
164
                        bold=True, orient=wx.VERTICAL,
 
165
                )
 
166
                options_notebook = wx.Notebook(self)
 
167
                options_notebook_args = list()
 
168
                CHANNELS = [('Ch %d'%(i+1), i) for i in range(parent.num_inputs)]
 
169
                ##################################################
 
170
                # Channel Menu Boxes
 
171
                ##################################################
 
172
                for i in range(parent.num_inputs):
 
173
                        channel_menu_panel = wx.Panel(options_notebook)
 
174
                        options_notebook_args.append((channel_menu_panel, i, 'Ch%d'%(i+1)))
 
175
                        channel_menu_box = wx.BoxSizer(wx.VERTICAL)
 
176
                        channel_menu_panel.SetSizer(channel_menu_box)
 
177
                        #ac couple check box
 
178
                        channel_menu_box.AddStretchSpacer()
 
179
                        forms.drop_down(
 
180
                                parent=channel_menu_panel, sizer=channel_menu_box,
 
181
                                ps=parent, key=common.index_key(AC_COUPLE_KEY, i),
 
182
                                choices=map(lambda x: x[1], COUPLING_MODES),
 
183
                                labels=map(lambda x: x[0], COUPLING_MODES),
 
184
                                label='Coupling', width=WIDTH,
 
185
                        )
 
186
                        #marker
 
187
                        channel_menu_box.AddStretchSpacer()
 
188
                        forms.drop_down(
 
189
                                parent=channel_menu_panel, sizer=channel_menu_box,
 
190
                                ps=parent, key=common.index_key(MARKER_KEY, i),
 
191
                                choices=map(lambda x: x[1], MARKER_TYPES),
 
192
                                labels=map(lambda x: x[0], MARKER_TYPES),
 
193
                                label='Marker', width=WIDTH,
 
194
                        )
 
195
                        channel_menu_box.AddStretchSpacer()
 
196
                ##################################################
 
197
                # Trigger Menu Box
 
198
                ##################################################
 
199
                trigger_menu_panel = wx.Panel(options_notebook)
 
200
                options_notebook_args.append((trigger_menu_panel, TRIGGER_PAGE_INDEX, 'Trig'))
 
201
                trigger_menu_box = wx.BoxSizer(wx.VERTICAL)
 
202
                trigger_menu_panel.SetSizer(trigger_menu_box)
 
203
                #trigger mode
 
204
                forms.drop_down(
 
205
                        parent=trigger_menu_panel, sizer=trigger_menu_box,
 
206
                        ps=parent, key=TRIGGER_MODE_KEY,
 
207
                        choices=map(lambda x: x[1], TRIGGER_MODES),
 
208
                        labels=map(lambda x: x[0], TRIGGER_MODES),
 
209
                        label='Mode', width=WIDTH,
 
210
                )
 
211
                #trigger slope
 
212
                trigger_slope_chooser = forms.drop_down(
 
213
                        parent=trigger_menu_panel, sizer=trigger_menu_box,
 
214
                        ps=parent, key=TRIGGER_SLOPE_KEY,
 
215
                        choices=map(lambda x: x[1], TRIGGER_SLOPES),
 
216
                        labels=map(lambda x: x[0], TRIGGER_SLOPES),
 
217
                        label='Slope', width=WIDTH,
 
218
                )
 
219
                #trigger channel
 
220
                trigger_channel_chooser = forms.drop_down(
 
221
                        parent=trigger_menu_panel, sizer=trigger_menu_box,
 
222
                        ps=parent, key=TRIGGER_CHANNEL_KEY,
 
223
                        choices=map(lambda x: x[1], CHANNELS),
 
224
                        labels=map(lambda x: x[0], CHANNELS),
 
225
                        label='Channel', width=WIDTH,
 
226
                )
 
227
                #trigger level
 
228
                hbox = wx.BoxSizer(wx.HORIZONTAL)
 
229
                trigger_menu_box.Add(hbox, 0, wx.EXPAND)
 
230
                hbox.Add(wx.StaticText(trigger_menu_panel, label='Level:'), 1, wx.ALIGN_CENTER_VERTICAL)
 
231
                trigger_level_button = forms.single_button(
 
232
                        parent=trigger_menu_panel, sizer=hbox, label='50%',
 
233
                        callback=parent.set_auto_trigger_level, style=wx.BU_EXACTFIT,
 
234
                )
 
235
                hbox.AddSpacer(WIDTH-60)
 
236
                trigger_level_buttons = forms.incr_decr_buttons(
 
237
                        parent=trigger_menu_panel, sizer=hbox,
 
238
                        on_incr=self._on_incr_trigger_level, on_decr=self._on_decr_trigger_level,
 
239
                )
 
240
                def disable_all(trigger_mode):
 
241
                        for widget in (trigger_slope_chooser, trigger_channel_chooser, trigger_level_buttons, trigger_level_button):
 
242
                                widget.Disable(trigger_mode == gr.gr_TRIG_MODE_FREE)
 
243
                parent.subscribe(TRIGGER_MODE_KEY, disable_all)
 
244
                disable_all(parent[TRIGGER_MODE_KEY])
 
245
                ##################################################
 
246
                # XY Menu Box
 
247
                ##################################################
 
248
                if parent.num_inputs > 1:
 
249
                        xy_menu_panel = wx.Panel(options_notebook)
 
250
                        options_notebook_args.append((xy_menu_panel, XY_PAGE_INDEX, 'XY'))
 
251
                        xy_menu_box = wx.BoxSizer(wx.VERTICAL)
 
252
                        xy_menu_panel.SetSizer(xy_menu_box)
 
253
                        #x and y channel choosers
 
254
                        xy_menu_box.AddStretchSpacer()
 
255
                        forms.drop_down(
 
256
                                parent=xy_menu_panel, sizer=xy_menu_box,
 
257
                                ps=parent, key=X_CHANNEL_KEY,
 
258
                                choices=map(lambda x: x[1], CHANNELS),
 
259
                                labels=map(lambda x: x[0], CHANNELS),
 
260
                                label='Channel X', width=WIDTH,
 
261
                        )
 
262
                        xy_menu_box.AddStretchSpacer()
 
263
                        forms.drop_down(
 
264
                                parent=xy_menu_panel, sizer=xy_menu_box,
 
265
                                ps=parent, key=Y_CHANNEL_KEY,
 
266
                                choices=map(lambda x: x[1], CHANNELS),
 
267
                                labels=map(lambda x: x[0], CHANNELS),
 
268
                                label='Channel Y', width=WIDTH,
 
269
                        )
 
270
                        #marker
 
271
                        xy_menu_box.AddStretchSpacer()
 
272
                        forms.drop_down(
 
273
                                parent=xy_menu_panel, sizer=xy_menu_box,
 
274
                                ps=parent, key=XY_MARKER_KEY,
 
275
                                choices=map(lambda x: x[1], MARKER_TYPES),
 
276
                                labels=map(lambda x: x[0], MARKER_TYPES),
 
277
                                label='Marker', width=WIDTH,
 
278
                        )
 
279
                        xy_menu_box.AddStretchSpacer()
 
280
                ##################################################
 
281
                # Setup Options Notebook
 
282
                ##################################################
 
283
                forms.notebook(
 
284
                        parent=self, sizer=chan_options_box,
 
285
                        notebook=options_notebook,
 
286
                        ps=parent, key=CHANNEL_OPTIONS_KEY,
 
287
                        pages=map(lambda x: x[0], options_notebook_args),
 
288
                        choices=map(lambda x: x[1], options_notebook_args),
 
289
                        labels=map(lambda x: x[2], options_notebook_args),
 
290
                )
 
291
                #gui handling for channel options changing
 
292
                def options_notebook_changed(chan_opt):
 
293
                        try:
 
294
                                parent[TRIGGER_SHOW_KEY] = chan_opt == TRIGGER_PAGE_INDEX
 
295
                                parent[XY_MODE_KEY] = chan_opt == XY_PAGE_INDEX
 
296
                        except wx.PyDeadObjectError: pass
 
297
                parent.subscribe(CHANNEL_OPTIONS_KEY, options_notebook_changed)
 
298
                #gui handling for xy mode changing
 
299
                def xy_mode_changed(mode):
 
300
                        #ensure xy tab is selected
 
301
                        if mode and parent[CHANNEL_OPTIONS_KEY] != XY_PAGE_INDEX:
 
302
                                parent[CHANNEL_OPTIONS_KEY] = XY_PAGE_INDEX
 
303
                        #ensure xy tab is not selected
 
304
                        elif not mode and parent[CHANNEL_OPTIONS_KEY] == XY_PAGE_INDEX:
 
305
                                parent[CHANNEL_OPTIONS_KEY] = 0
 
306
                        #show/hide control buttons
 
307
                        scope_mode_box.ShowItems(not mode)
 
308
                        xy_mode_box.ShowItems(mode)
 
309
                        control_box.Layout()
 
310
                parent.subscribe(XY_MODE_KEY, xy_mode_changed)
 
311
                xy_mode_changed(parent[XY_MODE_KEY])
 
312
                ##################################################
 
313
                # Run/Stop Button
 
314
                ##################################################
 
315
                #run/stop
 
316
                control_box.AddStretchSpacer()
 
317
                forms.toggle_button(
 
318
                        sizer=control_box, parent=self,
 
319
                        true_label='Stop', false_label='Run',
 
320
                        ps=parent, key=RUNNING_KEY,
 
321
                )
 
322
                #set sizer
 
323
                self.SetSizerAndFit(control_box)
 
324
                #mouse wheel event
 
325
                def on_mouse_wheel(event):
 
326
                        if not parent[XY_MODE_KEY]:
 
327
                                if event.GetWheelRotation() < 0: self._on_incr_t_divs(event)
 
328
                                else: self._on_decr_t_divs(event)
 
329
                parent.plotter.Bind(wx.EVT_MOUSEWHEEL, on_mouse_wheel)
 
330
 
 
331
        ##################################################
 
332
        # Event handlers
 
333
        ##################################################
 
334
        #trigger level
 
335
        def _on_incr_trigger_level(self, event):
 
336
                self.parent[TRIGGER_LEVEL_KEY] += self.parent[Y_PER_DIV_KEY]/3.
 
337
        def _on_decr_trigger_level(self, event):
 
338
                self.parent[TRIGGER_LEVEL_KEY] -= self.parent[Y_PER_DIV_KEY]/3.
 
339
        #incr/decr divs
 
340
        def _on_incr_t_divs(self, event):
 
341
                self.parent[T_PER_DIV_KEY] = common.get_clean_incr(self.parent[T_PER_DIV_KEY])
 
342
        def _on_decr_t_divs(self, event):
 
343
                self.parent[T_PER_DIV_KEY] = common.get_clean_decr(self.parent[T_PER_DIV_KEY])
 
344
        def _on_incr_x_divs(self, event):
 
345
                self.parent[X_PER_DIV_KEY] = common.get_clean_incr(self.parent[X_PER_DIV_KEY])
 
346
        def _on_decr_x_divs(self, event):
 
347
                self.parent[X_PER_DIV_KEY] = common.get_clean_decr(self.parent[X_PER_DIV_KEY])
 
348
        def _on_incr_y_divs(self, event):
 
349
                self.parent[Y_PER_DIV_KEY] = common.get_clean_incr(self.parent[Y_PER_DIV_KEY])
 
350
        def _on_decr_y_divs(self, event):
 
351
                self.parent[Y_PER_DIV_KEY] = common.get_clean_decr(self.parent[Y_PER_DIV_KEY])
 
352
        #incr/decr offset
 
353
        def _on_incr_x_off(self, event):
 
354
                self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] + self.parent[X_PER_DIV_KEY]
 
355
        def _on_decr_x_off(self, event):
 
356
                self.parent[X_OFF_KEY] = self.parent[X_OFF_KEY] - self.parent[X_PER_DIV_KEY]
 
357
        def _on_incr_y_off(self, event):
 
358
                self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] + self.parent[Y_PER_DIV_KEY]
 
359
        def _on_decr_y_off(self, event):
 
360
                self.parent[Y_OFF_KEY] = self.parent[Y_OFF_KEY] - self.parent[Y_PER_DIV_KEY]
 
361
 
 
362
##################################################
 
363
# Scope window with plotter and control panel
 
364
##################################################
 
365
class scope_window(wx.Panel, pubsub.pubsub):
 
366
        def __init__(
 
367
                self,
 
368
                parent,
 
369
                controller,
 
370
                size,
 
371
                title,
 
372
                frame_rate,
 
373
                num_inputs,
 
374
                sample_rate_key,
 
375
                t_scale,
 
376
                v_scale,
 
377
                xy_mode,
 
378
                ac_couple_key,
 
379
                trigger_level_key,
 
380
                trigger_mode_key,
 
381
                trigger_slope_key,
 
382
                trigger_channel_key,
 
383
                decimation_key,
 
384
                msg_key,
 
385
        ):
 
386
                pubsub.pubsub.__init__(self)
 
387
                #check num inputs
 
388
                assert num_inputs <= len(CHANNEL_COLOR_SPECS)
 
389
                #setup
 
390
                self.sampleses = None
 
391
                self.num_inputs = num_inputs
 
392
                autorange = not v_scale
 
393
                self.autorange_ts = 0
 
394
                v_scale = v_scale or 1
 
395
                self.frame_rate_ts = 0
 
396
                #proxy the keys
 
397
                self.proxy(MSG_KEY, controller, msg_key)
 
398
                self.proxy(SAMPLE_RATE_KEY, controller, sample_rate_key)
 
399
                self.proxy(TRIGGER_LEVEL_KEY, controller, trigger_level_key)
 
400
                self.proxy(TRIGGER_MODE_KEY, controller, trigger_mode_key)
 
401
                self.proxy(TRIGGER_SLOPE_KEY, controller, trigger_slope_key)
 
402
                self.proxy(TRIGGER_CHANNEL_KEY, controller, trigger_channel_key)
 
403
                self.proxy(DECIMATION_KEY, controller, decimation_key)
 
404
                #initialize values
 
405
                self[RUNNING_KEY] = True
 
406
                self[XY_MARKER_KEY] = 2.0
 
407
                self[CHANNEL_OPTIONS_KEY] = 0
 
408
                self[XY_MODE_KEY] = xy_mode
 
409
                self[X_CHANNEL_KEY] = 0
 
410
                self[Y_CHANNEL_KEY] = self.num_inputs-1
 
411
                self[AUTORANGE_KEY] = autorange
 
412
                self[T_PER_DIV_KEY] = t_scale
 
413
                self[X_PER_DIV_KEY] = v_scale
 
414
                self[Y_PER_DIV_KEY] = v_scale
 
415
                self[T_OFF_KEY] = 0
 
416
                self[X_OFF_KEY] = 0
 
417
                self[Y_OFF_KEY] = 0
 
418
                self[T_DIVS_KEY] = 8
 
419
                self[X_DIVS_KEY] = 8
 
420
                self[Y_DIVS_KEY] = 8
 
421
                self[FRAME_RATE_KEY] = frame_rate
 
422
                self[TRIGGER_LEVEL_KEY] = 0
 
423
                self[TRIGGER_CHANNEL_KEY] = 0
 
424
                self[TRIGGER_MODE_KEY] = gr.gr_TRIG_MODE_AUTO
 
425
                self[TRIGGER_SLOPE_KEY] = gr.gr_TRIG_SLOPE_POS
 
426
                self[T_FRAC_OFF_KEY] = 0.5
 
427
                for i in range(num_inputs):
 
428
                        self.proxy(common.index_key(AC_COUPLE_KEY, i), controller, common.index_key(ac_couple_key, i))
 
429
                #init panel and plot
 
430
                wx.Panel.__init__(self, parent, style=wx.SIMPLE_BORDER)
 
431
                self.plotter = plotter.channel_plotter(self)
 
432
                self.plotter.SetSize(wx.Size(*size))
 
433
                self.plotter.set_title(title)
 
434
                self.plotter.enable_legend(True)
 
435
                self.plotter.enable_point_label(True)
 
436
                self.plotter.enable_grid_lines(True)
 
437
                #setup the box with plot and controls
 
438
                self.control_panel = control_panel(self)
 
439
                main_box = wx.BoxSizer(wx.HORIZONTAL)
 
440
                main_box.Add(self.plotter, 1, wx.EXPAND)
 
441
                main_box.Add(self.control_panel, 0, wx.EXPAND)
 
442
                self.SetSizerAndFit(main_box)
 
443
                #register events for message
 
444
                self.subscribe(MSG_KEY, self.handle_msg)
 
445
                #register events for grid
 
446
                for key in [common.index_key(MARKER_KEY, i) for i in range(self.num_inputs)] + [
 
447
                        TRIGGER_LEVEL_KEY, TRIGGER_MODE_KEY,
 
448
                        T_PER_DIV_KEY, X_PER_DIV_KEY, Y_PER_DIV_KEY,
 
449
                        T_OFF_KEY, X_OFF_KEY, Y_OFF_KEY,
 
450
                        T_DIVS_KEY, X_DIVS_KEY, Y_DIVS_KEY,
 
451
                        XY_MODE_KEY, AUTORANGE_KEY, T_FRAC_OFF_KEY,
 
452
                        TRIGGER_SHOW_KEY, XY_MARKER_KEY, X_CHANNEL_KEY, Y_CHANNEL_KEY,
 
453
                ]: self.subscribe(key, self.update_grid)
 
454
                #initial update
 
455
                self.update_grid()
 
456
 
 
457
        def handle_msg(self, msg):
 
458
                """
 
459
                Handle the message from the scope sink message queue.
 
460
                Plot the list of arrays of samples onto the grid.
 
461
                Each samples array gets its own channel.
 
462
                @param msg the time domain data as a character array
 
463
                """
 
464
                if not self[RUNNING_KEY]: return
 
465
                #check time elapsed
 
466
                if time.time() - self.frame_rate_ts < 1.0/self[FRAME_RATE_KEY]: return
 
467
                #convert to floating point numbers
 
468
                samples = numpy.fromstring(msg, numpy.float32)
 
469
                #extract the trigger offset
 
470
                self.trigger_offset = samples[-1]
 
471
                samples = samples[:-1]
 
472
                samps_per_ch = len(samples)/self.num_inputs
 
473
                self.sampleses = [samples[samps_per_ch*i:samps_per_ch*(i+1)] for i in range(self.num_inputs)]
 
474
                #handle samples
 
475
                self.handle_samples()
 
476
                self.frame_rate_ts = time.time()
 
477
 
 
478
        def set_auto_trigger_level(self, *args):
 
479
                """
 
480
                Use the current trigger channel and samples to calculate the 50% level.
 
481
                """
 
482
                if not self.sampleses: return
 
483
                samples = self.sampleses[self[TRIGGER_CHANNEL_KEY]]
 
484
                self[TRIGGER_LEVEL_KEY] = (numpy.max(samples)+numpy.min(samples))/2
 
485
 
 
486
        def handle_samples(self):
 
487
                """
 
488
                Handle the cached samples from the scope input.
 
489
                Perform ac coupling, triggering, and auto ranging.
 
490
                """
 
491
                if not self.sampleses: return
 
492
                sampleses = self.sampleses
 
493
                if self[XY_MODE_KEY]:
 
494
                        self[DECIMATION_KEY] = 1
 
495
                        x_samples = sampleses[self[X_CHANNEL_KEY]]
 
496
                        y_samples = sampleses[self[Y_CHANNEL_KEY]]
 
497
                        #autorange
 
498
                        if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
 
499
                                x_min, x_max = common.get_min_max(x_samples)
 
500
                                y_min, y_max = common.get_min_max(y_samples)
 
501
                                #adjust the x per div
 
502
                                x_per_div = common.get_clean_num((x_max-x_min)/self[X_DIVS_KEY])
 
503
                                if x_per_div != self[X_PER_DIV_KEY]: self[X_PER_DIV_KEY] = x_per_div; return
 
504
                                #adjust the x offset
 
505
                                x_off = x_per_div*round((x_max+x_min)/2/x_per_div)
 
506
                                if x_off != self[X_OFF_KEY]: self[X_OFF_KEY] = x_off; return
 
507
                                #adjust the y per div
 
508
                                y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
 
509
                                if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
 
510
                                #adjust the y offset
 
511
                                y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
 
512
                                if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
 
513
                                self.autorange_ts = time.time()
 
514
                        #plot xy channel
 
515
                        self.plotter.set_waveform(
 
516
                                channel='XY',
 
517
                                samples=(x_samples, y_samples),
 
518
                                color_spec=CHANNEL_COLOR_SPECS[0],
 
519
                                marker=self[XY_MARKER_KEY],
 
520
                        )
 
521
                        #turn off each waveform
 
522
                        for i, samples in enumerate(sampleses):
 
523
                                self.plotter.clear_waveform(channel='Ch%d'%(i+1))
 
524
                else:
 
525
                        #autorange
 
526
                        if self[AUTORANGE_KEY] and time.time() - self.autorange_ts > AUTORANGE_UPDATE_RATE:
 
527
                                bounds = [common.get_min_max(samples) for samples in sampleses]
 
528
                                y_min = numpy.min([bound[0] for bound in bounds])
 
529
                                y_max = numpy.max([bound[1] for bound in bounds])
 
530
                                #adjust the y per div
 
531
                                y_per_div = common.get_clean_num((y_max-y_min)/self[Y_DIVS_KEY])
 
532
                                if y_per_div != self[Y_PER_DIV_KEY]: self[Y_PER_DIV_KEY] = y_per_div; return
 
533
                                #adjust the y offset
 
534
                                y_off = y_per_div*round((y_max+y_min)/2/y_per_div)
 
535
                                if y_off != self[Y_OFF_KEY]: self[Y_OFF_KEY] = y_off; return
 
536
                                self.autorange_ts = time.time()
 
537
                        #number of samples to scale to the screen
 
538
                        actual_rate = self.get_actual_rate()
 
539
                        time_span = self[T_PER_DIV_KEY]*self[T_DIVS_KEY]
 
540
                        num_samps = int(round(time_span*actual_rate))
 
541
                        #handle the time offset
 
542
                        t_off = self[T_FRAC_OFF_KEY]*(len(sampleses[0])/actual_rate - time_span)
 
543
                        if t_off != self[T_OFF_KEY]: self[T_OFF_KEY] = t_off; return
 
544
                        samps_off = int(round(actual_rate*self[T_OFF_KEY]))
 
545
                        #adjust the decim so that we use about half the samps
 
546
                        self[DECIMATION_KEY] = int(round(
 
547
                                        time_span*self[SAMPLE_RATE_KEY]/(0.5*len(sampleses[0]))
 
548
                                )
 
549
                        )
 
550
                        #num samps too small, auto increment the time
 
551
                        if num_samps < 2: self[T_PER_DIV_KEY] = common.get_clean_incr(self[T_PER_DIV_KEY])
 
552
                        #num samps in bounds, plot each waveform
 
553
                        elif num_samps <= len(sampleses[0]):
 
554
                                for i, samples in enumerate(sampleses):
 
555
                                        #plot samples
 
556
                                        self.plotter.set_waveform(
 
557
                                                channel='Ch%d'%(i+1),
 
558
                                                samples=samples[samps_off:num_samps+samps_off],
 
559
                                                color_spec=CHANNEL_COLOR_SPECS[i],
 
560
                                                marker=self[common.index_key(MARKER_KEY, i)],
 
561
                                                trig_off=self.trigger_offset,
 
562
                                        )
 
563
                        #turn XY channel off
 
564
                        self.plotter.clear_waveform(channel='XY')
 
565
                #keep trigger level within range
 
566
                if self[TRIGGER_LEVEL_KEY] > self.get_y_max():
 
567
                        self[TRIGGER_LEVEL_KEY] = self.get_y_max(); return
 
568
                if self[TRIGGER_LEVEL_KEY] < self.get_y_min():
 
569
                        self[TRIGGER_LEVEL_KEY] = self.get_y_min(); return
 
570
                #disable the trigger channel
 
571
                if not self[TRIGGER_SHOW_KEY] or self[XY_MODE_KEY] or self[TRIGGER_MODE_KEY] == gr.gr_TRIG_MODE_FREE:
 
572
                        self.plotter.clear_waveform(channel='Trig')
 
573
                else: #show trigger channel
 
574
                        trigger_level = self[TRIGGER_LEVEL_KEY]
 
575
                        trigger_point = (len(self.sampleses[0])-1)/self.get_actual_rate()/2.0
 
576
                        self.plotter.set_waveform(
 
577
                                channel='Trig',
 
578
                                samples=(
 
579
                                        [self.get_t_min(), trigger_point, trigger_point, trigger_point, trigger_point, self.get_t_max()],
 
580
                                        [trigger_level, trigger_level, self.get_y_max(), self.get_y_min(), trigger_level, trigger_level]
 
581
                                ),
 
582
                                color_spec=TRIGGER_COLOR_SPEC,
 
583
                        )
 
584
                #update the plotter
 
585
                self.plotter.update()
 
586
 
 
587
        def get_actual_rate(self): return 1.0*self[SAMPLE_RATE_KEY]/self[DECIMATION_KEY]
 
588
        def get_t_min(self): return self[T_OFF_KEY]
 
589
        def get_t_max(self): return self[T_PER_DIV_KEY]*self[T_DIVS_KEY] + self[T_OFF_KEY]
 
590
        def get_x_min(self): return -1*self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
 
591
        def get_x_max(self): return self[X_PER_DIV_KEY]*self[X_DIVS_KEY]/2.0 + self[X_OFF_KEY]
 
592
        def get_y_min(self): return -1*self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
 
593
        def get_y_max(self): return self[Y_PER_DIV_KEY]*self[Y_DIVS_KEY]/2.0 + self[Y_OFF_KEY]
 
594
 
 
595
        def update_grid(self, *args):
 
596
                """
 
597
                Update the grid to reflect the current settings:
 
598
                xy divisions, xy offset, xy mode setting
 
599
                """
 
600
                if self[T_FRAC_OFF_KEY] < 0: self[T_FRAC_OFF_KEY] = 0; return
 
601
                if self[T_FRAC_OFF_KEY] > 1: self[T_FRAC_OFF_KEY] = 1; return
 
602
                if self[XY_MODE_KEY]:
 
603
                        #update the x axis
 
604
                        self.plotter.set_x_label('Ch%d'%(self[X_CHANNEL_KEY]+1))
 
605
                        self.plotter.set_x_grid(self.get_x_min(), self.get_x_max(), self[X_PER_DIV_KEY])
 
606
                        #update the y axis
 
607
                        self.plotter.set_y_label('Ch%d'%(self[Y_CHANNEL_KEY]+1))
 
608
                        self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
 
609
                else:
 
610
                        #update the t axis
 
611
                        self.plotter.set_x_label('Time', 's')
 
612
                        self.plotter.set_x_grid(self.get_t_min(), self.get_t_max(), self[T_PER_DIV_KEY], True)
 
613
                        #update the y axis
 
614
                        self.plotter.set_y_label('Counts')
 
615
                        self.plotter.set_y_grid(self.get_y_min(), self.get_y_max(), self[Y_PER_DIV_KEY])
 
616
                #redraw current sample
 
617
                self.handle_samples()