3 # Copyright 2003,2004,2005,2006,2007 Free Software Foundation, Inc.
5 # This file is part of GNU Radio
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)
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.
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.
23 from gnuradio import gr, gru, window
24 from gnuradio.wxgui import stdgui2
26 import gnuradio.wxgui.plot as plot
31 default_numbersink_size = (640,240)
32 default_number_rate = gr.prefs().get_long('wxgui', 'number_rate', 15)
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,
37 number_rate=default_number_rate,
38 average=False, avg_alpha=None, label='', peak_hold=False):
40 # initialize common attributes
42 self.base_value = base_value
47 self.decimal_places=decimal_places
48 self.ref_level = ref_level
49 self.sample_rate = sample_rate
51 self.number_size = number_size
52 self.number_rate = number_rate
53 self.average = average
55 self.avg_alpha = 2.0 / number_rate
57 self.avg_alpha = avg_alpha
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
64 def set_decimal_places(self, decimal_places):
65 self.decimal_places = decimal_places
67 def set_ref_level(self, ref_level):
68 self.ref_level = ref_level
70 def print_current_value(self, comment):
71 print comment,self.win.current_value
73 def set_average(self, average):
74 self.average = average
76 self.avg.set_taps(self.avg_alpha)
77 self.set_peak_hold(False)
79 self.avg.set_taps(1.0)
81 def set_peak_hold(self, enable):
82 self.peak_hold = enable
84 self.set_average(False)
85 self.win.set_peak_hold(enable)
87 def set_show_gauge(self, enable):
88 self.show_gauge = enable
89 self.win.set_show_gauge(enable)
91 def set_avg_alpha(self, avg_alpha):
92 self.avg_alpha = avg_alpha
94 def set_base_value(self, base_value):
95 self.base_value = base_value
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):
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
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,
116 one_in_n = gr.keep_one_in_n(gr.sizeof_float,
117 max(1, int(sample_rate/number_rate)))
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)
123 self.win = number_window(self, parent, size=size,label=label)
124 self.set_average(self.average)
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):
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
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,
143 one_in_n = gr.keep_one_in_n(gr.sizeof_gr_complex,
144 max(1, int(sample_rate/number_rate)))
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)
150 self.win = number_window(self, parent, size=size,label=label)
151 self.set_average(self.average)
154 # ------------------------------------------------------------------------
156 myDATA_EVENT = wx.NewEventType()
157 EVT_DATA_EVENT = wx.PyEventBinder (myDATA_EVENT, 0)
160 class DataEvent(wx.PyEvent):
161 def __init__(self, data):
162 wx.PyEvent.__init__(self)
163 self.SetEventType (myDATA_EVENT)
167 self.__class__ (self.GetId())
170 class input_watcher (threading.Thread):
171 def __init__ (self, msgq, number_size, event_receiver, **kwds):
172 threading.Thread.__init__ (self, **kwds)
175 self.number_size = number_size
176 self.event_receiver = event_receiver
177 self.keep_running = True
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())
186 s = msg.to_string() # get the body of the msg as a string
188 # There may be more than one number in the message.
189 # If so, we take only the last one
191 start = itemsize * (nitems - 1)
192 s = s[start:start+itemsize]
194 complex_data = numpy.fromstring (s, numpy.float32)
195 de = DataEvent (complex_data)
196 wx.PostEvent (self.event_receiver, de)
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)
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)
213 def on_close_window (self, event):
214 print "number_window:on_close_window"
215 self.keep_running = False
217 def set_peak_hold(self, enable):
218 self.peak_hold = enable
219 self.peak_vals = None
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)
226 def on_average(self, evt):
228 self.numbersink.set_average(evt.IsChecked())
230 def on_peak_hold(self, evt):
231 # print "on_peak_hold"
232 self.numbersink.set_peak_hold(evt.IsChecked())
234 def on_show_gauge(self, evt):
235 # print "on_show_gauge"
236 self.numbersink.set_show_gauge(evt.IsChecked())
237 print evt.IsChecked()
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)
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)
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)
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))
257 def on_decimal_places(self, evt):
258 # print "on_decimal_places"
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)
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)
278 self.PopupMenu(menu, event.GetPosition())
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()
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)
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")
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,
343 Return the first item in seq that is > v.
350 def next_down(v, seq):
352 Return the last item in seq that is < v.
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)
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)
383 self.SetSizer (self.sizer)
384 self.SetAutoLayout (True)
385 self.sizer.Fit (self)
388 self.numbersink = numbersink
389 self.peak_hold = False
390 self.peak_vals = None
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)
396 def on_close_window (self, event):
397 # print "number_window:on_close_window"
398 self.keep_running = False
400 def set_show_gauge(self, enable):
401 self.show_gauge = enable
404 if not self.numbersink.input_is_real:
405 self.gauge_imag.Show()
409 if not self.numbersink.input_is_real:
410 self.gauge_imag.Hide()
413 def set_data (self, evt):
418 if self.peak_vals is None:
419 self.peak_vals = numbers
421 self.peak_vals = numpy.maximum(numbers, self.peak_vals)
422 numbers = self.peak_vals
424 if self.numbersink.input_is_real:
425 real_value=numbers[0]*self.numbersink.factor + self.numbersink.base_value
427 self.current_value=real_value
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)
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)
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)
455 def set_peak_hold(self, enable):
456 self.peak_hold = enable
457 self.peak_vals = None
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)
464 def on_average(self, evt):
466 self.numbersink.set_average(evt.IsChecked())
468 def on_peak_hold(self, evt):
469 # print "on_peak_hold"
470 self.numbersink.set_peak_hold(evt.IsChecked())
473 # ----------------------------------------------------------------
474 # Standalone test app
475 # ----------------------------------------------------------------
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)
481 # build our flow graph
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)
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)
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)
502 self.connect (src1, thr1, sink1)
503 self.connect (src2, thr2, sink2)
506 app = stdgui2.stdapp (test_app_flow_graph, "Number Sink Test App")
509 if __name__ == '__main__':