Added missed numbersink2.py
[debian/gnuradio] / gr-wxgui / src / python / numbersink2.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc.
4
5 # This file is part of GNU Radio
6
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
10 # any later version.
11
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING.  If not, write to
19 # the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 # Boston, MA 02111-1307, USA.
21
22
23 from gnuradio import gr, gru, window
24 from gnuradio.wxgui import stdgui2
25 import wx
26 import gnuradio.wxgui.plot as plot
27 import numpy
28 import threading
29 import math    
30
31 default_numbersink_size = (640,240)
32 default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15)
33
34 class number_sink_base(object):
35     def __init__(self, input_is_real=False, unit='',base_value=0, minval=-100.0,maxval=100.0,factor=1.0,decimal_places=10, ref_level=50,
36                  sample_rate=1, 
37                  number_rate=default_number_rate,
38                  average=False, avg_alpha=None, label='', peak_hold=False):
39
40         # initialize common attributes
41         self.unit=unit
42         self.base_value = base_value
43         self.minval=minval
44         self.maxval=maxval
45         self.factor=factor
46         self.y_divs = 8
47         self.decimal_places=decimal_places
48         self.ref_level = ref_level
49         self.sample_rate = sample_rate
50         number_size=1
51         self.number_size = number_size
52         self.number_rate = number_rate
53         self.average = average
54         if avg_alpha is None:
55             self.avg_alpha = 2.0 / number_rate
56         else:
57             self.avg_alpha = avg_alpha
58         self.label = label
59         self.peak_hold = peak_hold
60         self.show_gauge = True
61         self.input_is_real = input_is_real
62         self.msgq = gr.msg_queue(2)         # queue that holds a maximum of 2 messages
63
64     def set_decimal_places(self, decimal_places):
65         self.decimal_places = decimal_places
66
67     def set_ref_level(self, ref_level):
68         self.ref_level = ref_level
69
70     def print_current_value(self, comment):
71         print comment,self.win.current_value
72
73     def set_average(self, average):
74         self.average = average
75         if average:
76             self.avg.set_taps(self.avg_alpha)
77             self.set_peak_hold(False)
78         else:
79             self.avg.set_taps(1.0)
80
81     def set_peak_hold(self, enable):
82         self.peak_hold = enable
83         if enable:
84             self.set_average(False)
85         self.win.set_peak_hold(enable)
86
87     def set_show_gauge(self, enable):
88         self.show_gauge = enable
89         self.win.set_show_gauge(enable)
90
91     def set_avg_alpha(self, avg_alpha):
92         self.avg_alpha = avg_alpha
93
94     def set_base_value(self, base_value):
95         self.base_value = base_value
96         
97
98 class number_sink_f(gr.hier_block2, number_sink_base):
99     def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0,
100                  decimal_places=10, ref_level=50, sample_rate=1, 
101                  number_rate=default_number_rate, average=False, avg_alpha=None,
102                  label='', size=default_numbersink_size, peak_hold=False):
103
104         gr.hier_block2.__init__(self, "number_sink_f",
105                                 gr.io_signature(1, 1, gr.sizeof_float), # Input signature
106                                 gr.io_signature(0, 0, 0))               # Output signature
107
108         number_sink_base.__init__(self, unit=unit, input_is_real=True, base_value=base_value,
109                                minval=minval,maxval=maxval,factor=factor,
110                                decimal_places=decimal_places, ref_level=ref_level,
111                                sample_rate=sample_rate, number_rate=number_rate,
112                                average=average, avg_alpha=avg_alpha, label=label,
113                                peak_hold=peak_hold)
114          
115         number_size=1                      
116         one_in_n = gr.keep_one_in_n(gr.sizeof_float,
117                                     max(1, int(sample_rate/number_rate)))
118             
119         self.avg = gr.single_pole_iir_filter_ff(1.0, number_size)
120         sink = gr.message_sink(gr.sizeof_float , self.msgq, True)
121         self.connect(self, self.avg, one_in_n, sink)
122
123         self.win = number_window(self, parent, size=size,label=label)
124         self.set_average(self.average)
125
126 class number_sink_c(gr.hier_block2, number_sink_base):
127     def __init__(self, parent, unit='',base_value=0,minval=-100.0,maxval=100.0,factor=1.0,
128                  decimal_places=10, ref_level=50, sample_rate=1,
129                  number_rate=default_number_rate, average=False, avg_alpha=None,
130                  label='', size=default_numbersink_size, peak_hold=False):
131
132         gr.hier_block2.__init__(self, "number_sink_c",
133                                 gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
134                                 gr.io_signature(0, 0, 0))                    # Output signature
135
136         number_sink_base.__init__(self, unit=unit, input_is_real=False, base_value=base_value,factor=factor,
137                                minval=minval,maxval=maxval,decimal_places=decimal_places, ref_level=ref_level,
138                                sample_rate=sample_rate, number_rate=number_rate,
139                                average=average, avg_alpha=avg_alpha, label=label,
140                                peak_hold=peak_hold)
141
142         number_size=1                      
143         one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex,
144                                     max(1, int(sample_rate/number_rate)))
145             
146         self.avg = gr.single_pole_iir_filter_cc(1.0, number_size)
147         sink = gr.message_sink(gr.sizeof_gr_complex , self.msgq, True)
148         self.connect(self, self.avg, one_in_n, sink)
149
150         self.win = number_window(self, parent, size=size,label=label)
151         self.set_average(self.average)
152
153
154 # ------------------------------------------------------------------------
155
156 myDATA_EVENT = wx.NewEventType()
157 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
158
159
160 class DataEvent(wx.PyEvent):
161     def __init__(self, data):
162         wx.PyEvent.__init__(self)
163         self.SetEventType (myDATA_EVENT)
164         self.data = data
165
166     def Clone (self): 
167         self.__class__ (self.GetId())
168
169
170 class input_watcher (threading.Thread):
171     def __init__ (self, msgq, number_size, event_receiver, **kwds):
172         threading.Thread.__init__ (self, **kwds)
173         self.setDaemon (1)
174         self.msgq = msgq
175         self.number_size = number_size
176         self.event_receiver = event_receiver
177         self.keep_running = True
178         self.start ()
179
180     def run (self):
181         while (self.keep_running):
182             msg = self.msgq.delete_head()  # blocking read of message queue
183             itemsize = int(msg.arg1())
184             nitems = int(msg.arg2())
185
186             s = msg.to_string()            # get the body of the msg as a string
187
188             # There may be more than one number in the message.
189             # If so, we take only the last one
190             if nitems > 1:
191                 start = itemsize * (nitems - 1)
192                 s = s[start:start+itemsize]
193
194             complex_data = numpy.fromstring (s, numpy.float32)
195             de = DataEvent (complex_data)
196             wx.PostEvent (self.event_receiver, de)
197             del de
198     
199 #========================================================================================
200 class static_text_window (wx.StaticText): #plot.PlotCanvas):
201     def __init__ (self, parent, numbersink,id = -1,label="number",
202                   pos = wx.DefaultPosition, size = wx.DefaultSize,
203                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
204         wx.StaticText.__init__(self, parent, id, label, pos, size, style, name)
205         self.parent=parent
206         self.label=label
207         self.numbersink = numbersink
208         self.peak_hold = False
209         self.peak_vals = None
210         self.build_popup_menu()
211         self.Bind(wx.EVT_RIGHT_UP, self.on_right_click)
212
213     def on_close_window (self, event):
214         print "number_window:on_close_window"
215         self.keep_running = False
216
217     def set_peak_hold(self, enable):
218         self.peak_hold = enable
219         self.peak_vals = None
220
221     def update_y_range (self):
222         ymax = self.numbersink.ref_level
223         ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs
224         self.y_range = self._axisInterval ('min', ymin, ymax)
225
226     def on_average(self, evt):
227         # print "on_average"
228         self.numbersink.set_average(evt.IsChecked())
229
230     def on_peak_hold(self, evt):
231         # print "on_peak_hold"
232         self.numbersink.set_peak_hold(evt.IsChecked())
233
234     def on_show_gauge(self, evt):
235         # print "on_show_gauge"
236         self.numbersink.set_show_gauge(evt.IsChecked())
237         print evt.IsChecked()
238
239     def on_incr_ref_level(self, evt):
240         # print "on_incr_ref_level"
241         self.numbersink.set_ref_level(self.numbersink.ref_level
242                                    + self.numbersink.decimal_places)
243
244     def on_decr_ref_level(self, evt):
245         # print "on_decr_ref_level"
246         self.numbersink.set_ref_level(self.numbersink.ref_level
247                                    - self.numbersink.decimal_places)
248
249     def on_incr_decimal_places(self, evt):
250         # print "on_incr_decimal_places"
251         self.numbersink.set_decimal_places(self.numbersink.decimal_places+1)
252
253     def on_decr_decimal_places(self, evt):
254         # print "on_decr_decimal_places"
255         self.numbersink.set_decimal_places(max(self.numbersink.decimal_places-1,0)) 
256
257     def on_decimal_places(self, evt):
258         # print "on_decimal_places"
259         Id = evt.GetId()
260         if Id == self.id_decimal_places_0:
261             self.numbersink.set_decimal_places(0)
262         elif Id == self.id_decimal_places_1:
263             self.numbersink.set_decimal_places(1)
264         elif Id == self.id_decimal_places_2:
265             self.numbersink.set_decimal_places(2)
266         elif Id == self.id_decimal_places_3:
267             self.numbersink.set_decimal_places(3)
268         elif Id == self.id_decimal_places_6:
269             self.numbersink.set_decimal_places(6)
270         elif Id == self.id_decimal_places_9:
271             self.numbersink.set_decimal_places(9)
272         
273     def on_right_click(self, event):
274         menu = self.popup_menu
275         for id, pred in self.checkmarks.items():
276             item = menu.FindItemById(id)
277             item.Check(pred())
278         self.PopupMenu(menu, event.GetPosition())
279
280     def build_popup_menu(self):
281         self.id_show_gauge = wx.NewId()
282         self.id_incr_ref_level = wx.NewId()
283         self.id_decr_ref_level = wx.NewId()
284         self.id_incr_decimal_places = wx.NewId()
285         self.id_decr_decimal_places = wx.NewId()
286         self.id_decimal_places_0 = wx.NewId()
287         self.id_decimal_places_1 = wx.NewId()
288         self.id_decimal_places_2 = wx.NewId()
289         self.id_decimal_places_3 = wx.NewId()
290         self.id_decimal_places_6 = wx.NewId()
291         self.id_decimal_places_9 = wx.NewId()
292         self.id_average = wx.NewId()
293         self.id_peak_hold = wx.NewId()
294
295         self.Bind(wx.EVT_MENU, self.on_average, id=self.id_average)
296         self.Bind(wx.EVT_MENU, self.on_peak_hold, id=self.id_peak_hold)
297         #self.Bind(wx.EVT_MENU, self.on_hide_gauge, id=self.id_hide_gauge)
298         self.Bind(wx.EVT_MENU, self.on_show_gauge, id=self.id_show_gauge)
299         self.Bind(wx.EVT_MENU, self.on_incr_ref_level, id=self.id_incr_ref_level)
300         self.Bind(wx.EVT_MENU, self.on_decr_ref_level, id=self.id_decr_ref_level)
301         self.Bind(wx.EVT_MENU, self.on_incr_decimal_places, id=self.id_incr_decimal_places)
302         self.Bind(wx.EVT_MENU, self.on_decr_decimal_places, id=self.id_decr_decimal_places)
303         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_0)
304         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_1)
305         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_2)
306         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_3)
307         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_6)
308         self.Bind(wx.EVT_MENU, self.on_decimal_places, id=self.id_decimal_places_9)
309
310         # make a menu
311         menu = wx.Menu()
312         self.popup_menu = menu
313         menu.AppendCheckItem(self.id_average, "Average")
314         menu.AppendCheckItem(self.id_peak_hold, "Peak Hold")
315         menu.AppendCheckItem(self.id_show_gauge, "Show gauge")
316         menu.Append(self.id_incr_ref_level, "Incr Ref Level")
317         menu.Append(self.id_decr_ref_level, "Decr Ref Level")
318         menu.Append(self.id_incr_decimal_places, "Incr decimal places")
319         menu.Append(self.id_decr_decimal_places, "Decr decimal places")
320         menu.AppendSeparator()
321         # we'd use RadioItems for these, but they're not supported on Mac
322         menu.AppendCheckItem(self.id_decimal_places_0, "0 decimal places")
323         menu.AppendCheckItem(self.id_decimal_places_1, "1 decimal places")
324         menu.AppendCheckItem(self.id_decimal_places_2, "2 decimal places")
325         menu.AppendCheckItem(self.id_decimal_places_3, "3 decimal places")
326         menu.AppendCheckItem(self.id_decimal_places_6, "6 decimal places")
327         menu.AppendCheckItem(self.id_decimal_places_9, "9 decimal places")
328
329         self.checkmarks = {
330             self.id_average : lambda : self.numbersink.average,
331             self.id_peak_hold : lambda : self.numbersink.peak_hold,
332             self.id_show_gauge : lambda : self.numbersink.show_gauge,
333             self.id_decimal_places_0 : lambda : self.numbersink.decimal_places == 0,
334             self.id_decimal_places_1 : lambda : self.numbersink.decimal_places == 1,
335             self.id_decimal_places_2 : lambda : self.numbersink.decimal_places == 2,
336             self.id_decimal_places_3 : lambda : self.numbersink.decimal_places == 3,
337             self.id_decimal_places_6 : lambda : self.numbersink.decimal_places == 6,
338             self.id_decimal_places_9 : lambda : self.numbersink.decimal_places == 9,
339             }
340
341 def next_up(v, seq):
342     """
343     Return the first item in seq that is > v.
344     """
345     for s in seq:
346         if s > v:
347             return s
348     return v
349
350 def next_down(v, seq):
351     """
352     Return the last item in seq that is < v.
353     """
354     rseq = list(seq[:])
355     rseq.reverse()
356
357     for s in rseq:
358         if s < v:
359             return s
360     return v
361
362
363 #========================================================================================
364 class number_window (plot.PlotCanvas):
365     def __init__ (self, numbersink, parent, id = -1,label="number",
366                   pos = wx.DefaultPosition, size = wx.DefaultSize,
367                   style = wx.DEFAULT_FRAME_STYLE, name = ""):
368         plot.PlotCanvas.__init__ (self, parent, id, pos, size, style, name)
369         self.static_text=static_text_window( self, numbersink,id, label, pos, (size[0]/2,size[1]/2), style, name)
370         gauge_style = wx.GA_HORIZONTAL
371         vbox=wx.BoxSizer(wx.VERTICAL)
372         vbox.Add (self.static_text, 0, wx.EXPAND)
373         self.current_value=None
374         if numbersink.input_is_real:
375           self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/2),size=(size[0]/2,size[1]/2), style=gauge_style,  name = "gauge")
376           vbox.Add (self.gauge, 1, wx.EXPAND)
377         else:
378           self.gauge=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]/3),size=(size[0]/2,size[1]/3), style=gauge_style,  name = "gauge")
379           self.gauge_imag=wx.Gauge( self, id, range=1000, pos=(pos[0],pos[1]+size[1]*2/3),size=(size[0]/2,size[1]/3), style=gauge_style,  name = "gauge_imag")
380           vbox.Add (self.gauge, 1, wx.EXPAND)
381           vbox.Add (self.gauge_imag, 1, wx.EXPAND)
382         self.sizer = vbox
383         self.SetSizer (self.sizer)
384         self.SetAutoLayout (True)
385         self.sizer.Fit (self)
386
387         self.label=label
388         self.numbersink = numbersink
389         self.peak_hold = False
390         self.peak_vals = None
391         
392         EVT_DATA_EVENT (self, self.set_data)
393         wx.EVT_CLOSE (self, self.on_close_window)
394         self.input_watcher = input_watcher(numbersink.msgq, numbersink.number_size, self)
395
396     def on_close_window (self, event):
397         # print "number_window:on_close_window"
398         self.keep_running = False
399
400     def set_show_gauge(self, enable):
401         self.show_gauge = enable
402         if enable:
403           self.gauge.Show()
404           if not self.numbersink.input_is_real:
405             self.gauge_imag.Show()
406           #print 'show'
407         else:
408           self.gauge.Hide()
409           if not self.numbersink.input_is_real:
410             self.gauge_imag.Hide()
411           #print 'hide'
412
413     def set_data (self, evt):
414         numbers = evt.data
415         L = len (numbers)
416
417         if self.peak_hold:
418             if self.peak_vals is None:
419                 self.peak_vals = numbers
420             else:
421                 self.peak_vals = numpy.maximum(numbers, self.peak_vals)
422                 numbers = self.peak_vals
423
424         if self.numbersink.input_is_real:
425             real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value
426             imag_value=0.0
427             self.current_value=real_value
428         else:
429             real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value
430             imag_value=numbers[1]*self.numbersink.factor + self.numbersink.base_value
431             self.current_value=complex(real_value,imag_value)
432         x = max(real_value, imag_value)
433         if x >= 1e9:
434             sf = 1e-9
435             unit_prefix = "G"
436         elif x >= 1e6:
437             sf = 1e-6
438             unit_prefix = "M"
439         elif x>= 1e3:
440             sf = 1e-3
441             unit_prefix = "k"
442         else :
443             sf = 1
444             unit_prefix = ""
445         if self.numbersink.input_is_real:
446           showtext = "%s: %.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,unit_prefix,self.numbersink.unit)
447         else:
448           showtext = "%s: %.*f,%.*f %s%s" % (self.label, self.numbersink.decimal_places,real_value*sf,
449                                                        self.numbersink.decimal_places,imag_value*sf,unit_prefix,self.numbersink.unit)
450         self.static_text.SetLabel(showtext)
451         self.gauge.SetValue(int(float((real_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500)
452         if not self.numbersink.input_is_real:
453           self.gauge.SetValue(int(float((imag_value-self.numbersink.base_value)*1000.0/(self.numbersink.maxval-self.numbersink.minval)))+500)
454
455     def set_peak_hold(self, enable):
456         self.peak_hold = enable
457         self.peak_vals = None
458
459     def update_y_range (self):
460         ymax = self.numbersink.ref_level
461         ymin = self.numbersink.ref_level - self.numbersink.decimal_places * self.numbersink.y_divs
462         self.y_range = self._axisInterval ('min', ymin, ymax)
463
464     def on_average(self, evt):
465         # print "on_average"
466         self.numbersink.set_average(evt.IsChecked())
467
468     def on_peak_hold(self, evt):
469         # print "on_peak_hold"
470         self.numbersink.set_peak_hold(evt.IsChecked())
471
472
473 # ----------------------------------------------------------------
474 # Standalone test app
475 # ----------------------------------------------------------------
476
477 class test_app_flow_graph (stdgui2.std_top_block):
478     def __init__(self, frame, panel, vbox, argv):
479         stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
480
481         # build our flow graph
482         input_rate = 20.48e3
483
484         # Generate a real and complex sinusoids
485         src1 = gr.sig_source_f (input_rate, gr.GR_SIN_WAVE, 2e3, 1)
486         src2 = gr.sig_source_c (input_rate, gr.GR_SIN_WAVE, 2e3, 1)
487
488         # We add these throttle blocks so that this demo doesn't
489         # suck down all the CPU available.  Normally you wouldn't use these.
490         thr1 = gr.throttle(gr.sizeof_float, input_rate)
491         thr2 = gr.throttle(gr.sizeof_gr_complex, input_rate)
492
493         sink1 = number_sink_f (panel, unit='Hz',label="Real Data", avg_alpha=0.001,
494                             sample_rate=input_rate, base_value=100e3,
495                             ref_level=0, decimal_places=3)
496         vbox.Add (sink1.win, 1, wx.EXPAND)
497         sink2 = number_sink_c (panel, unit='V',label="Complex Data", avg_alpha=0.001,
498                             sample_rate=input_rate, base_value=0,
499                             ref_level=0, decimal_places=3)
500         vbox.Add (sink2.win, 1, wx.EXPAND)
501
502         self.connect (src1, thr1, sink1)
503         self.connect (src2, thr2, sink2)
504
505 def main ():
506     app = stdgui2.stdapp (test_app_flow_graph, "Number Sink Test App")
507     app.MainLoop ()
508
509 if __name__ == '__main__':
510     main ()