Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/talogo.py129
-rw-r--r--TurtleArt/tawindow.py82
-rw-r--r--plugins/audio_sensors_plugin.py272
3 files changed, 304 insertions, 179 deletions
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index 953c296..c3e232e 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -25,8 +25,6 @@ import gtk
from time import time
from math import sqrt
-from numpy import append
-from numpy.fft import rfft
from random import uniform
from operator import isNumberType
import os.path
@@ -39,21 +37,17 @@ except ImportError:
pass
from taconstants import TAB_LAYER, BLACK, WHITE, DEFAULT_SCALE, ICON_SIZE, \
- BLOCK_NAMES, CONSTANTS, SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS, XO1, XO15, \
- PREFIX_DICTIONARY
+ BLOCK_NAMES, CONSTANTS, PREFIX_DICTIONARY
from tajail import myfunc, myfunc_import
from tautils import get_pixbuf_from_journal, convert, data_from_file, \
text_media_type, round_int, chr_to_ord, strtype, get_path
from RtfParser import RtfTextOnly
-from ringbuffer import RingBuffer1d
-
from gettext import gettext as _
VALUE_BLOCKS = ['box1', 'box2', 'color', 'shade', 'gray', 'scale', 'pensize',
- 'heading', 'xcor', 'ycor', 'pop', 'time', 'keyboard', 'sound',
- 'volume', 'pitch', 'resistance', 'voltage', 'see']
+ 'heading', 'xcor', 'ycor', 'pop', 'time', 'keyboard', 'see']
MEDIA_BLOCKS_DICTIONARY = {} # new media blocks get added here
PLUGIN_DICTIONARY = {} # new block primitives get added here
@@ -268,20 +262,6 @@ def _identity(x):
return(x)
-def _avg(array, abs_value=False):
- """ Calc. the average value of an array """
- if len(array) == 0:
- return 0
- array_sum = 0
- if abs_value:
- for a in array:
- array_sum += abs(a)
- else:
- for a in array:
- array_sum += a
- return float(array_sum) / len(array)
-
-
def _just_stop():
""" yield False to stop stack """
yield False
@@ -367,7 +347,6 @@ class LogoCode:
'pendown': [0, lambda self: self.tw.canvas.setpen(True)],
'pensize': [0, lambda self: self.tw.canvas.pensize],
'penup': [0, lambda self: self.tw.canvas.setpen(False)],
- 'pitch': [0, lambda self: self._get_pitch()],
'plus': [2, lambda self, x, y: _plus(x, y)],
'polar': [0, lambda self: self.tw.set_polar(True)],
'pop': [0, lambda self: self._prim_pop()],
@@ -380,7 +359,6 @@ class LogoCode:
'readpixel': [0, lambda self: self._read_pixel()],
'red': [0, lambda self: CONSTANTS['red']],
'repeat': [2, self._prim_repeat, True],
- 'resistance': [0, lambda self: self._get_resistance()],
'right': [1, lambda self, x: self._prim_right(x)],
'rightx': [0, lambda self: CONSTANTS['rightx']],
'rpos': [0, lambda self: CONSTANTS['rightpos']],
@@ -411,7 +389,6 @@ class LogoCode:
'showaligned': [1, lambda self, x: self._show(x, False)],
'showblocks': [0, lambda self: self.tw.showblocks()],
'skin': [1, lambda self, x: self._reskin(x)],
- 'sound': [0, lambda self: self._get_sound()],
'sqrt': [1, lambda self, x: _sqrt(x)],
'stack1': [0, self._prim_stack1, True],
'stack': [1, self._prim_stack, True],
@@ -442,8 +419,6 @@ class LogoCode:
'userdefined3': [3, lambda self, x, y,
z: self._prim_myblock([x, y, z])],
'video': [1, lambda self, x: self._play_video(x)],
- 'voltage': [0, lambda self: self._get_voltage()],
- 'volume': [0, lambda self: self._get_volume()],
'vres': [0, lambda self: CONSTANTS['height']],
'wait': [1, self._prim_wait, True],
'white': [0, lambda self: WHITE],
@@ -494,23 +469,12 @@ class LogoCode:
self.scale = DEFAULT_SCALE
- self.max_samples = 1500
- self.input_step = 1
-
- self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16')
- if self.tw.hw == XO1:
- self.voltage_gain = 0.00002225
- self.voltage_bias = 1.140
- elif self.tw.hw == XO15:
- self.voltage_gain = -0.0001471
- self.voltage_bias = 1.695
-
def stop_logo(self):
""" Stop logo is called from the Stop button on the toolbar """
self.tw.step_time = 0
self.step = _just_stop()
- for p in self.tw._plugins:
- print p.stop()
+ for plugin in self.tw._plugins:
+ plugin.stop()
if self.tw.gst_available:
from tagplay import stop_media
stop_media(self)
@@ -539,7 +503,6 @@ class LogoCode:
self.tw.saving_svg = False
self.find_value_blocks()
- self._update_audio_mode()
if self.trace > 0:
self.update_values = True
else:
@@ -1106,21 +1069,6 @@ class LogoCode:
self.value_blocks[name] = self.tw.block_list.get_similar_blocks(
'block', name)
- def _update_audio_mode(self):
- """ If there are sensor blocks, set the appropriate audio mode """
- if not self.tw.gst_available:
- return
- for name in ['sound', 'volume', 'pitch']:
- if len(self.value_blocks[name]) > 0:
- self.tw.audiograb.set_sensor_type()
- return
- if len(self.value_blocks['resistance']) > 0:
- self.tw.audiograb.set_sensor_type(SENSOR_DC_BIAS)
- return
- elif len(self.value_blocks['voltage']) > 0:
- self.tw.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS)
- return
-
def update_label_value(self, name, value=None):
""" Update the label of value blocks to reflect current value """
if self.tw.hide or not self.tw.interactive_mode or \
@@ -1460,75 +1408,6 @@ class LogoCode:
self.heap.append(b)
self.heap.append(g)
self.heap.append(r)
-
- def _get_volume(self):
- """ return mic in value """
- #TODO: Adjust gain for different HW
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- volume = float(_avg(buf, abs_value=True))
- self.update_label_value('volume', volume)
- return volume
- else:
- return 0
-
- def _get_sound(self):
- """ return raw mic in value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- sound = float(buf[0])
- self.update_label_value('sound', sound)
- return sound
- else:
- return 0
-
- def _get_pitch(self):
- """ return index of max value in fft of mic in values """
- buf = []
- for i in range(4):
- buf = append(buf, self.ringbuffer.read(None, self.input_step))
- if len(buf) > 0:
- r = []
- for j in rfft(buf):
- r.append(abs(j))
- # Convert output to Hertz
- pitch = r.index(max(r)) * 48000 / len(buf)
- self.update_label_value('pitch', pitch)
- return pitch
- else:
- return 0
-
- def _get_resistance(self):
- """ return resistance sensor value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
- # TODO: test this calibration on XO 1.5
- if self.tw.hw == XO1:
- resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \
- 8.0531)
- else:
- avg_buf = float(_avg(buf))
- if avg_buf > 0:
- resistance = (420000000 / avg_buf) - 13500
- else:
- resistance = 420000000
- self.update_label_value('resistance', resistance)
- return resistance
- else:
- return 0
-
- def _get_voltage(self):
- """ return voltage sensor value """
- buf = self.ringbuffer.read(None, self.input_step)
- if len(buf) > 0:
- # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
- voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias
- self.update_label_value('voltage', voltage)
- return voltage
- else:
- return 0
-
# Depreciated block methods
def _show_template1x1(self, title, media):
diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py
index 589e60c..7288f9c 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -87,7 +87,6 @@ from dbus.mainloop.glib import DBusGMainLoop
if GST_AVAILABLE:
from tagplay import stop_media
- from audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15
import logging
_logger = logging.getLogger('turtleart-activity')
@@ -264,23 +263,15 @@ class TurtleArtWindow():
self._setup_misc()
self._show_toolbar_palette(0, False)
- # setup sound/sensor grab
- if self.hw in [XO1, XO15]:
- PALETTES[PALETTE_NAMES.index('sensor')].append('resistance')
- PALETTES[PALETTE_NAMES.index('sensor')].append('voltage')
- self.audio_started = False
-
self._plugins = []
self._init_plugins()
self.lc = LogoCode(self)
- self._run_plugins()
+ self._setup_plugins()
self.saved_pictures = []
self.block_operation = ''
- ####
-
def _get_plugin_home(self):
""" Look in current directory first, then usual places """
path = os.path.join(os.getcwd(), self._PLUGIN_SUBPATH)
@@ -316,29 +307,32 @@ class TurtleArtWindow():
return plugin_files
def _init_plugins(self):
- for p in self._get_plugin_candidates(self._get_plugin_home()):
- P = p.capitalize()
+ for pluginfile in self._get_plugin_candidates(self._get_plugin_home()):
+ pluginclass = pluginfile.capitalize()
f = "def f(self): from plugins.%s import %s; return %s(self)" \
- % (p, P, P)
- plugin = {}
- exec f in globals(), plugin
- self._plugins.append(plugin.values()[0](self))
- '''
+ % (pluginfile, pluginclass, pluginclass)
+ plugins = {}
try:
- exec f in globals(), plugin
- self._plugins.append(plugin.values()[0](self))
+ exec f in globals(), plugins
+ self._plugins.append(plugins.values()[0](self))
except ImportError:
- print 'failed to import %s' % (P)
- '''
+ print 'failed to import %s' % (pluginclass)
- def _run_plugins(self):
- for p in self._plugins:
- p.setup()
+ def _setup_plugins(self):
+ for plugin in self._plugins:
+ plugin.setup()
- def new_buffer(self, buf):
- """ Append a new buffer to the ringbuffer """
- self.lc.ringbuffer.append(buf)
- return True
+ def _start_plugins(self):
+ for plugin in self._plugins:
+ plugin.start()
+
+ def _stop_plugins(self):
+ for plugin in self._plugins:
+ plugin.stop()
+
+ def _quit_plugins(self):
+ for plugin in self._plugins:
+ plugin.quit()
def _setup_events(self):
""" Register the events we listen to. """
@@ -414,31 +408,13 @@ class TurtleArtWindow():
self.lc.prim_clear()
self.display_coordinates()
- def _start_audiograb(self):
- """ Start grabbing audio if there is an audio block in use """
- if not self.gst_available:
- return
- if len(self.block_list.get_similar_blocks('block',
- ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0:
- if self.audio_started:
- self.audiograb.resume_grabbing()
- else:
- if self.hw == XO15:
- self.audiograb = AudioGrab_XO15(self.new_buffer, self)
- elif self.hw == XO1:
- self.audiograb = AudioGrab_XO1(self.new_buffer, self)
- else:
- self.audiograb = AudioGrab_Unknown(self.new_buffer, self)
- self.audiograb.start_grabbing()
- self.audio_started = True
-
def run_button(self, time):
""" Run turtle! """
if self.running_sugar:
self.activity.recenter()
if self.interactive_mode:
- self._start_audiograb()
+ self._start_plugins()
# Look for a 'start' block
for blk in self.just_blocks():
@@ -459,8 +435,7 @@ class TurtleArtWindow():
def stop_button(self):
""" Stop button """
self.lc.stop_logo()
- if self.audio_started:
- self.audiograb.pause_grabbing()
+ self._stop_plugins()
def set_userdefined(self, blk=None):
""" Change icon for user-defined blocks after loading Python code. """
@@ -1625,7 +1600,7 @@ class TurtleArtWindow():
dy = 20
blk.expand_in_y(dy)
else:
- self._start_audiograb()
+ self._start_plugins()
self._run_stack(blk)
return
@@ -1688,7 +1663,7 @@ class TurtleArtWindow():
elif blk.name in PYTHON_SKIN:
self._import_py()
else:
- self._start_audiograb()
+ self._start_plugins()
self._run_stack(blk)
elif blk.name in ['sandwichtop_no_arm_no_label',
@@ -1703,7 +1678,7 @@ class TurtleArtWindow():
collapse_stack(top)
else:
- self._start_audiograb()
+ self._start_plugins()
self._run_stack(blk)
def _expand_boolean(self, blk, blk2, dy):
@@ -1993,8 +1968,7 @@ class TurtleArtWindow():
if keyname == "p":
self.hideshow_button()
elif keyname == 'q':
- if self.audio_started:
- self.audiograb.stop_grabbing()
+ self._plugins_quit()
if self.gst_available:
stop_media(self.lc)
exit()
diff --git a/plugins/audio_sensors_plugin.py b/plugins/audio_sensors_plugin.py
new file mode 100644
index 0000000..d01e321
--- /dev/null
+++ b/plugins/audio_sensors_plugin.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python
+#Copyright (c) 2011 Walter Bender
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gst
+try:
+ from numpy import append
+ from numpy.fft import rfft
+ PITCH_AVAILABLE = True
+except:
+ PITCH_AVAILABLE = False
+import gtk
+
+from gettext import gettext as _
+
+from plugin import Plugin
+
+from audio.audiograb import AudioGrab_Unknown, AudioGrab_XO1, AudioGrab_XO15, \
+ SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS
+
+from audio.ringbuffer import RingBuffer1d
+
+from TurtleArt.taconstants import PALETTES, PALETTE_NAMES, BOX_STYLE_MEDIA, \
+ CONTENT_BLOCKS, BLOCK_NAMES, DEFAULTS, SPECIAL_NAMES, HELP_STRINGS, \
+ BOX_STYLE, PRIMITIVES, XO1, XO15
+from TurtleArt.talogo import VALUE_BLOCKS, PLUGIN_DICTIONARY
+from TurtleArt.tautils import get_path
+
+import logging
+_logger = logging.getLogger('turtleart-activity audio sensors plugin')
+
+
+def _avg(array, abs_value=False):
+ """ Calc. the average value of an array """
+ if len(array) == 0:
+ return 0
+ array_sum = 0
+ if abs_value:
+ for a in array:
+ array_sum += abs(a)
+ else:
+ for a in array:
+ array_sum += a
+ return float(array_sum) / len(array)
+
+
+class Audio_sensors_plugin(Plugin):
+
+ def __init__(self, parent):
+ self._parent = parent
+ self.hw = self._parent.hw
+ self._status = True # TODO: test for audio device
+
+ def setup(self):
+ # set up audio-sensor-specific blocks
+ if not self._status:
+ return
+
+ self.max_samples = 1500
+ self.input_step = 1
+
+ self.ringbuffer = RingBuffer1d(self.max_samples, dtype='int16')
+ if self.hw == XO1:
+ self.voltage_gain = 0.00002225
+ self.voltage_bias = 1.140
+ elif self.hw == XO15:
+ self.voltage_gain = -0.0001471
+ self.voltage_bias = 1.695
+
+ PALETTES[PALETTE_NAMES.index('sensor')].append('sound')
+ BOX_STYLE.append('sound')
+ BLOCK_NAMES['sound'] = [_('sound')]
+ HELP_STRINGS['sound'] = _('raw microphone input signal'),
+ VALUE_BLOCKS.append('sound')
+ PRIMITIVES['sound'] = 'sound'
+ PLUGIN_DICTIONARY['sound'] = self.prim_sound
+ self._parent.lc._def_prim('sound', 0,
+ lambda self: PLUGIN_DICTIONARY['sound']())
+ PALETTES[PALETTE_NAMES.index('sensor')].append('volume')
+ BOX_STYLE.append('volume')
+ BLOCK_NAMES['volume'] = [_('volume')]
+ HELP_STRINGS['volume'] = _('microphone input volume')
+ VALUE_BLOCKS.append('volume')
+ PRIMITIVES['volume'] = 'volume'
+ PLUGIN_DICTIONARY['volume'] = self.prim_volume
+ self._parent.lc._def_prim('volume', 0,
+ lambda self: PLUGIN_DICTIONARY['volume']())
+ PALETTES[PALETTE_NAMES.index('sensor')].append('pitch')
+ BOX_STYLE.append('pitch')
+ BLOCK_NAMES['pitch'] = [_('pitch')]
+ HELP_STRINGS['pitch'] = _('microphone input pitch')
+ VALUE_BLOCKS.append('pitch')
+ PRIMITIVES['pitch'] = 'pitch'
+ PLUGIN_DICTIONARY['pitch'] = self.prim_pitch
+ self._parent.lc._def_prim('pitch', 0,
+ lambda self: PLUGIN_DICTIONARY['pitch']())
+
+ if self.hw in [XO1, XO15]:
+ PALETTES[PALETTE_NAMES.index('sensor')].append('resistance')
+ BOX_STYLE.append('resistance')
+ BLOCK_NAMES['resistance'] = [_('resistance')]
+ HELP_STRINGS['resistance'] = _('sensor input resistance')
+ VALUE_BLOCKS.append('resistance')
+ PRIMITIVES['resistance'] = 'resistance'
+ PLUGIN_DICTIONARY['resistance'] = self.prim_resistance
+ self._parent.lc._def_prim('resistance', 0,
+ lambda self: PLUGIN_DICTIONARY['resistance']())
+
+ PALETTES[PALETTE_NAMES.index('sensor')].append('voltage')
+ BOX_STYLE.append('voltage')
+ BLOCK_NAMES['voltage'] = [_('voltage')]
+ HELP_STRINGS['voltage'] = _('sensor voltage')
+ VALUE_BLOCKS.append('voltage')
+ PRIMITIVES['voltage'] = 'voltage'
+ PLUGIN_DICTIONARY['voltage'] = self.prim_voltage
+ self._parent.lc._def_prim('voltage', 0,
+ lambda self: PLUGIN_DICTIONARY['voltage']())
+ self.audio_started = False
+
+ def start(self):
+ # This gets called by the start button
+ if not self._status:
+ return
+ """ Start grabbing audio if there is an audio block in use """
+ if len(self._parent.block_list.get_similar_blocks('block',
+ ['volume', 'sound', 'pitch', 'resistance', 'voltage'])) > 0:
+ if self.audio_started:
+ self.audiograb.resume_grabbing()
+ else:
+ if self.hw == XO15:
+ self.audiograb = AudioGrab_XO15(self.new_buffer, self)
+ elif self.hw == XO1:
+ self.audiograb = AudioGrab_XO1(self.new_buffer, self)
+ else:
+ self.audiograb = AudioGrab_Unknown(self.new_buffer, self)
+ self.audiograb.start_grabbing()
+ self.audio_started = True
+ self._update_audio_mode()
+
+ def new_buffer(self, buf):
+ """ Append a new buffer to the ringbuffer """
+ self.ringbuffer.append(buf)
+ return True
+
+ def _update_audio_mode(self):
+ """ If there are sensor blocks, set the appropriate audio mode """
+ if not hasattr(self._parent.lc, 'value_blocks'):
+ return
+ for name in ['sound', 'volume', 'pitch']:
+ if name in self._parent.lc.value_blocks:
+ if len(self._parent.lc.value_blocks[name]) > 0:
+ self.audiograb.set_sensor_type()
+ return
+ if 'resistance' in self._parent.lc.value_blocks:
+ if len(self._parent.lc.value_blocks['resistance']) > 0:
+ self.audiograb.set_sensor_type(SENSOR_DC_BIAS)
+ return
+ if 'voltage' in self._parent.lc.value_blocks:
+ if len(self._parent.lc.value_blocks['voltage']) > 0:
+ self.audiograb.set_sensor_type(SENSOR_DC_NO_BIAS)
+ return
+
+ def stop(self):
+ # This gets called by the stop button
+ if self._status:
+ if self.audio_started:
+ self.audiograb.pause_grabbing()
+
+ def goto_background(self):
+ # This gets called when your process is sent to the background
+ pass
+
+ def return_to_foreground(self):
+ # This gets called when your process returns from the background
+ pass
+
+ def quit(self):
+ # This gets called by the quit button
+ self.stop()
+
+ def _status_report(self):
+ print 'Reporting audio sensor status: %s' % (str(self._status))
+ return self._status
+
+ # Block primitives used in talogo
+
+ def prim_volume(self):
+ """ return mic in value """
+ #TODO: Adjust gain for different HW
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ volume = float(_avg(buf, abs_value=True))
+ self._parent.lc.update_label_value('volume', volume)
+ return volume
+ else:
+ return 0
+
+ def prim_sound(self):
+ """ return raw mic in value """
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ sound = float(buf[0])
+ self._parent.lc.update_label_value('sound', sound)
+ return sound
+ else:
+ return 0
+
+ def prim_pitch(self):
+ """ return index of max value in fft of mic in values """
+ if not PITCH_AVAILABLE:
+ return 0
+ buf = []
+ for i in range(4):
+ buf = append(buf, self.ringbuffer.read(None, self.input_step))
+ if len(buf) > 0:
+ r = []
+ for j in rfft(buf):
+ r.append(abs(j))
+ # Convert output to Hertz
+ pitch = r.index(max(r)) * 48000 / len(buf)
+ self._parent.lc.update_label_value('pitch', pitch)
+ return pitch
+ else:
+ return 0
+
+ def prim_resistance(self):
+ """ return resistance sensor value """
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
+ # TODO: test this calibration on XO 1.5
+ if self.hw == XO1:
+ resistance = 2.718 ** ((float(_avg(buf)) * 0.000045788) + \
+ 8.0531)
+ else:
+ avg_buf = float(_avg(buf))
+ if avg_buf > 0:
+ resistance = (420000000 / avg_buf) - 13500
+ else:
+ resistance = 420000000
+ self._parent.lc.update_label_value('resistance', resistance)
+ return resistance
+ else:
+ return 0
+
+ def prim_voltage(self):
+ """ return voltage sensor value """
+ buf = self.ringbuffer.read(None, self.input_step)
+ if len(buf) > 0:
+ # See <http://bugs.sugarlabs.org/ticket/552#comment:7>
+ voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias
+ self._parent.lc.update_label_value('voltage', voltage)
+ return voltage
+ else:
+ return 0