From c3f0e8e2bfa09e2043016e808673a8c0779e4886 Mon Sep 17 00:00:00 2001 From: Walter Bender Date: Thu, 07 Apr 2011 14:51:40 +0000 Subject: reorganizing plugins into separate directories; adding support for plugin palette icons in plugin directory --- (limited to 'plugins') diff --git a/plugins/audio_sensors/__init__.py b/plugins/audio_sensors/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/audio_sensors/__init__.py diff --git a/plugins/audio_sensors/audio_sensors.py b/plugins/audio_sensors/audio_sensors.py new file mode 100644 index 0000000..39bad17 --- /dev/null +++ b/plugins/audio_sensors/audio_sensors.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +try: + from numpy import append + from numpy.fft import rfft + PITCH_AVAILABLE = True +except: + PITCH_AVAILABLE = False + +from plugins.plugin import Plugin + +from plugins.audio_sensors.audiograb import AudioGrab_Unknown, AudioGrab_XO1, \ + AudioGrab_XO15, SENSOR_DC_NO_BIAS, SENSOR_DC_BIAS + +from plugins.audio_sensors.ringbuffer import RingBuffer1d + +from TurtleArt.tapalette import make_palette +from TurtleArt.taconstants import XO1, XO15 +from TurtleArt.talogo import primitive_dictionary +from TurtleArt.tautils import debug_output + +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): + + def __init__(self, parent): + self._parent = parent + self.hw = self._parent.hw + self.running_sugar = self._parent.running_sugar + 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 + + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['sound'] = self.prim_sound + palette.add_block('sound', + style='box-style', + label=_('sound'), + help_string=_('raw microphone input signal'), + value_block=True, + prim_name='sound') + self._parent.lc.def_prim('sound', 0, + lambda self: primitive_dictionary['sound']()) + + primitive_dictionary['volume'] = self.prim_volume + palette.add_block('volume', + style='box-style', + label=_('loudness'), + help_string=_('microphone input volume'), + value_block=True, + prim_name='volume') + self._parent.lc.def_prim('volume', 0, + lambda self: primitive_dictionary['volume']()) + + primitive_dictionary['pitch'] = self.prim_pitch + if PITCH_AVAILABLE: + palette.add_block('pitch', + style='box-style', + label=_('pitch'), + help_string=_('microphone input pitch'), + value_block=True, + prim_name='pitch') + else: + palette.add_block('pitch', + hidden=True, + style='box-style', + label=_('pitch'), + help_string=_('microphone input pitch'), + value_block=True, + prim_name='pitch') + self._parent.lc.def_prim('pitch', 0, + lambda self: primitive_dictionary['pitch']()) + + if self.hw in [XO1, XO15]: + primitive_dictionary['resistance'] = self.prim_resistance + palette.add_block('resistance', + style='box-style', + label=_('resistance'), + help_string=_('microphone input resistance'), + value_block=True, + prim_name='resistance') + self._parent.lc.def_prim('resistance', 0, + lambda self: primitive_dictionary['resistance']()) + + primitive_dictionary['voltage'] = self.prim_voltage + palette.add_block('voltage', + style='box-style', + label=_('voltage'), + help_string=_('microphone input voltage'), + value_block=True, + prim_name='voltage') + self._parent.lc.def_prim('voltage', 0, + lambda self: primitive_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 + # TODO: handle this case + pass + + def return_to_foreground(self): + # This gets called when your process returns from the background + # TODO: handle this case + pass + + def quit(self): + # This gets called by the quit button + self.stop() + + def _status_report(self): + debug_output('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 + # 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 + voltage = float(_avg(buf)) * self.voltage_gain + self.voltage_bias + self._parent.lc.update_label_value('voltage', voltage) + return voltage + else: + return 0 diff --git a/plugins/audio_sensors/audiograb.py b/plugins/audio_sensors/audiograb.py new file mode 100644 index 0000000..84e0c8d --- /dev/null +++ b/plugins/audio_sensors/audiograb.py @@ -0,0 +1,623 @@ +#! /usr/bin/python +# +# Author: Arjun Sarwal arjun@laptop.org +# Copyright (C) 2007, Arjun Sarwal +# Copyright (C) 2009,10 Walter Bender +# Copyright (C) 2009, Benjamin Berg, Sebastian Berg +# Copyright (C) 2009, Sayamindu Dasgupta +# Copyright (C) 2010, Sascha Silbe +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +SENSOR_AC_NO_BIAS = 'external' +SENSOR_AC_BIAS = 'sound' +SENSOR_DC_NO_BIAS = 'voltage' +SENSOR_DC_BIAS = 'resistance' + +import pygst +import gst +import gst.interfaces +from numpy import fromstring +import os +import subprocess +from string import find +from threading import Timer + +# Initial device settings +RATE = 48000 +MIC_BOOST = True +DC_MODE_ENABLE = False +CAPTURE_GAIN = 50 +BIAS = True + +# Setting on quit +QUIT_MIC_BOOST = False +QUIT_DC_MODE_ENABLE = False +QUIT_CAPTURE_GAIN = 100 +QUIT_BIAS = True + +from TurtleArt.taconstants import XO1 +from TurtleArt.tautils import debug_output + + +class AudioGrab: + """ The interface between measure and the audio device """ + + def __init__(self, callable1, parent): + """ Initialize the class: callable1 is a data buffer; + parent is the parent class""" + + self.callable1 = callable1 + self.parent = parent + self.sensor = None + + self.temp_buffer = [0] + self.picture_buffer = [] # place to hold screen grabs + + self.draw_graph_status = False + self.screenshot = True + + self.rate = 48000 + self.final_count = 0 + self.count_temp = 0 + self.entry_count = 0 + + self.counter_buffer = 0 + + self._dc_control = None + self._mic_bias_control = None + self._capture_control = None + self._mic_boost_control = None + self._hardwired = False # Query controls or use hardwired names + + # Set up gst pipeline + self.pipeline = gst.Pipeline("pipeline") + self.alsasrc = gst.element_factory_make("alsasrc", "alsa-source") + self.pipeline.add(self.alsasrc) + self.caps1 = gst.element_factory_make("capsfilter", "caps1") + self.pipeline.add(self.caps1) + caps_str = "audio/x-raw-int,rate=%d,channels=1,depth=16" % (RATE) + self.caps1.set_property("caps", gst.caps_from_string(caps_str)) + self.fakesink = gst.element_factory_make("fakesink", "fsink") + self.pipeline.add(self.fakesink) + self.fakesink.connect("handoff", self.on_buffer) + self.fakesink.set_property("signal-handoffs", True) + gst.element_link_many(self.alsasrc, self.caps1, self.fakesink) + + self.dont_queue_the_buffer = False + + self._mixer = gst.element_factory_make('alsamixer') + rc = self._mixer.set_state(gst.STATE_PAUSED) + assert rc == gst.STATE_CHANGE_SUCCESS + + # Query the available controls + try: # F11+ + debug_output('controls: %r' % \ + ([t.props.untranslated_label for t in self._mixer.list_tracks()]), + self.parent.running_sugar) + self._dc_control = self._find_control(['dc mode']) + self._mic_bias_control = self._find_control(['mic bias', + 'dc input bias', + 'v_refout']) + self._mic_boost_control = self._find_control(['mic boost', + 'mic boost (+20db)', + 'internal mic boost', + 'analog mic boost']) + self._mic_gain_control = self._find_control(['mic']) + self._capture_control = self._find_control(['capture']) + self._master_control = self._find_control(['master']) + except AttributeError: # F9- (no untranslated_label attribute) + self._hardwired = True + + # Variables for saving and resuming state of sound device + self.master = self.get_master() + self.bias = BIAS + self.dcmode = DC_MODE_ENABLE + self.capture_gain = CAPTURE_GAIN + self.mic_boost = MIC_BOOST + self.mic = self.get_mic_gain() + + # Timer for interval sampling and switch to indicate when to capture + self.capture_timer = None + self.capture_interval_sample = False + + def set_handoff_signal(self, handoff_state): + """Sets whether the handoff signal would generate an interrupt or not""" + self.fakesink.set_property("signal-handoffs", handoff_state) + + def _new_buffer(self, buf): + """ Use a new buffer """ + if not self.dont_queue_the_buffer: + self.temp_buffer = buf + self.callable1(buf) + else: + pass + + def on_buffer(self, element, buffer, pad): + """The function that is called whenever new data is available + This is the signal handler for the handoff signal""" + if buffer is None: + debug_output('audiograb buffer is None', self.parent.running_sugar) + return False + + temp_buffer = fromstring(buffer, 'int16') + if not self.dont_queue_the_buffer: + self._new_buffer(temp_buffer) + return False + + def set_sensor(self, sensor): + """Keep a reference to the sensot toolbar for logging""" + self.sensor = sensor + + def start_sound_device(self): + """Start or Restart grabbing data from the audio capture""" + gst.event_new_flush_start() + self.pipeline.set_state(gst.STATE_PLAYING) + + def stop_sound_device(self): + """Stop grabbing data from capture device""" + gst.event_new_flush_stop() + self.pipeline.set_state(gst.STATE_NULL) + + def set_sampling_rate(self, sr): + """Sets the sampling rate of the capture device + Sampling rate must be given as an integer for example 16000 for + setting 16Khz sampling rate + The sampling rate would be set in the device to the nearest available""" + self.pause_grabbing() + caps_str = "audio/x-raw-int,rate=%d,channels=1,depth=16" % (sr, ) + self.caps1.set_property("caps", gst.caps_from_string(caps_str)) + self.resume_grabbing() + + def get_sampling_rate(self): + """Gets the sampling rate of the capture device""" + return int(self.caps1.get_property("caps")[0]['rate']) + + def set_callable1(self, callable1): + """Sets the callable to the drawing function for giving the + data at the end of idle-add""" + self.callable1 = callable1 + + def start_grabbing(self): + """Called right at the start of the Activity""" + self.start_sound_device() + + def pause_grabbing(self): + """When Activity goes into background""" + self.save_state() + self.stop_sound_device() + + def resume_grabbing(self): + """When Activity becomes active after going to background""" + self.start_sound_device() + self.resume_state() + + def stop_grabbing(self): + """Not used ???""" + self.stop_sound_device() + self.set_handoff_signal(False) + + def _find_control(self, prefixes): + """Try to find a mixer control matching one of the prefixes. + + The control with the best match (smallest difference in length + between label and prefix) will be returned. If no match is found, + None is returned. + """ + def best_prefix(label, prefixes): + matches =\ + [len(label) - len(p) for p in prefixes if label.startswith(p)] + if not matches: + return None + + matches.sort() + return matches[0] + + controls = [] + for track in self._mixer.list_tracks(): + label = track.props.untranslated_label.lower() + diff = best_prefix(label, prefixes) + if diff is not None: + controls.append((track, diff)) + + controls.sort(key=lambda e: e[1]) + if controls: + debug_output('Found control: %s' % \ + (str(controls[0][0].props.untranslated_label)), + self.parent.running_sugar) + return controls[0][0] + + return None + + def save_state(self): + """Saves the state of all audio controls""" + self.master = self.get_master() + self.bias = self.get_bias() + self.dcmode = self.get_dc_mode() + self.capture_gain = self.get_capture_gain() + self.mic_boost = self.get_mic_boost() + + def resume_state(self): + """Put back all audio control settings from the saved state""" + self.set_master(self.master) + self.set_bias(self.bias) + self.set_dc_mode(self.dcmode) + self.set_capture_gain(self.capture_gain) + self.set_mic_boost(self.mic_boost) + + def _get_mute(self, control, name, default): + """Get mute status of a control""" + if not control: + return default + + value = bool(control.flags & gst.interfaces.MIXER_TRACK_MUTE) + debug_output('Getting %s (%s) mute status: %r' % (name, + control.props.untranslated_label, value), + self.parent.running_sugar) + return value + + def _set_mute(self, control, name, value): + """Mute a control""" + if not control: + return + + self._mixer.set_mute(control, value) + debug_output('Set mute for %s (%s) to %r' % (name, + control.props.untranslated_label, value), + self.parent.running_sugar) + + def _get_volume(self, control, name): + """Get volume of a control and convert to a scale of 0-100""" + if not control: + return 100 + + try: # sometimes get_volume does not return a tuple + hw_volume = self._mixer.get_volume(control)[0] + except IndexError: + return 100 + + min_vol = control.min_volume + max_vol = control.max_volume + percent = (hw_volume - min_vol)*100//(max_vol - min_vol) + return percent + + def _set_volume(self, control, name, value): + """Sets the level of a control on a scale of 0-100""" + if not control: + return + + # convert value to scale of control + min_vol = control.min_volume + max_vol = control.max_volume + if min_vol != max_vol: + hw_volume = value*(max_vol - min_vol)//100 + min_vol + self._mixer.set_volume(control, (hw_volume,)*control.num_channels) + + def amixer_set(self, control, state): + """ Direct call to amixer for old systems. """ + if state: + os.system("amixer set '%s' unmute" % (control)) + else: + os.system("amixer set '%s' mute" % (control)) + + def mute_master(self): + """Mutes the Master Control""" + if not self._hardwired and self.parent.hw != XO1: + self._set_mute(self._master_control, 'Master', True) + else: + self.amixer_set('Master', False) + + def unmute_master(self): + """Unmutes the Master Control""" + if not self._hardwired and self.parent.hw != XO1: + self._set_mute(self._master_control, 'Master', True) + else: + self.amixer_set('Master', True) + + def set_master(self, master_val): + """Sets the Master gain slider settings + master_val must be given as an integer between 0 and 100 indicating the + percentage of the slider to be set""" + if not self._hardwired: + self._set_volume(self._master_control, 'Master', master_val) + else: + os.system("amixer set Master " + str(master_val) + "%") + + def get_master(self): + """Gets the Master gain slider settings. The value returned is an + integer between 0-100 and is an indicative of the percentage 0 - 100%""" + if not self._hardwired: + return self._get_volume(self._master_control, 'master') + else: + p = str(subprocess.Popen(["amixer", "get", "Master"], + stdout=subprocess.PIPE).communicate()[0]) + p = p[find(p, "Front Left:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "%]")] + return int(p) + + def set_bias(self, bias_state=False): + """Enables / disables bias voltage.""" + if not self._hardwired and self.parent.hw != XO1: + if self._mic_bias_control is None: + return + # if not isinstance(self._mic_bias_control, + # gst.interfaces.MixerOptions): + if self._mic_bias_control not in self._mixer.list_tracks(): + return self._set_mute(self._mic_bias_control, 'Mic Bias', + not bias_state) + + # We assume that values are sorted from lowest (=off) to highest. + # Since they are mixed strings ("Off", "50%", etc.), we cannot + # easily ensure this by sorting with the default sort order. + try: + if bias_state: + # self._mixer.set_option(self._mic_bias_control, values[-1]) + self._mixer.set_volume(self._mic_bias_control, + self._mic_bias_control.max_volume) + else: + self._mixer.set_volume(self._mic_bias_control, + self._mic_bias_control.min_volume) + # self._mixer.set_option(self._mic_bias_control, values[0]) + except TypeError: + self._set_mute(self._mic_bias_control, 'Mic Bias', + not bias_state) + elif self._hardwired: + self.amixer_set('V_REFOUT Enable', bias_state) + else: + self.amixer_set('MIC Bias Enable', bias_state) + + def get_bias(self): + """Check whether bias voltage is enabled.""" + if not self._hardwired: + if self._mic_bias_control is None: + return False + if self._mic_bias_control not in self._mixer.list_tracks(): + return not self._get_mute(self._mic_bias_control, 'Mic Bias', + False) + current = self._mixer.get_volume(self._mic_bias_control) + # same ordering assertion as in set_bias() applies + # if current == values[0]: + if current == self._mic_bias_control.min_volume: + return False + return True + else: + p = str(subprocess.Popen(["amixer", "get", "'V_REFOUT Enable'"], + stdout=subprocess.PIPE).communicate()[0]) + p = p[find(p, "Mono:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "]")] + if p == "on": + return True + return False + + def set_dc_mode(self, dc_mode=False): + """Sets the DC Mode Enable control + pass False to mute and True to unmute""" + if not self._hardwired and self.parent.hw != XO1: + if self._dc_control is not None: + self._set_mute(self._dc_control, 'DC mode', not dc_mode) + else: + self.amixer_set('DC Mode Enable', dc_mode) + + def get_dc_mode(self): + """Returns the setting of DC Mode Enable control + i .e. True: Unmuted and False: Muted""" + if not self._hardwired: + if self._dc_control is not None: + return not self._get_mute(self._dc_control, 'DC mode', False) + else: + return False + else: + p = str(subprocess.Popen(["amixer", "get", "'DC Mode Enable'"], + stdout=subprocess.PIPE).communicate()[0]) + p = p[find(p, "Mono:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "]")] + if p == "on": + return True + else: + return False + + def set_mic_boost(self, mic_boost=False): + """Set Mic Boost. + True = +20dB, False = 0dB""" + if not self._hardwired: + if self._mic_boost_control is None: + return + if self._mic_boost_control not in self._mixer.list_tracks(): + return self._set_mute(self._mic_boost_control, 'Mic Boost', + mic_boost) + value = self._mixer.get_volume(self._mic_boost_control) + try: + if mic_boost: + self._mixer.set_volume(self._mic_boost_control, + self._mic_boost_control.max_volume) + else: + self._mixer.set_volume(self._mic_boost_control, + self._mic_boost_control.min_volume) + except TypeError: + return self._set_mute(self._mic_boost_control, 'Mic Boost', + not mic_boost) + else: + self.amixer_set('Mic Boost (+20dB)', mic_boost) + + def get_mic_boost(self): + """Return Mic Boost setting. + True = +20dB, False = 0dB""" + if not self._hardwired: + if self._mic_boost_control is None: + return False + if self._mic_boost_control not in self._mixer.list_tracks(): + return self._get_mute(self._mic_boost_control, 'Mic Boost', + False) + current = self._mixer.get_volume(self._mic_boost_control) + debug_output('current: %s' % (str(current)), + self.parent.running_sugar) + if current != self._mic_boost_control.min_volume: + return True + return False + else: + p = str(subprocess.Popen(["amixer", "get", "'Mic Boost (+20dB)'"], + stdout=subprocess.PIPE).communicate()[0]) + p = p[find(p, "Mono:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "]")] + if p == "on": + return True + else: + return False + + def set_capture_gain(self, capture_val): + """Sets the Capture gain slider settings + capture_val must be given as an integer between 0 and 100 indicating the + percentage of the slider to be set""" + if not self._hardwired and self.parent.hw != XO1: + if self._capture_control is not None: + self._set_volume(self._capture_control, 'Capture', capture_val) + else: + os.system("amixer set Capture " + str(capture_val) + "%") + + def get_capture_gain(self): + """Gets the Capture gain slider settings. The value returned is an + integer between 0-100 and is an indicative of the percentage 0 - 100%""" + if not self._hardwired: + if self._capture_control is not None: + return self._get_volume(self._capture_control, 'Capture') + else: + return 0 + else: + p = str(subprocess.Popen(["amixer", "get", "Capture"], + stdout=subprocess.PIPE).communicate()[0]) + p = p[find(p, "Front Left:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "%]")] + return int(p) + + def set_mic_gain(self, mic_val): + """Sets the MIC gain slider settings + mic_val must be given as an integer between 0 and 100 indicating the + percentage of the slider to be set""" + if not self._hardwired and self.parent.hw != XO1: + self._set_volume(self._mic_gain_control, 'Mic', mic_val) + else: + os.system("amixer set Mic " + str(mic_val) + "%") + + def get_mic_gain(self): + """Gets the MIC gain slider settings. The value returned is an + integer between 0-100 and is an indicative of the percentage 0 - 100%""" + if not self._hardwired: + return self._get_volume(self._mic_gain_control, 'Mic') + else: + p = str(subprocess.Popen(["amixer", "get", "Mic"], + stdout=subprocess.PIPE).communicate()[0]) + try: + p = p[find(p, "Mono:"):] + p = p[find(p, "[")+1:] + p = p[:find(p, "%]")] + return int(p) + except: + return(0) + + def set_sensor_type(self, sensor_type=SENSOR_AC_BIAS): + """Set the type of sensor you want to use. Set sensor_type according + to the following + SENSOR_AC_NO_BIAS - AC coupling with Bias Off --> Very rarely used. + Use when connecting a dynamic microphone externally + SENSOR_AC_BIAS - AC coupling with Bias On --> The default settings. + The internal MIC uses these + SENSOR_DC_NO_BIAS - DC coupling with Bias Off --> measuring voltage + output sensor. For example LM35 which gives output proportional + to temperature + SENSOR_DC_BIAS - DC coupling with Bias On --> measuing resistance. + """ + PARAMETERS = { + SENSOR_AC_NO_BIAS: (False, False, 50, True), + SENSOR_AC_BIAS: (False, True, 40, True), + SENSOR_DC_NO_BIAS: (True, False, 0, False), + SENSOR_DC_BIAS: (True, True, 0, False) + } + mode, bias, gain, boost = PARAMETERS[sensor_type] + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) + self._set_sensor_type(mode, bias, gain, boost) + + def _set_sensor_type(self, mode=None, bias=None, gain=None, boost=None): + """Helper to modify (some) of the sensor settings.""" + if mode is not None: + self.set_dc_mode(mode) + if self._dc_control is not None: + os.system("amixer get '%s'" %\ + (self._dc_control.props.untranslated_label)) + if bias is not None: + self.set_bias(bias) + if self._mic_bias_control is not None: + os.system("amixer get '%s'" %\ + (self._mic_bias_control.props.untranslated_label)) + if gain is not None: + self.set_capture_gain(gain) + if self._capture_control is not None: + os.system("amixer get '%s'" %\ + (self._capture_control.props.untranslated_label)) + if boost is not None: + self.set_mic_boost(boost) + if self._mic_boost_control is not None: + os.system("amixer get '%s'" %\ + (self._mic_boost_control.props.untranslated_label)) + + def on_activity_quit(self): + """When Activity quits""" + self.set_mic_boost(QUIT_MIC_BOOST) + self.set_dc_mode(QUIT_DC_MODE_ENABLE) + self.set_capture_gain(QUIT_CAPTURE_GAIN) + self.set_bias(QUIT_BIAS) + self.stop_sound_device() + + +class AudioGrab_XO1(AudioGrab): + """ Use default parameters for OLPC XO 1.0 laptop """ + pass + + +class AudioGrab_XO15(AudioGrab): + """ Override parameters for OLPC XO 1.5 laptop """ + def set_sensor_type(self, sensor_type=SENSOR_AC_BIAS): + """Helper to modify (some) of the sensor settings.""" + PARAMETERS = { + SENSOR_AC_NO_BIAS: (False, False, 80, True), + SENSOR_AC_BIAS: (False, True, 80, True), + SENSOR_DC_NO_BIAS: (True, False, 80, False), + SENSOR_DC_BIAS: (True, True, 90, False) + } + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) + mode, bias, gain, boost = PARAMETERS[sensor_type] + self._set_sensor_type(mode, bias, gain, boost) + + +class AudioGrab_Unknown(AudioGrab): + """ Override parameters for generic hardware """ + def set_sensor_type(self, sensor_type=SENSOR_AC_BIAS): + """Helper to modify (some) of the sensor settings.""" + PARAMETERS = { + SENSOR_AC_NO_BIAS: (None, False, 50, True), + SENSOR_AC_BIAS: (None, True, 40, True), + SENSOR_DC_NO_BIAS: (True, False, 80, False), + SENSOR_DC_BIAS: (True, True, 90, False) + } + debug_output('Set Sensor Type to %s' % (str(sensor_type)), + self.parent.running_sugar) + mode, bias, gain, boost = PARAMETERS[sensor_type] + self._set_sensor_type(mode, bias, gain, boost) diff --git a/plugins/audio_sensors/ringbuffer.py b/plugins/audio_sensors/ringbuffer.py new file mode 100644 index 0000000..2afb5c9 --- /dev/null +++ b/plugins/audio_sensors/ringbuffer.py @@ -0,0 +1,108 @@ +# Copyright (C) 2009, Benjamin Berg, Sebastian Berg +# Copyright (C) 2010, Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import numpy as np + + +class RingBuffer1d(object): + """This class implements an array being written in as a ring and that can + be read from continuously ending with the newest data or starting with the + oldest. It returns a numpy array copy of the data; + """ + + def __init__(self, length, dtype=None): + """Initialize the 1 dimensional ring buffer with the given lengths. + The initial values are all 0s + """ + self.offset = 0 + + self._data = np.zeros(length, dtype=dtype) + + self.stored = 0 + + def fill(self, number): + self._data.fill(number) + self.offset = 0 + + def append(self, data): + """Append to the ring buffer (and overwrite old data). If len(data) + is greater then the ring buffers length, the newest data takes + precedence. + """ + data = np.asarray(data) + + if len(self._data) == 0: + return + + if len(data) >= len(self._data): + self._data[:] = data[-len(self._data):] + self.offset = 0 + self.stored = len(self._data) + + elif len(self._data) - self.offset >= len(data): + self._data[self.offset: self.offset + len(data)] = data + self.offset = self.offset + len(data) + self.stored += len(data) + else: + self._data[self.offset:] = data[:len(self._data) - self.offset] + self._data[:len(data) - (len(self._data) - self.offset)] = \ + data[-len(data) + (len(self._data) - self.offset):] + self.offset = len(data) - (len(self._data) - self.offset) + self.stored += len(data) + + if len(self._data) <= self.stored: + self.read = self._read + + def read(self, number=None, step=1): + """Read the ring Buffer. Number can be positive or negative. + Positive values will give the latest information, negative values will + give the newest added information from the buffer. (in normal order) + + Before the buffer is filled once: This returns just None + """ + return np.array([]) + + def _read(self, number=None, step=1): + """Read the ring Buffer. Number can be positive or negative. + Positive values will give the latest information, negative values will + give the newest added information from the buffer. (in normal order) + """ + if number == None: + number = len(self._data) // step + + number *= step + assert abs(number) <= len(self._data), \ + 'Number to read*step must be smaller then length' + + if number < 0: + if abs(number) <= self.offset: + return self._data[self.offset + number:self.offset:step] + + spam = (self.offset - 1) % step + + return np.concatenate( + (self._data[step - spam - 1 + self.offset + number::step], + self._data[spam:self.offset:step])) + + if number - (len(self._data) - self.offset) > 0: + spam = ((self.offset + number) - self.offset - 1) % step + return np.concatenate( + (self._data[self.offset:self.offset + number:step], + self._data[spam:number - + (len(self._data) - self.offset):step])) + + return self._data[self.offset:self.offset + number:step].copy() diff --git a/plugins/camera_sensor/__init__.py b/plugins/camera_sensor/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/camera_sensor/__init__.py diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py new file mode 100644 index 0000000..dd2e6ef --- /dev/null +++ b/plugins/camera_sensor/camera_sensor.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gst +import gtk +from fcntl import ioctl + +from plugins.camera_sensor.tacamera import Camera +from plugins.camera_sensor.v4l2 import v4l2_control, V4L2_CID_AUTOGAIN, \ + VIDIOC_G_CTRL, VIDIOC_S_CTRL + +from plugins.plugin import Plugin + +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import media_blocks_dictionary, primitive_dictionary +from TurtleArt.tautils import get_path, debug_output + +import logging +_logger = logging.getLogger('turtleart-activity camera plugin') + + +class Camera_sensor(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + v4l2src = gst.element_factory_make('v4l2src') + if v4l2src.props.device_name is not None: + + if self._parent.running_sugar: + self._imagepath = get_path(self._parent.activity, + 'data/turtlepic.png') + else: + self._imagepath = '/tmp/turtlepic.png' + self._camera = Camera(self._imagepath) + + self._status = True + + def setup(self): + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + # set up camera-specific blocks + if self._status: + primitive_dictionary['luminance'] = self.prim_read_camera + palette.add_block('luminance', + style='box-style', + label=_('brightness'), + help_string=_('light level detected by camera'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('luminance', 0, + lambda self: primitive_dictionary['luminance'](True)) + + # Depreciated block + primitive_dictionary['read_camera'] = self.prim_read_camera + palette.add_block('read_camera', + hidden=True, + style='box-style', + label=_('brightness'), + help_string=_('Average RGB color from camera \ +is pushed to the stack'), + value_block=True, + prim_name='luminance') + self._parent.lc.def_prim('read_camera', 0, + lambda self: primitive_dictionary['read_camera'](True)) + + media_blocks_dictionary['camera'] = self.prim_take_picture + palette.add_block('camera', + style='box-style-media', + label=' ', + default='CAMERA', + help_string=_('camera output'), + content_block=True) + + def stop(self): + # This gets called by the stop button + if self._status: + self._camera.stop_camera_input() + + def _status_report(self): + debug_output('Reporting camera status: %s' % (str(self._status))) + return self._status + + # Block primitives used in talogo + + def prim_take_picture(self): + if self._status: + ''' method called by media block ''' + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + self._parent.lc.filepath = self._imagepath + + def prim_read_camera(self, luminance_only=False): + """ Read average pixel from camera and push b, g, r to the stack """ + pixbuf = None + array = None + w = int((self._parent.canvas.width * self._parent.lc.scale) / 100.) + h = int((self._parent.canvas.height * self._parent.lc.scale) / 100.) + if w > 0 and h > 0 and self._status: + try: + self._video_capture_device = open('/dev/video0', 'rw') + except: + self._video_capture_device = None + _logger.debug('video capture device not available') + + if self._video_capture_device is not None: + self._ag_control = v4l2_control(V4L2_CID_AUTOGAIN) + try: + ioctl(self._video_capture_device, VIDIOC_G_CTRL, + self._ag_control) + self._ag_control.value = 0 # disable AUTOGAIN + ioctl(self._video_capture_device, VIDIOC_S_CTRL, + self._ag_control) + except: + _logger.debug('AUTOGAIN control not available') + pass + + if self._video_capture_device is not None: + self._video_capture_device.close() + + self._camera.save_camera_input_to_file() + self._camera.stop_camera_input() + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self._imagepath, + w, h) + try: + array = pixbuf.get_pixels() + except: + array = None + + if array is not None: + length = len(array) / 3 + r, g, b, i = 0, 0, 0, 0 + for j in range(length): + r += ord(array[i]) + i += 1 + g += ord(array[i]) + i += 1 + b += ord(array[i]) + i += 1 + if luminance_only: + lum = int((r * 0.3 + g * 0.6 + b * 0.1) / length) + self._parent.lc.update_label_value('luminance', lum) + return lum + else: + self._parent.lc.heap.append(int((b / length))) + self._parent.lc.heap.append(int((g / length))) + self._parent.lc.heap.append(int((r / length))) + else: + if luminance_only: + return -1 + else: + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) + self._parent.lc.heap.append(-1) diff --git a/plugins/camera_sensor/tacamera.py b/plugins/camera_sensor/tacamera.py new file mode 100644 index 0000000..2177288 --- /dev/null +++ b/plugins/camera_sensor/tacamera.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2010, Walter Bender +#Copyright (c) 2010, Tony Forster + +#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, time + +GST_PIPE = ['v4l2src', 'ffmpegcolorspace', 'pngenc'] + +class Camera(): + """ A class for representing the video camera """ + + def __init__(self, imagepath): + GST_PIPE.append('filesink location=%s' % imagepath) + self.pipe = gst.parse_launch('!'.join(GST_PIPE)) + self.bus = self.pipe.get_bus() + + def save_camera_input_to_file(self): + """ Grab a frame from the camera """ + self.pipe.set_state(gst.STATE_PLAYING) + self.bus.poll(gst.MESSAGE_EOS, -1) + + def stop_camera_input(self): + self.pipe.set_state(gst.STATE_NULL) + diff --git a/plugins/camera_sensor/v4l2.py b/plugins/camera_sensor/v4l2.py new file mode 100644 index 0000000..9c052fd --- /dev/null +++ b/plugins/camera_sensor/v4l2.py @@ -0,0 +1,1914 @@ +# Python bindings for the v4l2 userspace api + +# Copyright (C) 1999-2009 the contributors + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# Alternatively you can redistribute this file under the terms of the +# BSD license as stated below: + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# 3. The names of its contributors may not be used to endorse or promote +# products derived from this software without specific prior written +# permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +Python bindings for the v4l2 userspace api in Linux 2.6.34 +""" + +# see linux/videodev2.h + +import ctypes + + +_IOC_NRBITS = 8 +_IOC_TYPEBITS = 8 +_IOC_SIZEBITS = 14 +_IOC_DIRBITS = 2 + +_IOC_NRSHIFT = 0 +_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS +_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS +_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS + +_IOC_NONE = 0 +_IOC_WRITE = 1 +_IOC_READ = 2 + + +def _IOC(dir_, type_, nr, size): + return ( + ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value | + ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value | + ctypes.c_int32(nr << _IOC_NRSHIFT).value | + ctypes.c_int32(size << _IOC_SIZESHIFT).value) + + +def _IOC_TYPECHECK(t): + return ctypes.sizeof(t) + + +def _IO(type_, nr): + return _IOC(_IOC_NONE, type_, nr, 0) + + +def _IOW(type_, nr, size): + return _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK(size)) + + +def _IOR(type_, nr, size): + return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size)) + + +def _IOWR(type_, nr, size): + return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size)) + + +# +# type alias +# + +enum = ctypes.c_uint +c_int = ctypes.c_int + + +# +# time +# + +class timeval(ctypes.Structure): + _fields_ = [ + ('secs', ctypes.c_long), + ('usecs', ctypes.c_long), + ] + + +# +# v4l2 +# + + +VIDEO_MAX_FRAME = 32 + + +VID_TYPE_CAPTURE = 1 +VID_TYPE_TUNER = 2 +VID_TYPE_TELETEXT = 4 +VID_TYPE_OVERLAY = 8 +VID_TYPE_CHROMAKEY = 16 +VID_TYPE_CLIPPING = 32 +VID_TYPE_FRAMERAM = 64 +VID_TYPE_SCALES = 128 +VID_TYPE_MONOCHROME = 256 +VID_TYPE_SUBCAPTURE = 512 +VID_TYPE_MPEG_DECODER = 1024 +VID_TYPE_MPEG_ENCODER = 2048 +VID_TYPE_MJPEG_DECODER = 4096 +VID_TYPE_MJPEG_ENCODER = 8192 + + +def v4l2_fourcc(a, b, c, d): + return ord(a) | (ord(b) << 8) | (ord(c) << 16) | (ord(d) << 24) + + +v4l2_field = enum +( + V4L2_FIELD_ANY, + V4L2_FIELD_NONE, + V4L2_FIELD_TOP, + V4L2_FIELD_BOTTOM, + V4L2_FIELD_INTERLACED, + V4L2_FIELD_SEQ_TB, + V4L2_FIELD_SEQ_BT, + V4L2_FIELD_ALTERNATE, + V4L2_FIELD_INTERLACED_TB, + V4L2_FIELD_INTERLACED_BT, +) = range(10) + + +def V4L2_FIELD_HAS_TOP(field): + return ( + field == V4L2_FIELD_TOP or + field == V4L2_FIELD_INTERLACED or + field == V4L2_FIELD_INTERLACED_TB or + field == V4L2_FIELD_INTERLACED_BT or + field == V4L2_FIELD_SEQ_TB or + field == V4L2_FIELD_SEQ_BT) + + +def V4L2_FIELD_HAS_BOTTOM(field): + return ( + field == V4L2_FIELD_BOTTOM or + field == V4L2_FIELD_INTERLACED or + field == V4L2_FIELD_INTERLACED_TB or + field == V4L2_FIELD_INTERLACED_BT or + field == V4L2_FIELD_SEQ_TB or + field == V4L2_FIELD_SEQ_BT) + + +def V4L2_FIELD_HAS_BOTH(field): + return ( + field == V4L2_FIELD_INTERLACED or + field == V4L2_FIELD_INTERLACED_TB or + field == V4L2_FIELD_INTERLACED_BT or + field == V4L2_FIELD_SEQ_TB or + field == V4L2_FIELD_SEQ_BT) + + +v4l2_buf_type = enum +( + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_BUF_TYPE_VIDEO_OUTPUT, + V4L2_BUF_TYPE_VIDEO_OVERLAY, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_BUF_TYPE_VBI_OUTPUT, + V4L2_BUF_TYPE_SLICED_VBI_CAPTURE, + V4L2_BUF_TYPE_SLICED_VBI_OUTPUT, + V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY, + V4L2_BUF_TYPE_PRIVATE, +) = range(1, 9) + [0x80] + + +v4l2_ctrl_type = enum +( + V4L2_CTRL_TYPE_INTEGER, + V4L2_CTRL_TYPE_BOOLEAN, + V4L2_CTRL_TYPE_MENU, + V4L2_CTRL_TYPE_BUTTON, + V4L2_CTRL_TYPE_INTEGER64, + V4L2_CTRL_TYPE_CTRL_CLASS, + V4L2_CTRL_TYPE_STRING, +) = range(1, 8) + + +v4l2_tuner_type = enum +( + V4L2_TUNER_RADIO, + V4L2_TUNER_ANALOG_TV, + V4L2_TUNER_DIGITAL_TV, +) = range(1, 4) + + +v4l2_memory = enum +( + V4L2_MEMORY_MMAP, + V4L2_MEMORY_USERPTR, + V4L2_MEMORY_OVERLAY, +) = range(1, 4) + + +v4l2_colorspace = enum +( + V4L2_COLORSPACE_SMPTE170M, + V4L2_COLORSPACE_SMPTE240M, + V4L2_COLORSPACE_REC709, + V4L2_COLORSPACE_BT878, + V4L2_COLORSPACE_470_SYSTEM_M, + V4L2_COLORSPACE_470_SYSTEM_BG, + V4L2_COLORSPACE_JPEG, + V4L2_COLORSPACE_SRGB, +) = range(1, 9) + + +v4l2_priority = enum +( + V4L2_PRIORITY_UNSET, + V4L2_PRIORITY_BACKGROUND, + V4L2_PRIORITY_INTERACTIVE, + V4L2_PRIORITY_RECORD, + V4L2_PRIORITY_DEFAULT, +) = range(0, 4) + [2] + + +class v4l2_rect(ctypes.Structure): + _fields_ = [ + ('left', ctypes.c_int32), + ('top', ctypes.c_int32), + ('width', ctypes.c_int32), + ('height', ctypes.c_int32), + ] + + +class v4l2_fract(ctypes.Structure): + _fields_ = [ + ('numerator', ctypes.c_uint32), + ('denominator', ctypes.c_uint32), + ] + + +# +# Driver capabilities +# + +class v4l2_capability(ctypes.Structure): + _fields_ = [ + ('driver', ctypes.c_char * 16), + ('card', ctypes.c_char * 32), + ('bus_info', ctypes.c_char * 32), + ('version', ctypes.c_uint32), + ('capabilities', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +# +# Values for 'capabilities' field +# + +V4L2_CAP_VIDEO_CAPTURE = 0x00000001 +V4L2_CAP_VIDEO_OUTPUT = 0x00000002 +V4L2_CAP_VIDEO_OVERLAY = 0x00000004 +V4L2_CAP_VBI_CAPTURE = 0x00000010 +V4L2_CAP_VBI_OUTPUT = 0x00000020 +V4L2_CAP_SLICED_VBI_CAPTURE = 0x00000040 +V4L2_CAP_SLICED_VBI_OUTPUT = 0x00000080 +V4L2_CAP_RDS_CAPTURE = 0x00000100 +V4L2_CAP_VIDEO_OUTPUT_OVERLAY = 0x00000200 +V4L2_CAP_HW_FREQ_SEEK = 0x00000400 +V4L2_CAP_RDS_OUTPUT = 0x00000800 + +V4L2_CAP_TUNER = 0x00010000 +V4L2_CAP_AUDIO = 0x00020000 +V4L2_CAP_RADIO = 0x00040000 +V4L2_CAP_MODULATOR = 0x00080000 + +V4L2_CAP_READWRITE = 0x01000000 +V4L2_CAP_ASYNCIO = 0x02000000 +V4L2_CAP_STREAMING = 0x04000000 + + +# +# Video image format +# + +class v4l2_pix_format(ctypes.Structure): + _fields_ = [ + ('width', ctypes.c_uint32), + ('height', ctypes.c_uint32), + ('pixelformat', ctypes.c_uint32), + ('field', v4l2_field), + ('bytesperline', ctypes.c_uint32), + ('sizeimage', ctypes.c_uint32), + ('colorspace', v4l2_colorspace), + ('priv', ctypes.c_uint32), + ] + +# RGB formats +V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1') +V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4') +V4L2_PIX_FMT_RGB555 = v4l2_fourcc('R', 'G', 'B', 'O') +V4L2_PIX_FMT_RGB565 = v4l2_fourcc('R', 'G', 'B', 'P') +V4L2_PIX_FMT_RGB555X = v4l2_fourcc('R', 'G', 'B', 'Q') +V4L2_PIX_FMT_RGB565X = v4l2_fourcc('R', 'G', 'B', 'R') +V4L2_PIX_FMT_BGR24 = v4l2_fourcc('B', 'G', 'R', '3') +V4L2_PIX_FMT_RGB24 = v4l2_fourcc('R', 'G', 'B', '3') +V4L2_PIX_FMT_BGR32 = v4l2_fourcc('B', 'G', 'R', '4') +V4L2_PIX_FMT_RGB32 = v4l2_fourcc('R', 'G', 'B', '4') + +# Grey formats +V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y') +V4L2_PIX_FMT_Y10 = v4l2_fourcc('Y', '1', '0', ' ') +V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ') + +# Palette formats +V4L2_PIX_FMT_PAL8 = v4l2_fourcc('P', 'A', 'L', '8') + +# Luminance+Chrominance formats +V4L2_PIX_FMT_YVU410 = v4l2_fourcc('Y', 'V', 'U', '9') +V4L2_PIX_FMT_YVU420 = v4l2_fourcc('Y', 'V', '1', '2') +V4L2_PIX_FMT_YUYV = v4l2_fourcc('Y', 'U', 'Y', 'V') +V4L2_PIX_FMT_YYUV = v4l2_fourcc('Y', 'Y', 'U', 'V') +V4L2_PIX_FMT_YVYU = v4l2_fourcc('Y', 'V', 'Y', 'U') +V4L2_PIX_FMT_UYVY = v4l2_fourcc('U', 'Y', 'V', 'Y') +V4L2_PIX_FMT_VYUY = v4l2_fourcc('V', 'Y', 'U', 'Y') +V4L2_PIX_FMT_YUV422P = v4l2_fourcc('4', '2', '2', 'P') +V4L2_PIX_FMT_YUV411P = v4l2_fourcc('4', '1', '1', 'P') +V4L2_PIX_FMT_Y41P = v4l2_fourcc('Y', '4', '1', 'P') +V4L2_PIX_FMT_YUV444 = v4l2_fourcc('Y', '4', '4', '4') +V4L2_PIX_FMT_YUV555 = v4l2_fourcc('Y', 'U', 'V', 'O') +V4L2_PIX_FMT_YUV565 = v4l2_fourcc('Y', 'U', 'V', 'P') +V4L2_PIX_FMT_YUV32 = v4l2_fourcc('Y', 'U', 'V', '4') +V4L2_PIX_FMT_YUV410 = v4l2_fourcc('Y', 'U', 'V', '9') +V4L2_PIX_FMT_YUV420 = v4l2_fourcc('Y', 'U', '1', '2') +V4L2_PIX_FMT_HI240 = v4l2_fourcc('H', 'I', '2', '4') +V4L2_PIX_FMT_HM12 = v4l2_fourcc('H', 'M', '1', '2') + +# two planes -- one Y, one Cr + Cb interleaved +V4L2_PIX_FMT_NV12 = v4l2_fourcc('N', 'V', '1', '2') +V4L2_PIX_FMT_NV21 = v4l2_fourcc('N', 'V', '2', '1') +V4L2_PIX_FMT_NV16 = v4l2_fourcc('N', 'V', '1', '6') +V4L2_PIX_FMT_NV61 = v4l2_fourcc('N', 'V', '6', '1') + +# Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm +V4L2_PIX_FMT_SBGGR8 = v4l2_fourcc('B', 'A', '8', '1') +V4L2_PIX_FMT_SGBRG8 = v4l2_fourcc('G', 'B', 'R', 'G') +V4L2_PIX_FMT_SGRBG8 = v4l2_fourcc('G', 'R', 'B', 'G') +V4L2_PIX_FMT_SRGGB8 = v4l2_fourcc('R', 'G', 'G', 'B') +V4L2_PIX_FMT_SBGGR10 = v4l2_fourcc('B', 'G', '1', '0') +V4L2_PIX_FMT_SGBRG10 = v4l2_fourcc('G', 'B', '1', '0') +V4L2_PIX_FMT_SGRBG10 = v4l2_fourcc('B', 'A', '1', '0') +V4L2_PIX_FMT_SRGGB10 = v4l2_fourcc('R', 'G', '1', '0') +V4L2_PIX_FMT_SGRBG10DPCM8 = v4l2_fourcc('B', 'D', '1', '0') +V4L2_PIX_FMT_SBGGR16 = v4l2_fourcc('B', 'Y', 'R', '2') + +# compressed formats +V4L2_PIX_FMT_MJPEG = v4l2_fourcc('M', 'J', 'P', 'G') +V4L2_PIX_FMT_JPEG = v4l2_fourcc('J', 'P', 'E', 'G') +V4L2_PIX_FMT_DV = v4l2_fourcc('d', 'v', 's', 'd') +V4L2_PIX_FMT_MPEG = v4l2_fourcc('M', 'P', 'E', 'G') + +# Vendor-specific formats +V4L2_PIX_FMT_CPIA1 = v4l2_fourcc('C', 'P', 'I', 'A') +V4L2_PIX_FMT_WNVA = v4l2_fourcc('W', 'N', 'V', 'A') +V4L2_PIX_FMT_SN9C10X = v4l2_fourcc('S', '9', '1', '0') +V4L2_PIX_FMT_SN9C20X_I420 = v4l2_fourcc('S', '9', '2', '0') +V4L2_PIX_FMT_PWC1 = v4l2_fourcc('P', 'W', 'C', '1') +V4L2_PIX_FMT_PWC2 = v4l2_fourcc('P', 'W', 'C', '2') +V4L2_PIX_FMT_ET61X251 = v4l2_fourcc('E', '6', '2', '5') +V4L2_PIX_FMT_SPCA501 = v4l2_fourcc('S', '5', '0', '1') +V4L2_PIX_FMT_SPCA505 = v4l2_fourcc('S', '5', '0', '5') +V4L2_PIX_FMT_SPCA508 = v4l2_fourcc('S', '5', '0', '8') +V4L2_PIX_FMT_SPCA561 = v4l2_fourcc('S', '5', '6', '1') +V4L2_PIX_FMT_PAC207 = v4l2_fourcc('P', '2', '0', '7') +V4L2_PIX_FMT_MR97310A = v4l2_fourcc('M', '3', '1', '0') +V4L2_PIX_FMT_SN9C2028 = v4l2_fourcc('S', 'O', 'N', 'X') +V4L2_PIX_FMT_SQ905C = v4l2_fourcc('9', '0', '5', 'C') +V4L2_PIX_FMT_PJPG = v4l2_fourcc('P', 'J', 'P', 'G') +V4L2_PIX_FMT_OV511 = v4l2_fourcc('O', '5', '1', '1') +V4L2_PIX_FMT_OV518 = v4l2_fourcc('O', '5', '1', '8') +V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0') + + +# +# Format enumeration +# + +class v4l2_fmtdesc(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('type', ctypes.c_int), + ('flags', ctypes.c_uint32), + ('description', ctypes.c_char * 32), + ('pixelformat', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + +V4L2_FMT_FLAG_COMPRESSED = 0x0001 +V4L2_FMT_FLAG_EMULATED = 0x0002 + + +# +# Experimental frame size and frame rate enumeration +# + +v4l2_frmsizetypes = enum +( + V4L2_FRMSIZE_TYPE_DISCRETE, + V4L2_FRMSIZE_TYPE_CONTINUOUS, + V4L2_FRMSIZE_TYPE_STEPWISE, +) = range(1, 4) + + +class v4l2_frmsize_discrete(ctypes.Structure): + _fields_ = [ + ('width', ctypes.c_uint32), + ('height', ctypes.c_uint32), + ] + + +class v4l2_frmsize_stepwise(ctypes.Structure): + _fields_ = [ + ('min_width', ctypes.c_uint32), + ('min_height', ctypes.c_uint32), + ('step_width', ctypes.c_uint32), + ('min_height', ctypes.c_uint32), + ('max_height', ctypes.c_uint32), + ('step_height', ctypes.c_uint32), + ] + + +class v4l2_frmsizeenum(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('discrete', v4l2_frmsize_discrete), + ('stepwise', v4l2_frmsize_stepwise), + ] + + _fields_ = [ + ('index', ctypes.c_uint32), + ('pixel_format', ctypes.c_uint32), + ('type', ctypes.c_uint32), + ('_u', _u), + ('reserved', ctypes.c_uint32 * 2) + ] + + _anonymous_ = ('_u',) + + +# +# Frame rate enumeration +# + +v4l2_frmivaltypes = enum +( + V4L2_FRMIVAL_TYPE_DISCRETE, + V4L2_FRMIVAL_TYPE_CONTINUOUS, + V4L2_FRMIVAL_TYPE_STEPWISE, +) = range(1, 4) + + +class v4l2_frmival_stepwise(ctypes.Structure): + _fields_ = [ + ('min', v4l2_fract), + ('max', v4l2_fract), + ('step', v4l2_fract), + ] + + +class v4l2_frmivalenum(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('discrete', v4l2_fract), + ('stepwise', v4l2_frmival_stepwise), + ] + + _fields_ = [ + ('index', ctypes.c_uint32), + ('pixel_format', ctypes.c_uint32), + ('width', ctypes.c_uint32), + ('height', ctypes.c_uint32), + ('type', ctypes.c_uint32), + ('_u', _u), + ('reserved', ctypes.c_uint32 * 2), + ] + + _anonymous_ = ('_u',) + + +# +# Timecode +# + +class v4l2_timecode(ctypes.Structure): + _fields_ = [ + ('type', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('frames', ctypes.c_uint8), + ('seconds', ctypes.c_uint8), + ('minutes', ctypes.c_uint8), + ('hours', ctypes.c_uint8), + ('userbits', ctypes.c_uint8 * 4), + ] + + +V4L2_TC_TYPE_24FPS = 1 +V4L2_TC_TYPE_25FPS = 2 +V4L2_TC_TYPE_30FPS = 3 +V4L2_TC_TYPE_50FPS = 4 +V4L2_TC_TYPE_60FPS = 5 + +V4L2_TC_FLAG_DROPFRAME = 0x0001 +V4L2_TC_FLAG_COLORFRAME = 0x0002 +V4L2_TC_USERBITS_field = 0x000C +V4L2_TC_USERBITS_USERDEFINED = 0x0000 +V4L2_TC_USERBITS_8BITCHARS = 0x0008 + + +class v4l2_jpegcompression(ctypes.Structure): + _fields_ = [ + ('quality', ctypes.c_int), + ('APPn', ctypes.c_int), + ('APP_len', ctypes.c_int), + ('APP_data', ctypes.c_char * 60), + ('COM_len', ctypes.c_int), + ('COM_data', ctypes.c_char * 60), + ('jpeg_markers', ctypes.c_uint32), + ] + + +V4L2_JPEG_MARKER_DHT = 1 << 3 +V4L2_JPEG_MARKER_DQT = 1 << 4 +V4L2_JPEG_MARKER_DRI = 1 << 5 +V4L2_JPEG_MARKER_COM = 1 << 6 +V4L2_JPEG_MARKER_APP = 1 << 7 + + +# +# Memory-mapping buffers +# + +class v4l2_requestbuffers(ctypes.Structure): + _fields_ = [ + ('count', ctypes.c_uint32), + ('type', v4l2_buf_type), + ('memory', v4l2_memory), + ('reserved', ctypes.c_uint32 * 2), + ] + + +class v4l2_buffer(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('offset', ctypes.c_uint32), + ('userptr', ctypes.c_ulong), + ] + + _fields_ = [ + ('index', ctypes.c_uint32), + ('type', v4l2_buf_type), + ('bytesused', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('field', v4l2_field), + ('timestamp', timeval), + ('timecode', v4l2_timecode), + ('sequence', ctypes.c_uint32), + ('memory', v4l2_memory), + ('m', _u), + ('length', ctypes.c_uint32), + ('input', ctypes.c_uint32), + ('reserved', ctypes.c_uint32), + ] + + +V4L2_BUF_FLAG_MAPPED = 0x0001 +V4L2_BUF_FLAG_QUEUED = 0x0002 +V4L2_BUF_FLAG_DONE = 0x0004 +V4L2_BUF_FLAG_KEYFRAME = 0x0008 +V4L2_BUF_FLAG_PFRAME = 0x0010 +V4L2_BUF_FLAG_BFRAME = 0x0020 +V4L2_BUF_FLAG_TIMECODE = 0x0100 +V4L2_BUF_FLAG_INPUT = 0x0200 + + +# +# Overlay preview +# + +class v4l2_framebuffer(ctypes.Structure): + _fields_ = [ + ('capability', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('base', ctypes.c_void_p), + ('fmt', v4l2_pix_format), + ] + +V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001 +V4L2_FBUF_CAP_CHROMAKEY = 0x0002 +V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004 +V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008 +V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010 +V4L2_FBUF_CAP_GLOBAL_ALPHA = 0x0020 +V4L2_FBUF_CAP_LOCAL_INV_ALPHA = 0x0040 +V4L2_FBUF_CAP_SRC_CHROMAKEY = 0x0080 + +V4L2_FBUF_FLAG_PRIMARY = 0x0001 +V4L2_FBUF_FLAG_OVERLAY = 0x0002 +V4L2_FBUF_FLAG_CHROMAKEY = 0x0004 +V4L2_FBUF_FLAG_LOCAL_ALPHA = 0x0008 +V4L2_FBUF_FLAG_GLOBAL_ALPHA = 0x0010 +V4L2_FBUF_FLAG_LOCAL_INV_ALPHA = 0x0020 +V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040 + + +class v4l2_clip(ctypes.Structure): + pass +v4l2_clip._fields_ = [ + ('c', v4l2_rect), + ('next', ctypes.POINTER(v4l2_clip)), +] + + +class v4l2_window(ctypes.Structure): + _fields_ = [ + ('w', v4l2_rect), + ('field', v4l2_field), + ('chromakey', ctypes.c_uint32), + ('clips', ctypes.POINTER(v4l2_clip)), + ('clipcount', ctypes.c_uint32), + ('bitmap', ctypes.c_void_p), + ('global_alpha', ctypes.c_uint8), + ] + + +# +# Capture parameters +# + +class v4l2_captureparm(ctypes.Structure): + _fields_ = [ + ('capability', ctypes.c_uint32), + ('capturemode', ctypes.c_uint32), + ('timeperframe', v4l2_fract), + ('extendedmode', ctypes.c_uint32), + ('readbuffers', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +V4L2_MODE_HIGHQUALITY = 0x0001 +V4L2_CAP_TIMEPERFRAME = 0x1000 + + +class v4l2_outputparm(ctypes.Structure): + _fields_ = [ + ('capability', ctypes.c_uint32), + ('outputmode', ctypes.c_uint32), + ('timeperframe', v4l2_fract), + ('extendedmode', ctypes.c_uint32), + ('writebuffers', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +# +# Input image cropping +# + +class v4l2_cropcap(ctypes.Structure): + _fields_ = [ + ('type', v4l2_buf_type), + ('bounds', v4l2_rect), + ('defrect', v4l2_rect), + ('pixelaspect', v4l2_fract), + ] + + +class v4l2_crop(ctypes.Structure): + _fields_ = [ + ('type', ctypes.c_int), + ('c', v4l2_rect), + ] + + +# +# Analog video standard +# + +v4l2_std_id = ctypes.c_uint64 + + +V4L2_STD_PAL_B = 0x00000001 +V4L2_STD_PAL_B1 = 0x00000002 +V4L2_STD_PAL_G = 0x00000004 +V4L2_STD_PAL_H = 0x00000008 +V4L2_STD_PAL_I = 0x00000010 +V4L2_STD_PAL_D = 0x00000020 +V4L2_STD_PAL_D1 = 0x00000040 +V4L2_STD_PAL_K = 0x00000080 + +V4L2_STD_PAL_M = 0x00000100 +V4L2_STD_PAL_N = 0x00000200 +V4L2_STD_PAL_Nc = 0x00000400 +V4L2_STD_PAL_60 = 0x00000800 + +V4L2_STD_NTSC_M = 0x00001000 +V4L2_STD_NTSC_M_JP = 0x00002000 +V4L2_STD_NTSC_443 = 0x00004000 +V4L2_STD_NTSC_M_KR = 0x00008000 + +V4L2_STD_SECAM_B = 0x00010000 +V4L2_STD_SECAM_D = 0x00020000 +V4L2_STD_SECAM_G = 0x00040000 +V4L2_STD_SECAM_H = 0x00080000 +V4L2_STD_SECAM_K = 0x00100000 +V4L2_STD_SECAM_K1 = 0x00200000 +V4L2_STD_SECAM_L = 0x00400000 +V4L2_STD_SECAM_LC = 0x00800000 + +V4L2_STD_ATSC_8_VSB = 0x01000000 +V4L2_STD_ATSC_16_VSB = 0x02000000 + + +# some common needed stuff +V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G) +V4L2_STD_PAL_DK = (V4L2_STD_PAL_D | V4L2_STD_PAL_D1 | V4L2_STD_PAL_K) +V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H | V4L2_STD_PAL_I) +V4L2_STD_NTSC = (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR) +V4L2_STD_SECAM_DK = (V4L2_STD_SECAM_D | V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1) +V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC) + +V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC | V4L2_STD_NTSC_443) +V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM) +V4L2_STD_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB) + +V4L2_STD_UNKNOWN = 0 +V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50) + +# some merged standards +V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC) +V4L2_STD_B = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_SECAM_B) +V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H|V4L2_STD_SECAM_G | V4L2_STD_SECAM_H) +V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK) + + +class v4l2_standard(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('id', v4l2_std_id), + ('name', ctypes.c_char * 24), + ('frameperiod', v4l2_fract), + ('framelines', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +# +# Video timings dv preset +# + +class v4l2_dv_preset(ctypes.Structure): + _fields_ = [ + ('preset', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4) + ] + + +# +# DV preset enumeration +# + +class v4l2_dv_enum_preset(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('preset', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('width', ctypes.c_uint32), + ('height', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + +# +# DV preset values +# + +V4L2_DV_INVALID = 0 +V4L2_DV_480P59_94 = 1 +V4L2_DV_576P50 = 2 +V4L2_DV_720P24 = 3 +V4L2_DV_720P25 = 4 +V4L2_DV_720P30 = 5 +V4L2_DV_720P50 = 6 +V4L2_DV_720P59_94 = 7 +V4L2_DV_720P60 = 8 +V4L2_DV_1080I29_97 = 9 +V4L2_DV_1080I30 = 10 +V4L2_DV_1080I25 = 11 +V4L2_DV_1080I50 = 12 +V4L2_DV_1080I60 = 13 +V4L2_DV_1080P24 = 14 +V4L2_DV_1080P25 = 15 +V4L2_DV_1080P30 = 16 +V4L2_DV_1080P50 = 17 +V4L2_DV_1080P60 = 18 + + +# +# DV BT timings +# + +class v4l2_bt_timings(ctypes.Structure): + _fields_ = [ + ('width', ctypes.c_uint32), + ('height', ctypes.c_uint32), + ('interlaced', ctypes.c_uint32), + ('polarities', ctypes.c_uint32), + ('pixelclock', ctypes.c_uint64), + ('hfrontporch', ctypes.c_uint32), + ('hsync', ctypes.c_uint32), + ('hbackporch', ctypes.c_uint32), + ('vfrontporch', ctypes.c_uint32), + ('vsync', ctypes.c_uint32), + ('vbackporch', ctypes.c_uint32), + ('il_vfrontporch', ctypes.c_uint32), + ('il_vsync', ctypes.c_uint32), + ('il_vbackporch', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 16), + ] + + _pack_ = True + +# Interlaced or progressive format +V4L2_DV_PROGRESSIVE = 0 +V4L2_DV_INTERLACED = 1 + +# Polarities. If bit is not set, it is assumed to be negative polarity +V4L2_DV_VSYNC_POS_POL = 0x00000001 +V4L2_DV_HSYNC_POS_POL = 0x00000002 + + +class v4l2_dv_timings(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('bt', v4l2_bt_timings), + ('reserved', ctypes.c_uint32 * 32), + ] + + _fields_ = [ + ('type', ctypes.c_uint32), + ('_u', _u), + ] + + _anonymous_ = ('_u',) + _pack_ = True + + +# Values for the type field +V4L2_DV_BT_656_1120 = 0 + + +# +# Video inputs +# + +class v4l2_input(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('type', ctypes.c_uint32), + ('audioset', ctypes.c_uint32), + ('tuner', ctypes.c_uint32), + ('std', v4l2_std_id), + ('status', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +V4L2_INPUT_TYPE_TUNER = 1 +V4L2_INPUT_TYPE_CAMERA = 2 + +V4L2_IN_ST_NO_POWER = 0x00000001 +V4L2_IN_ST_NO_SIGNAL = 0x00000002 +V4L2_IN_ST_NO_COLOR = 0x00000004 + +V4L2_IN_ST_HFLIP = 0x00000010 +V4L2_IN_ST_VFLIP = 0x00000020 + +V4L2_IN_ST_NO_H_LOCK = 0x00000100 +V4L2_IN_ST_COLOR_KILL = 0x00000200 + +V4L2_IN_ST_NO_SYNC = 0x00010000 +V4L2_IN_ST_NO_EQU = 0x00020000 +V4L2_IN_ST_NO_CARRIER = 0x00040000 + +V4L2_IN_ST_MACROVISION = 0x01000000 +V4L2_IN_ST_NO_ACCESS = 0x02000000 +V4L2_IN_ST_VTR = 0x04000000 + +V4L2_IN_CAP_PRESETS = 0x00000001 +V4L2_IN_CAP_CUSTOM_TIMINGS = 0x00000002 +V4L2_IN_CAP_STD = 0x00000004 + +# +# Video outputs +# + +class v4l2_output(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('type', ctypes.c_uint32), + ('audioset', ctypes.c_uint32), + ('modulator', ctypes.c_uint32), + ('std', v4l2_std_id), + ('reserved', ctypes.c_uint32 * 4), + ] + + +V4L2_OUTPUT_TYPE_MODULATOR = 1 +V4L2_OUTPUT_TYPE_ANALOG = 2 +V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3 + +V4L2_OUT_CAP_PRESETS = 0x00000001 +V4L2_OUT_CAP_CUSTOM_TIMINGS = 0x00000002 +V4L2_OUT_CAP_STD = 0x00000004 + +# +# Controls +# + +class v4l2_control(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_uint32), + ('value', ctypes.c_int32), + ] + + +class v4l2_ext_control(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('value', ctypes.c_int32), + ('value64', ctypes.c_int64), + ('reserved', ctypes.c_void_p), + ] + + _fields_ = [ + ('id', ctypes.c_uint32), + ('reserved2', ctypes.c_uint32 * 2), + ('_u', _u) + ] + + _anonymous_ = ('_u',) + _pack_ = True + + +class v4l2_ext_controls(ctypes.Structure): + _fields_ = [ + ('ctrl_class', ctypes.c_uint32), + ('count', ctypes.c_uint32), + ('error_idx', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ('controls', ctypes.POINTER(v4l2_ext_control)), + ] + + +V4L2_CTRL_CLASS_USER = 0x00980000 +V4L2_CTRL_CLASS_MPEG = 0x00990000 +V4L2_CTRL_CLASS_CAMERA = 0x009a0000 +V4L2_CTRL_CLASS_FM_TX = 0x009b0000 + + +def V4L2_CTRL_ID_MASK(): + return 0x0fffffff + + +def V4L2_CTRL_ID2CLASS(id_): + return id_ & 0x0fff0000 # unsigned long + + +def V4L2_CTRL_DRIVER_PRIV(id_): + return (id_ & 0xffff) >= 0x1000 + + +class v4l2_queryctrl(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_uint32), + ('type', v4l2_ctrl_type), + ('name', ctypes.c_char * 32), + ('minimum', ctypes.c_int32), + ('maximum', ctypes.c_int32), + ('step', ctypes.c_int32), + ('default', ctypes.c_int32), + ('flags', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +class v4l2_querymenu(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_uint32), + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('reserved', ctypes.c_uint32), + ] + + +V4L2_CTRL_FLAG_DISABLED = 0x0001 +V4L2_CTRL_FLAG_GRABBED = 0x0002 +V4L2_CTRL_FLAG_READ_ONLY = 0x0004 +V4L2_CTRL_FLAG_UPDATE = 0x0008 +V4L2_CTRL_FLAG_INACTIVE = 0x0010 +V4L2_CTRL_FLAG_SLIDER = 0x0020 +V4L2_CTRL_FLAG_WRITE_ONLY = 0x0040 + +V4L2_CTRL_FLAG_NEXT_CTRL = 0x80000000 + +V4L2_CID_BASE = V4L2_CTRL_CLASS_USER | 0x900 +V4L2_CID_USER_BASE = V4L2_CID_BASE +V4L2_CID_PRIVATE_BASE = 0x08000000 + +V4L2_CID_USER_CLASS = V4L2_CTRL_CLASS_USER | 1 +V4L2_CID_BRIGHTNESS = V4L2_CID_BASE + 0 +V4L2_CID_CONTRAST = V4L2_CID_BASE + 1 +V4L2_CID_SATURATION = V4L2_CID_BASE + 2 +V4L2_CID_HUE = V4L2_CID_BASE + 3 +V4L2_CID_AUDIO_VOLUME = V4L2_CID_BASE + 5 +V4L2_CID_AUDIO_BALANCE = V4L2_CID_BASE + 6 +V4L2_CID_AUDIO_BASS = V4L2_CID_BASE + 7 +V4L2_CID_AUDIO_TREBLE = V4L2_CID_BASE + 8 +V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9 +V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10 +V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated +V4L2_CID_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12 +V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13 +V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14 +V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15 +V4L2_CID_GAMMA = V4L2_CID_BASE + 16 +V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated +V4L2_CID_EXPOSURE = V4L2_CID_BASE + 17 +V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18 +V4L2_CID_GAIN = V4L2_CID_BASE + 19 +V4L2_CID_HFLIP = V4L2_CID_BASE + 20 +V4L2_CID_VFLIP = V4L2_CID_BASE + 21 + +# Deprecated; use V4L2_CID_PAN_RESET and V4L2_CID_TILT_RESET +V4L2_CID_HCENTER = V4L2_CID_BASE + 22 +V4L2_CID_VCENTER = V4L2_CID_BASE + 23 + +V4L2_CID_POWER_LINE_FREQUENCY = V4L2_CID_BASE + 24 + +v4l2_power_line_frequency = enum +( + V4L2_CID_POWER_LINE_FREQUENCY_DISABLED, + V4L2_CID_POWER_LINE_FREQUENCY_50HZ, + V4L2_CID_POWER_LINE_FREQUENCY_60HZ, +) = range(3) + +V4L2_CID_HUE_AUTO = V4L2_CID_BASE + 25 +V4L2_CID_WHITE_BALANCE_TEMPERATURE = V4L2_CID_BASE + 26 +V4L2_CID_SHARPNESS = V4L2_CID_BASE + 27 +V4L2_CID_BACKLIGHT_COMPENSATION = V4L2_CID_BASE + 28 +V4L2_CID_CHROMA_AGC = V4L2_CID_BASE + 29 +V4L2_CID_COLOR_KILLER = V4L2_CID_BASE + 30 +V4L2_CID_COLORFX = V4L2_CID_BASE + 31 + +v4l2_colorfx = enum +( + V4L2_COLORFX_NONE, + V4L2_COLORFX_BW, + V4L2_COLORFX_SEPIA, +) = range(3) + +V4L2_CID_AUTOBRIGHTNESS = V4L2_CID_BASE + 32 +V4L2_CID_BAND_STOP_FILTER = V4L2_CID_BASE + 33 + +V4L2_CID_ROTATE = V4L2_CID_BASE + 34 +V4L2_CID_BG_COLOR = V4L2_CID_BASE + 35 +V4L2_CID_LASTP1 = V4L2_CID_BASE + 36 + +V4L2_CID_MPEG_BASE = V4L2_CTRL_CLASS_MPEG | 0x900 +V4L2_CID_MPEG_CLASS = V4L2_CTRL_CLASS_MPEG | 1 + +# MPEG streams +V4L2_CID_MPEG_STREAM_TYPE = V4L2_CID_MPEG_BASE + 0 + +v4l2_mpeg_stream_type = enum +( + V4L2_MPEG_STREAM_TYPE_MPEG2_PS, + V4L2_MPEG_STREAM_TYPE_MPEG2_TS, + V4L2_MPEG_STREAM_TYPE_MPEG1_SS, + V4L2_MPEG_STREAM_TYPE_MPEG2_DVD, + V4L2_MPEG_STREAM_TYPE_MPEG1_VCD, + V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD, +) = range(6) + +V4L2_CID_MPEG_STREAM_PID_PMT = V4L2_CID_MPEG_BASE + 1 +V4L2_CID_MPEG_STREAM_PID_AUDIO = V4L2_CID_MPEG_BASE + 2 +V4L2_CID_MPEG_STREAM_PID_VIDEO = V4L2_CID_MPEG_BASE + 3 +V4L2_CID_MPEG_STREAM_PID_PCR = V4L2_CID_MPEG_BASE + 4 +V4L2_CID_MPEG_STREAM_PES_ID_AUDIO = V4L2_CID_MPEG_BASE + 5 +V4L2_CID_MPEG_STREAM_PES_ID_VIDEO = V4L2_CID_MPEG_BASE + 6 +V4L2_CID_MPEG_STREAM_VBI_FMT = V4L2_CID_MPEG_BASE + 7 + +v4l2_mpeg_stream_vbi_fmt = enum +( + V4L2_MPEG_STREAM_VBI_FMT_NONE, + V4L2_MPEG_STREAM_VBI_FMT_IVTV, +) = range(2) + +V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ = V4L2_CID_MPEG_BASE + 100 + +v4l2_mpeg_audio_sampling_freq = enum +( + V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100, + V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000, + V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000, +) = range(3) + +V4L2_CID_MPEG_AUDIO_ENCODING = V4L2_CID_MPEG_BASE + 101 + +v4l2_mpeg_audio_encoding = enum +( + V4L2_MPEG_AUDIO_ENCODING_LAYER_1, + V4L2_MPEG_AUDIO_ENCODING_LAYER_2, + V4L2_MPEG_AUDIO_ENCODING_LAYER_3, + V4L2_MPEG_AUDIO_ENCODING_AAC, + V4L2_MPEG_AUDIO_ENCODING_AC3, +) = range(5) + +V4L2_CID_MPEG_AUDIO_L1_BITRATE = V4L2_CID_MPEG_BASE + 102 + +v4l2_mpeg_audio_l1_bitrate = enum +( + V4L2_MPEG_AUDIO_L1_BITRATE_32K, + V4L2_MPEG_AUDIO_L1_BITRATE_64K, + V4L2_MPEG_AUDIO_L1_BITRATE_96K, + V4L2_MPEG_AUDIO_L1_BITRATE_128K, + V4L2_MPEG_AUDIO_L1_BITRATE_160K, + V4L2_MPEG_AUDIO_L1_BITRATE_192K, + V4L2_MPEG_AUDIO_L1_BITRATE_224K, + V4L2_MPEG_AUDIO_L1_BITRATE_256K, + V4L2_MPEG_AUDIO_L1_BITRATE_288K, + V4L2_MPEG_AUDIO_L1_BITRATE_320K, + V4L2_MPEG_AUDIO_L1_BITRATE_352K, + V4L2_MPEG_AUDIO_L1_BITRATE_384K, + V4L2_MPEG_AUDIO_L1_BITRATE_416K, + V4L2_MPEG_AUDIO_L1_BITRATE_448K, +) = range(14) + +V4L2_CID_MPEG_AUDIO_L2_BITRATE = V4L2_CID_MPEG_BASE + 103 + +v4l2_mpeg_audio_l2_bitrate = enum +( + V4L2_MPEG_AUDIO_L2_BITRATE_32K, + V4L2_MPEG_AUDIO_L2_BITRATE_48K, + V4L2_MPEG_AUDIO_L2_BITRATE_56K, + V4L2_MPEG_AUDIO_L2_BITRATE_64K, + V4L2_MPEG_AUDIO_L2_BITRATE_80K, + V4L2_MPEG_AUDIO_L2_BITRATE_96K, + V4L2_MPEG_AUDIO_L2_BITRATE_112K, + V4L2_MPEG_AUDIO_L2_BITRATE_128K, + V4L2_MPEG_AUDIO_L2_BITRATE_160K, + V4L2_MPEG_AUDIO_L2_BITRATE_192K, + V4L2_MPEG_AUDIO_L2_BITRATE_224K, + V4L2_MPEG_AUDIO_L2_BITRATE_256K, + V4L2_MPEG_AUDIO_L2_BITRATE_320K, + V4L2_MPEG_AUDIO_L2_BITRATE_384K, +) = range(14) + +V4L2_CID_MPEG_AUDIO_L3_BITRATE = V4L2_CID_MPEG_BASE + 104 + +v4l2_mpeg_audio_l3_bitrate = enum +( + V4L2_MPEG_AUDIO_L3_BITRATE_32K, + V4L2_MPEG_AUDIO_L3_BITRATE_40K, + V4L2_MPEG_AUDIO_L3_BITRATE_48K, + V4L2_MPEG_AUDIO_L3_BITRATE_56K, + V4L2_MPEG_AUDIO_L3_BITRATE_64K, + V4L2_MPEG_AUDIO_L3_BITRATE_80K, + V4L2_MPEG_AUDIO_L3_BITRATE_96K, + V4L2_MPEG_AUDIO_L3_BITRATE_112K, + V4L2_MPEG_AUDIO_L3_BITRATE_128K, + V4L2_MPEG_AUDIO_L3_BITRATE_160K, + V4L2_MPEG_AUDIO_L3_BITRATE_192K, + V4L2_MPEG_AUDIO_L3_BITRATE_224K, + V4L2_MPEG_AUDIO_L3_BITRATE_256K, + V4L2_MPEG_AUDIO_L3_BITRATE_320K, +) = range(14) + +V4L2_CID_MPEG_AUDIO_MODE = V4L2_CID_MPEG_BASE + 105 + +v4l2_mpeg_audio_mode = enum +( + V4L2_MPEG_AUDIO_MODE_STEREO, + V4L2_MPEG_AUDIO_MODE_JOINT_STEREO, + V4L2_MPEG_AUDIO_MODE_DUAL, + V4L2_MPEG_AUDIO_MODE_MONO, +) = range(4) + +V4L2_CID_MPEG_AUDIO_MODE_EXTENSION = V4L2_CID_MPEG_BASE + 106 + +v4l2_mpeg_audio_mode_extension = enum +( + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4, + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_8, + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_12, + V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16, +) = range(4) + +V4L2_CID_MPEG_AUDIO_EMPHASIS = V4L2_CID_MPEG_BASE + 107 + +v4l2_mpeg_audio_emphasis = enum +( + V4L2_MPEG_AUDIO_EMPHASIS_NONE, + V4L2_MPEG_AUDIO_EMPHASIS_50_DIV_15_uS, + V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17, +) = range(3) + +V4L2_CID_MPEG_AUDIO_CRC = V4L2_CID_MPEG_BASE + 108 + +v4l2_mpeg_audio_crc = enum +( + V4L2_MPEG_AUDIO_CRC_NONE, + V4L2_MPEG_AUDIO_CRC_CRC16, +) = range(2) + +V4L2_CID_MPEG_AUDIO_MUTE = V4L2_CID_MPEG_BASE + 109 +V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_MPEG_BASE + 110 +V4L2_CID_MPEG_AUDIO_AC3_BITRATE = V4L2_CID_MPEG_BASE + 111 + +v4l2_mpeg_audio_ac3_bitrate = enum +( + V4L2_MPEG_AUDIO_AC3_BITRATE_32K, + V4L2_MPEG_AUDIO_AC3_BITRATE_40K, + V4L2_MPEG_AUDIO_AC3_BITRATE_48K, + V4L2_MPEG_AUDIO_AC3_BITRATE_56K, + V4L2_MPEG_AUDIO_AC3_BITRATE_64K, + V4L2_MPEG_AUDIO_AC3_BITRATE_80K, + V4L2_MPEG_AUDIO_AC3_BITRATE_96K, + V4L2_MPEG_AUDIO_AC3_BITRATE_112K, + V4L2_MPEG_AUDIO_AC3_BITRATE_128K, + V4L2_MPEG_AUDIO_AC3_BITRATE_160K, + V4L2_MPEG_AUDIO_AC3_BITRATE_192K, + V4L2_MPEG_AUDIO_AC3_BITRATE_224K, + V4L2_MPEG_AUDIO_AC3_BITRATE_256K, + V4L2_MPEG_AUDIO_AC3_BITRATE_320K, + V4L2_MPEG_AUDIO_AC3_BITRATE_384K, + V4L2_MPEG_AUDIO_AC3_BITRATE_448K, + V4L2_MPEG_AUDIO_AC3_BITRATE_512K, + V4L2_MPEG_AUDIO_AC3_BITRATE_576K, + V4L2_MPEG_AUDIO_AC3_BITRATE_640K, +) = range(19) + +V4L2_CID_MPEG_VIDEO_ENCODING = V4L2_CID_MPEG_BASE + 200 + +v4l2_mpeg_video_encoding = enum +( + V4L2_MPEG_VIDEO_ENCODING_MPEG_1, + V4L2_MPEG_VIDEO_ENCODING_MPEG_2, + V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, +) = range(3) + +V4L2_CID_MPEG_VIDEO_ASPECT = V4L2_CID_MPEG_BASE + 201 + +v4l2_mpeg_video_aspect = enum +( + V4L2_MPEG_VIDEO_ASPECT_1x1, + V4L2_MPEG_VIDEO_ASPECT_4x3, + V4L2_MPEG_VIDEO_ASPECT_16x9, + V4L2_MPEG_VIDEO_ASPECT_221x100, +) = range(4) + +V4L2_CID_MPEG_VIDEO_B_FRAMES = V4L2_CID_MPEG_BASE + 202 +V4L2_CID_MPEG_VIDEO_GOP_SIZE = V4L2_CID_MPEG_BASE + 203 +V4L2_CID_MPEG_VIDEO_GOP_CLOSURE = V4L2_CID_MPEG_BASE + 204 +V4L2_CID_MPEG_VIDEO_PULLDOWN = V4L2_CID_MPEG_BASE + 205 +V4L2_CID_MPEG_VIDEO_BITRATE_MODE = V4L2_CID_MPEG_BASE + 206 + +v4l2_mpeg_video_bitrate_mode = enum +( + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, +) = range(2) + +V4L2_CID_MPEG_VIDEO_BITRATE = V4L2_CID_MPEG_BASE + 207 +V4L2_CID_MPEG_VIDEO_BITRATE_PEAK = V4L2_CID_MPEG_BASE + 208 +V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION = V4L2_CID_MPEG_BASE + 209 +V4L2_CID_MPEG_VIDEO_MUTE = V4L2_CID_MPEG_BASE + 210 +V4L2_CID_MPEG_VIDEO_MUTE_YUV = V4L2_CID_MPEG_BASE + 211 + +V4L2_CID_MPEG_CX2341X_BASE = V4L2_CTRL_CLASS_MPEG | 0x1000 +V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 0 + +v4l2_mpeg_cx2341x_video_spatial_filter_mode = enum +( + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL, + V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO, +) = range(2) + +V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 1 +V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 2 + +v4l2_mpeg_cx2341x_video_luma_spatial_filter_type = enum +( + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_VERT, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_HV_SEPARABLE, + V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE, +) = range(5) + +V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 3 + +v4l2_mpeg_cx2341x_video_chroma_spatial_filter_type = enum +( + V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR, +) = range(2) + +V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 4 + +v4l2_mpeg_cx2341x_video_temporal_filter_mode = enum +( + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL, + V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO, +) = range(2) + +V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 5 +V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 6 + +v4l2_mpeg_cx2341x_video_median_filter_type = enum +( + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_VERT, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR_VERT, + V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG, +) = range(5) + +V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 7 +V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 8 +V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 9 +V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 10 +V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS = V4L2_CID_MPEG_CX2341X_BASE + 11 + +V4L2_CID_CAMERA_CLASS_BASE = V4L2_CTRL_CLASS_CAMERA | 0x900 +V4L2_CID_CAMERA_CLASS = V4L2_CTRL_CLASS_CAMERA | 1 + +V4L2_CID_EXPOSURE_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 1 + +v4l2_exposure_auto_type = enum +( + V4L2_EXPOSURE_AUTO, + V4L2_EXPOSURE_MANUAL, + V4L2_EXPOSURE_SHUTTER_PRIORITY, + V4L2_EXPOSURE_APERTURE_PRIORITY, +) = range(4) + +V4L2_CID_EXPOSURE_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 2 +V4L2_CID_EXPOSURE_AUTO_PRIORITY = V4L2_CID_CAMERA_CLASS_BASE + 3 + +V4L2_CID_PAN_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 4 +V4L2_CID_TILT_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 5 +V4L2_CID_PAN_RESET = V4L2_CID_CAMERA_CLASS_BASE + 6 +V4L2_CID_TILT_RESET = V4L2_CID_CAMERA_CLASS_BASE + 7 + +V4L2_CID_PAN_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 8 +V4L2_CID_TILT_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 9 + +V4L2_CID_FOCUS_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 10 +V4L2_CID_FOCUS_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 11 +V4L2_CID_FOCUS_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 12 + +V4L2_CID_ZOOM_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 13 +V4L2_CID_ZOOM_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 14 +V4L2_CID_ZOOM_CONTINUOUS = V4L2_CID_CAMERA_CLASS_BASE + 15 + +V4L2_CID_PRIVACY = V4L2_CID_CAMERA_CLASS_BASE + 16 + +V4L2_CID_FM_TX_CLASS_BASE = V4L2_CTRL_CLASS_FM_TX | 0x900 +V4L2_CID_FM_TX_CLASS = V4L2_CTRL_CLASS_FM_TX | 1 + +V4L2_CID_RDS_TX_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 1 +V4L2_CID_RDS_TX_PI = V4L2_CID_FM_TX_CLASS_BASE + 2 +V4L2_CID_RDS_TX_PTY = V4L2_CID_FM_TX_CLASS_BASE + 3 +V4L2_CID_RDS_TX_PS_NAME = V4L2_CID_FM_TX_CLASS_BASE + 5 +V4L2_CID_RDS_TX_RADIO_TEXT = V4L2_CID_FM_TX_CLASS_BASE + 6 + +V4L2_CID_AUDIO_LIMITER_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 64 +V4L2_CID_AUDIO_LIMITER_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 65 +V4L2_CID_AUDIO_LIMITER_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 66 + +V4L2_CID_AUDIO_COMPRESSION_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 80 +V4L2_CID_AUDIO_COMPRESSION_GAIN = V4L2_CID_FM_TX_CLASS_BASE + 81 +V4L2_CID_AUDIO_COMPRESSION_THRESHOLD = V4L2_CID_FM_TX_CLASS_BASE + 82 +V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME = V4L2_CID_FM_TX_CLASS_BASE + 83 +V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 84 + +V4L2_CID_PILOT_TONE_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 96 +V4L2_CID_PILOT_TONE_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 97 +V4L2_CID_PILOT_TONE_FREQUENCY = V4L2_CID_FM_TX_CLASS_BASE + 98 + +V4L2_CID_TUNE_PREEMPHASIS = V4L2_CID_FM_TX_CLASS_BASE + 112 + +v4l2_preemphasis = enum +( + V4L2_PREEMPHASIS_DISABLED, + V4L2_PREEMPHASIS_50_uS, + V4L2_PREEMPHASIS_75_uS, +) = range(3) + +V4L2_CID_TUNE_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113 +V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114 + + +# +# Tuning +# + +class v4l2_tuner(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('type', v4l2_tuner_type), + ('capability', ctypes.c_uint32), + ('rangelow', ctypes.c_uint32), + ('rangehigh', ctypes.c_uint32), + ('rxsubchans', ctypes.c_uint32), + ('audmode', ctypes.c_uint32), + ('signal', ctypes.c_int32), + ('afc', ctypes.c_int32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +class v4l2_modulator(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('capability', ctypes.c_uint32), + ('rangelow', ctypes.c_uint32), + ('rangehigh', ctypes.c_uint32), + ('txsubchans', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ] + + +V4L2_TUNER_CAP_LOW = 0x0001 +V4L2_TUNER_CAP_NORM = 0x0002 +V4L2_TUNER_CAP_STEREO = 0x0010 +V4L2_TUNER_CAP_LANG2 = 0x0020 +V4L2_TUNER_CAP_SAP = 0x0020 +V4L2_TUNER_CAP_LANG1 = 0x0040 +V4L2_TUNER_CAP_RDS = 0x0080 + +V4L2_TUNER_SUB_MONO = 0x0001 +V4L2_TUNER_SUB_STEREO = 0x0002 +V4L2_TUNER_SUB_LANG2 = 0x0004 +V4L2_TUNER_SUB_SAP = 0x0004 +V4L2_TUNER_SUB_LANG1 = 0x0008 +V4L2_TUNER_SUB_RDS = 0x0010 + +V4L2_TUNER_MODE_MONO = 0x0000 +V4L2_TUNER_MODE_STEREO = 0x0001 +V4L2_TUNER_MODE_LANG2 = 0x0002 +V4L2_TUNER_MODE_SAP = 0x0002 +V4L2_TUNER_MODE_LANG1 = 0x0003 +V4L2_TUNER_MODE_LANG1_LANG2 = 0x0004 + + +class v4l2_frequency(ctypes.Structure): + _fields_ = [ + ('tuner', ctypes.c_uint32), + ('type', v4l2_tuner_type), + ('frequency', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 8), + ] + + +class v4l2_hw_freq_seek(ctypes.Structure): + _fields_ = [ + ('tuner', ctypes.c_uint32), + ('type', v4l2_tuner_type), + ('seek_upward', ctypes.c_uint32), + ('wrap_around', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 8), + ] + + +# +# RDS +# + +class v4l2_rds_data(ctypes.Structure): + _fields_ = [ + ('lsb', ctypes.c_char), + ('msb', ctypes.c_char), + ('block', ctypes.c_char), + ] + + _pack_ = True + + +V4L2_RDS_BLOCK_MSK = 0x7 +V4L2_RDS_BLOCK_A = 0 +V4L2_RDS_BLOCK_B = 1 +V4L2_RDS_BLOCK_C = 2 +V4L2_RDS_BLOCK_D = 3 +V4L2_RDS_BLOCK_C_ALT = 4 +V4L2_RDS_BLOCK_INVALID = 7 + +V4L2_RDS_BLOCK_CORRECTED = 0x40 +V4L2_RDS_BLOCK_ERROR = 0x80 + + +# +# Audio +# + +class v4l2_audio(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('capability', ctypes.c_uint32), + ('mode', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +V4L2_AUDCAP_STEREO = 0x00001 +V4L2_AUDCAP_AVL = 0x00002 + +V4L2_AUDMODE_AVL = 0x00001 + + +class v4l2_audioout(ctypes.Structure): + _fields_ = [ + ('index', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ('capability', ctypes.c_uint32), + ('mode', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +# +# Mpeg services (experimental) +# + +V4L2_ENC_IDX_FRAME_I = 0 +V4L2_ENC_IDX_FRAME_P = 1 +V4L2_ENC_IDX_FRAME_B = 2 +V4L2_ENC_IDX_FRAME_MASK = 0xf + + +class v4l2_enc_idx_entry(ctypes.Structure): + _fields_ = [ + ('offset', ctypes.c_uint64), + ('pts', ctypes.c_uint64), + ('length', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +V4L2_ENC_IDX_ENTRIES = 64 + + +class v4l2_enc_idx(ctypes.Structure): + _fields_ = [ + ('entries', ctypes.c_uint32), + ('entries_cap', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 4), + ('entry', v4l2_enc_idx_entry * V4L2_ENC_IDX_ENTRIES), + ] + + +V4L2_ENC_CMD_START = 0 +V4L2_ENC_CMD_STOP = 1 +V4L2_ENC_CMD_PAUSE = 2 +V4L2_ENC_CMD_RESUME = 3 + +V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0 + + +class v4l2_encoder_cmd(ctypes.Structure): + class _u(ctypes.Union): + class _s(ctypes.Structure): + _fields_ = [ + ('data', ctypes.c_uint32 * 8), + ] + + _fields_ = [ + ('raw', _s), + ] + + _fields_ = [ + ('cmd', ctypes.c_uint32), + ('flags', ctypes.c_uint32), + ('_u', _u), + ] + + _anonymous_ = ('_u',) + + +# +# Data services (VBI) +# + +class v4l2_vbi_format(ctypes.Structure): + _fields_ = [ + ('sampling_rate', ctypes.c_uint32), + ('offset', ctypes.c_uint32), + ('samples_per_line', ctypes.c_uint32), + ('sample_format', ctypes.c_uint32), + ('start', ctypes.c_int32 * 2), + ('count', ctypes.c_uint32 * 2), + ('flags', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +V4L2_VBI_UNSYNC = 1 << 0 +V4L2_VBI_INTERLACED = 1 << 1 + + +class v4l2_sliced_vbi_format(ctypes.Structure): + _fields_ = [ + ('service_set', ctypes.c_uint16), + ('service_lines', ctypes.c_uint16 * 2 * 24), + ('io_size', ctypes.c_uint32), + ('reserved', ctypes.c_uint32 * 2), + ] + + +V4L2_SLICED_TELETEXT_B = 0x0001 +V4L2_SLICED_VPS = 0x0400 +V4L2_SLICED_CAPTION_525 = 0x1000 +V4L2_SLICED_WSS_625 = 0x4000 +V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525 +V4L2_SLICED_VBI_625 = ( + V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625) + + +class v4l2_sliced_vbi_cap(ctypes.Structure): + _fields_ = [ + ('service_set', ctypes.c_uint16), + ('service_lines', ctypes.c_uint16 * 2 * 24), + ('type', v4l2_buf_type), + ('reserved', ctypes.c_uint32 * 3), + ] + + +class v4l2_sliced_vbi_data(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_uint32), + ('field', ctypes.c_uint32), + ('line', ctypes.c_uint32), + ('reserved', ctypes.c_uint32), + ('data', ctypes.c_char * 48), + ] + + +# +# Sliced VBI data inserted into MPEG Streams +# + + +V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1 +V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4 +V4L2_MPEG_VBI_IVTV_WSS_625 = 5 +V4L2_MPEG_VBI_IVTV_VPS = 7 + + +class v4l2_mpeg_vbi_itv0_line(ctypes.Structure): + _fields_ = [ + ('id', ctypes.c_char), + ('data', ctypes.c_char * 42), + ] + + _pack_ = True + + +class v4l2_mpeg_vbi_itv0(ctypes.Structure): + _fields_ = [ + ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes? + ('line', v4l2_mpeg_vbi_itv0_line * 35), + ] + + _pack_ = True + + +class v4l2_mpeg_vbi_ITV0(ctypes.Structure): + _fields_ = [ + ('line', v4l2_mpeg_vbi_itv0_line * 36), + ] + + _pack_ = True + + +V4L2_MPEG_VBI_IVTV_MAGIC0 = "itv0" +V4L2_MPEG_VBI_IVTV_MAGIC1 = "ITV0" + + +class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('itv0', v4l2_mpeg_vbi_itv0), + ('ITV0', v4l2_mpeg_vbi_ITV0), + ] + + _fields_ = [ + ('magic', ctypes.c_char * 4), + ('_u', _u) + ] + + _anonymous_ = ('_u',) + _pack_ = True + + +# +# Aggregate structures +# + +class v4l2_format(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('pix', v4l2_pix_format), + ('win', v4l2_window), + ('vbi', v4l2_vbi_format), + ('sliced', v4l2_sliced_vbi_format), + ('raw_data', ctypes.c_char * 200), + ] + + _fields_ = [ + ('type', v4l2_buf_type), + ('fmt', _u), + ] + + +class v4l2_streamparm(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('capture', v4l2_captureparm), + ('output', v4l2_outputparm), + ('raw_data', ctypes.c_char * 200), + ] + + _fields_ = [ + ('type', v4l2_buf_type), + ('parm', _u) + ] + + +# +# Advanced debugging +# + +V4L2_CHIP_MATCH_HOST = 0 +V4L2_CHIP_MATCH_I2C_DRIVER = 1 +V4L2_CHIP_MATCH_I2C_ADDR = 2 +V4L2_CHIP_MATCH_AC97 = 3 + + +class v4l2_dbg_match(ctypes.Structure): + class _u(ctypes.Union): + _fields_ = [ + ('addr', ctypes.c_uint32), + ('name', ctypes.c_char * 32), + ] + + _fields_ = [ + ('type', ctypes.c_uint32), + ('_u', _u), + ] + + _anonymous_ = ('_u',) + _pack_ = True + + +class v4l2_dbg_register(ctypes.Structure): + _fields_ = [ + ('match', v4l2_dbg_match), + ('size', ctypes.c_uint32), + ('reg', ctypes.c_uint64), + ('val', ctypes.c_uint64), + ] + + _pack_ = True + + +class v4l2_dbg_chip_ident(ctypes.Structure): + _fields_ = [ + ('match', v4l2_dbg_match), + ('ident', ctypes.c_uint32), + ('revision', ctypes.c_uint32), + ] + + _pack_ = True + + +# +# ioctl codes for video devices +# + +VIDIOC_QUERYCAP = _IOR('V', 0, v4l2_capability) +VIDIOC_RESERVED = _IO('V', 1) +VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc) +VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format) +VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format) +VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers) +VIDIOC_QUERYBUF = _IOWR('V', 9, v4l2_buffer) +VIDIOC_G_FBUF = _IOR('V', 10, v4l2_framebuffer) +VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer) +VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int) +VIDIOC_QBUF = _IOWR('V', 15, v4l2_buffer) +VIDIOC_DQBUF = _IOWR('V', 17, v4l2_buffer) +VIDIOC_STREAMON = _IOW('V', 18, ctypes.c_int) +VIDIOC_STREAMOFF = _IOW('V', 19, ctypes.c_int) +VIDIOC_G_PARM = _IOWR('V', 21, v4l2_streamparm) +VIDIOC_S_PARM = _IOWR('V', 22, v4l2_streamparm) +VIDIOC_G_STD = _IOR('V', 23, v4l2_std_id) +VIDIOC_S_STD = _IOW('V', 24, v4l2_std_id) +VIDIOC_ENUMSTD = _IOWR('V', 25, v4l2_standard) +VIDIOC_ENUMINPUT = _IOWR('V', 26, v4l2_input) +VIDIOC_G_CTRL = _IOWR('V', 27, v4l2_control) +VIDIOC_S_CTRL = _IOWR('V', 28, v4l2_control) +VIDIOC_G_TUNER = _IOWR('V', 29, v4l2_tuner) +VIDIOC_S_TUNER = _IOW('V', 30, v4l2_tuner) +VIDIOC_G_AUDIO = _IOR('V', 33, v4l2_audio) +VIDIOC_S_AUDIO = _IOW('V', 34, v4l2_audio) +VIDIOC_QUERYCTRL = _IOWR('V', 36, v4l2_queryctrl) +VIDIOC_QUERYMENU = _IOWR('V', 37, v4l2_querymenu) +VIDIOC_G_INPUT = _IOR('V', 38, ctypes.c_int) +VIDIOC_S_INPUT = _IOWR('V', 39, ctypes.c_int) +VIDIOC_G_OUTPUT = _IOR('V', 46, ctypes.c_int) +VIDIOC_S_OUTPUT = _IOWR('V', 47, ctypes.c_int) +VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output) +VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout) +VIDIOC_S_AUDOUT = _IOW('V', 50, v4l2_audioout) +VIDIOC_G_MODULATOR = _IOWR('V', 54, v4l2_modulator) +VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator) +VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency) +VIDIOC_S_FREQUENCY = _IOW('V', 57, v4l2_frequency) +VIDIOC_CROPCAP = _IOWR('V', 58, v4l2_cropcap) +VIDIOC_G_CROP = _IOWR('V', 59, v4l2_crop) +VIDIOC_S_CROP = _IOW('V', 60, v4l2_crop) +VIDIOC_G_JPEGCOMP = _IOR('V', 61, v4l2_jpegcompression) +VIDIOC_S_JPEGCOMP = _IOW('V', 62, v4l2_jpegcompression) +VIDIOC_QUERYSTD = _IOR('V', 63, v4l2_std_id) +VIDIOC_TRY_FMT = _IOWR('V', 64, v4l2_format) +VIDIOC_ENUMAUDIO = _IOWR('V', 65, v4l2_audio) +VIDIOC_ENUMAUDOUT = _IOWR('V', 66, v4l2_audioout) +VIDIOC_G_PRIORITY = _IOR('V', 67, v4l2_priority) +VIDIOC_S_PRIORITY = _IOW('V', 68, v4l2_priority) +VIDIOC_G_SLICED_VBI_CAP = _IOWR('V', 69, v4l2_sliced_vbi_cap) +VIDIOC_LOG_STATUS = _IO('V', 70) +VIDIOC_G_EXT_CTRLS = _IOWR('V', 71, v4l2_ext_controls) +VIDIOC_S_EXT_CTRLS = _IOWR('V', 72, v4l2_ext_controls) +VIDIOC_TRY_EXT_CTRLS = _IOWR('V', 73, v4l2_ext_controls) + +VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, v4l2_frmsizeenum) +VIDIOC_ENUM_FRAMEINTERVALS = _IOWR('V', 75, v4l2_frmivalenum) +VIDIOC_G_ENC_INDEX = _IOR('V', 76, v4l2_enc_idx) +VIDIOC_ENCODER_CMD = _IOWR('V', 77, v4l2_encoder_cmd) +VIDIOC_TRY_ENCODER_CMD = _IOWR('V', 78, v4l2_encoder_cmd) + +VIDIOC_DBG_S_REGISTER = _IOW('V', 79, v4l2_dbg_register) +VIDIOC_DBG_G_REGISTER = _IOWR('V', 80, v4l2_dbg_register) + +VIDIOC_DBG_G_CHIP_IDENT = _IOWR('V', 81, v4l2_dbg_chip_ident) + +VIDIOC_S_HW_FREQ_SEEK = _IOW('V', 82, v4l2_hw_freq_seek) +VIDIOC_ENUM_DV_PRESETS = _IOWR('V', 83, v4l2_dv_enum_preset) +VIDIOC_S_DV_PRESET = _IOWR('V', 84, v4l2_dv_preset) +VIDIOC_G_DV_PRESET = _IOWR('V', 85, v4l2_dv_preset) +VIDIOC_QUERY_DV_PRESET = _IOR('V', 86, v4l2_dv_preset) +VIDIOC_S_DV_TIMINGS = _IOWR('V', 87, v4l2_dv_timings) +VIDIOC_G_DV_TIMINGS = _IOWR('V', 88, v4l2_dv_timings) + +VIDIOC_OVERLAY_OLD = _IOWR('V', 14, ctypes.c_int) +VIDIOC_S_PARM_OLD = _IOW('V', 22, v4l2_streamparm) +VIDIOC_S_CTRL_OLD = _IOW('V', 28, v4l2_control) +VIDIOC_G_AUDIO_OLD = _IOWR('V', 33, v4l2_audio) +VIDIOC_G_AUDOUT_OLD = _IOWR('V', 49, v4l2_audioout) +VIDIOC_CROPCAP_OLD = _IOR('V', 58, v4l2_cropcap) + +BASE_VIDIOC_PRIVATE = 192 diff --git a/plugins/rfid/__init__.py b/plugins/rfid/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/rfid/__init__.py diff --git a/plugins/rfid/device.py b/plugins/rfid/device.py new file mode 100644 index 0000000..04a82b2 --- /dev/null +++ b/plugins/rfid/device.py @@ -0,0 +1,61 @@ +import gobject + +class RFIDDevice(gobject.GObject): + """ + Ancestor class for every supported device. + The main class for the device driver must be called "RFIDReader". + """ + # signal "tag-read" has to be emitted when a tag has been read. + # The handler must receive the ISO-11784 hex value of the tag. + # signal "disconnected" has to be emitted when the device is + # unplugged or an error has been detected. + __gsignals__ = { + 'tag-read': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)), + 'disconnected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING,)) + } + def __init__(self): + """ + Initializer. Subclasses must call this method. + """ + self.__gobject_init__() + + def get_present(self): + """ + This method must detect if the device is present, returning True if so, + or False otherwise. + """ + raise NotImplementedError + + def get_version(self): + """ + Returns a descriptive text of the device. + """ + raise NotImplementedError + + def do_connect(self): + """ + Connects to the device. + Must return True if successfull, False otherwise. + """ + raise NotImplementedError + + def do_disconnect(self): + """ + Disconnects from the device. + """ + raise NotImplementedError + + def read_tag(self): + """ + Returns the 64 bit data in hex format of the last read tag. + """ + raise NotImplementedError + + def write_tag(self, hex_val): + """ + Could be implemented if the device is capable of writing tags. + Receives the hex value (according to ISO 11784) to be written. + Returns True if successfull or False if something went wrong. + """ diff --git a/plugins/rfid/rfid.py b/plugins/rfid/rfid.py new file mode 100644 index 0000000..a83bae0 --- /dev/null +++ b/plugins/rfid/rfid.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python +#Copyright (C) 2010,11 Emiliano Pastorino +#Copyright (c) 2011 Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import dbus +from dbus.mainloop.glib import DBusGMainLoop + +from plugins.rfid.rfidutils import strhex2bin, strbin2dec, find_device + +from plugins.plugin import Plugin + +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import primitive_dictionary +from TurtleArt.tautils import debug_output + +import logging +_logger = logging.getLogger('turtleart-activity RFID plugin') + +HAL_SERVICE = 'org.freedesktop.Hal' +HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' +HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' +HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' +REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ + 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' + + +class Rfid(Plugin): + + def __init__(self, parent): + self._parent = parent + self._status = False + + """ + The following code will initialize a USB RFID reader. Please note that + in order to make this initialization function work, it is necessary to + set the permission for the ttyUSB device to 0666. You can do this by + adding a rule to /etc/udev/rules.d + + As root (using sudo or su), copy the following text into a new file in + /etc/udev/rules.d/94-ttyUSB-rules + + KERNEL=="ttyUSB[0-9]",MODE="0666" + + You only have to do this once. + """ + + self.rfid_connected = False + self.rfid_device = find_device() + self.rfid_idn = '' + + if self.rfid_device is not None: + _logger.info("RFID device found") + self.rfid_connected = self.rfid_device.do_connect() + if self.rfid_connected: + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + loop = DBusGMainLoop() + bus = dbus.SystemBus(mainloop=loop) + hmgr_iface = dbus.Interface(bus.get_object(HAL_SERVICE, + HAL_MGR_PATH), HAL_MGR_IFACE) + + hmgr_iface.connect_to_signal('DeviceAdded', self._device_added_cb) + + self._status = True + + def setup(self): + # set up RFID-specific blocks + if self._status: + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['rfid'] = self.prim_read_camera + palette.add_block('rfid', + palette='sensor', + style='box-style', + label=_('RFID'), + help_string=_('read value from RFID device'), + value_block=True, + prim_name='rfid') + self._parent.lc.def_prim('rfid', 0, + lambda self: primitive_dictionary['rfid'](True)) + + def _status_report(self): + debug_output('Reporting RFID status: %s' % (str(self._status))) + return self._status + + def _device_added_cb(self, path): + """ + Called from hal connection when a new device is plugged. + """ + if not self.rfid_connected: + self.rfid_device = find_device() + _logger.debug("DEVICE_ADDED: %s" % self.rfid_device) + if self.rfid_device is not None: + _logger.debug("DEVICE_ADDED: RFID device is not None!") + self.rfid_connected = self._device.do_connect() + if self.rfid_connected: + _logger.debug("DEVICE_ADDED: Connected!") + self.rfid_device.connect("tag-read", self._tag_read_cb) + self.rfid_device.connect("disconnected", self._disconnected_cb) + + def _disconnected_cb(self, device, text): + """ + Called when the device is disconnected. + """ + self.rfid_connected = False + self.rfid_device = None + + def _tag_read_cb(self, device, tagid): + """ + Callback for "tag-read" signal. Receives the read tag id. + """ + idbin = strhex2bin(tagid) + self.rfid_idn = strbin2dec(idbin[26:64]) + while self.rfid_idn.__len__() < 9: + self.rfid_idn = '0' + self.rfid_idn + print tagid, idbin, self.rfid_idn + self.tw.lc.update_label_value('rfid', self.rfid_idn) + + # Block primitives used in talogo + + def prim_read_rfid(self): + if self._status: + return self.rfid_idn diff --git a/plugins/rfid/rfidrweusb.py b/plugins/rfid/rfidrweusb.py new file mode 100644 index 0000000..bd12631 --- /dev/null +++ b/plugins/rfid/rfidrweusb.py @@ -0,0 +1,200 @@ +from device import RFIDDevice +from serial import Serial +import dbus +from dbus.mainloop.glib import DBusGMainLoop +import gobject +from time import sleep +import utils + +HAL_SERVICE = 'org.freedesktop.Hal' +HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' +HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' +HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' +REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ + 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' + +VERSIONS = ['301'] + +class RFIDReader(RFIDDevice): + """ + RFIDRW-E-W interface. + """ + + def __init__(self): + + RFIDDevice.__init__(self) + self.last_tag = "" + self.tags = [] + self.ser = Serial() + self.device = '' + self.device_path = '' + self._connected = False + + loop = DBusGMainLoop() + self.bus = dbus.SystemBus(mainloop=loop) + hmgr_iface = dbus.Interface(self.bus.get_object(HAL_SERVICE, + HAL_MGR_PATH), HAL_MGR_IFACE) + + hmgr_iface.connect_to_signal('DeviceRemoved', self._device_removed_cb) + + def get_present(self): + """ + Checks if RFID-RW-USB device is present. + Returns True if so, False otherwise. + """ + hmgr_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, HAL_MGR_PATH), + HAL_MGR_IFACE) + serialusb_devices = set(hmgr_if.FindDeviceStringMatch('serial.type', + 'usb')) & set(hmgr_if.FindDeviceStringMatch( + 'info.subsystem', 'tty')) + for i in serialusb_devices: + serialusb_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, i), + HAL_DEV_IFACE) + if serialusb_if.PropertyExists('info.parent'): + parent_udi = str(serialusb_if.GetProperty('info.parent')) + parent = dbus.Interface(self.bus.get_object(HAL_SERVICE, + parent_udi), HAL_DEV_IFACE) + if parent.PropertyExists('info.linux.driver') and \ + str(parent.GetProperty('info.linux.driver')) == 'ftdi_sio': + device = str(serialusb_if.GetProperty('linux.device_file')) + ser = Serial(device, 9600, timeout=0.1) + ser.read(100) + ser.write('v') + ser.write('e') + ser.write('r') + ser.write('\x0D') + resp = ser.read(4) + if resp[0:-1] in VERSIONS: + self.device = device + self.device_path = i + return True + return False + + def do_connect(self): + """ + Connects to the device. + Returns True if successfull, False otherwise. + """ + retval = False + if self.get_present(): + try: + self.ser = Serial(self.device, 9600, timeout=0.1) + self._connected = True + if self._select_animal_tag: + #gobject.idle_add(self._loop) + gobject.timeout_add(1000, self._loop) + retval = True + except: + self._connected = False + return retval + + def do_disconnect(self): + """ + Disconnect from the device. + """ + self.ser.close() + self._connected = False + + def read_tag(self): + """ + Returns the last read value. + """ + return self.last_tag + + def _select_animal_tag(self): + """ + Sends the "Select Tag 2" (animal tag) command to the device. + """ + self.ser.read(100) + self.ser.write('s') + self.ser.write('t') + self.ser.write('2') + self.ser.write('\x0d') + resp = self.ser.read(3)[0:-1] + if resp == 'OK': + return True + return False + + def get_version(self): + """ + Sends the version command to the device and returns + a string with the device version. + """ + #self.ser.flushInput() + ver = "???" + self.ser.read(100) + self.ser.write('v') + self.ser.write('e') + self.ser.write('r') + self.ser.write('\x0d') + resp = self.ser.read(4)[0:-1] + if resp in VERSIONS: + return "RFIDRW-E-USB " + resp + return ver + + def _device_removed_cb(self, path): + """ + Called when a device is removed. + Checks if the removed device is itself and emits the "disconnected" + signal if so. + """ + if path == self.device_path: + self.device_path = '' + self.ser.close() + self._connected = False + self.tags = [] + self.emit("disconnected","RFID-RW-USB") + + def _loop(self): + """ + Threaded loop for reading data from the device. + """ + if not self._connected: + return False + + self.ser.read(100) + self.ser.write('r') + self.ser.write('a') + self.ser.write('t') + self.ser.write('\x0d') + resp = self.ser.read(33)[0:-1].split('_') + if resp.__len__() is not 6 or resp in self.tags: + return True + + self.tags.append(resp) + anbit_bin = utils.dec2bin(int(resp[2])) + reserved_bin = '00000000000000' + databit_bin = utils.dec2bin(int(resp[3])) + country_bin = utils.dec2bin(int(resp[0])) + while country_bin.__len__() < 10: + country_bin = '0' + country_bin + id_bin = utils.dec2bin(int(resp[1])) + while id_bin.__len__() < 10: + id_bin = '0' + id_bin + + tag_bin = anbit_bin + reserved_bin + databit_bin + country_bin + id_bin + data = utils.bin2hex(tag_bin) + self.emit("tag-read", data) + self.last_tag = data + #sleep(1) + return True + +# Testing +#if __name__ == '__main__': +# def handler(device, idhex): +# """ +# Handler for "tag-read" signal. +# Prints the tag id. +# """ +# print "ID: ", idhex +# +# dev = RFIDReader() +# if dev.get_present(): +# print "SIPI!" +# dev.do_connect() +# dev.connect('tag-read', handler) +# else: +# print "Not connected" +# +# mloop = gobject.MainLoop() +# mloop.run() diff --git a/plugins/rfid/rfidutils.py b/plugins/rfid/rfidutils.py new file mode 100644 index 0000000..4e02619 --- /dev/null +++ b/plugins/rfid/rfidutils.py @@ -0,0 +1,123 @@ +# utils.py - Helper functions for tis2000.py +# Copyright (C) 2010 Emiliano Pastorino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import os +import logging + +def find_device(): + """ + Search for devices. + Return a device instance or None. + """ + device = None + for i in os.listdir(os.path.join('.', 'plugins/rfid')): + if not os.path.isdir(os.path.join('.', 'plugins/rfid', i)): + try: + _tempmod = __import__('rfid.%s'%i.split('.')[0], globals(), + locals(), ['RFIDReader'], -1) + devtemp = _tempmod.RFIDReader() + if devtemp.get_present() == True: + device = devtemp + except Exception, e: + # logging.error("FIND_DEVICE: %s: %s"%(i, e)) + pass + if device is None: + logging.debug("No RFID device found") + return device + + +def strhex2bin(strhex): + """ + Convert a string representing an hex value into a + string representing the same value in binary format. + """ + dic = { '0':"0000", + '1':"0001", + '2':"0010", + '3':"0011", + '4':"0100", + '5':"0101", + '6':"0110", + '7':"0111", + '8':"1000", + '9':"1001", + 'A':"1010", + 'B':"1011", + 'C':"1100", + 'D':"1101", + 'E':"1110", + 'F':"1111" + } + binstr = "" + for i in strhex: + binstr = binstr + dic[i.upper()] + return binstr + +def strbin2dec(strbin): + """ + Convert a string representing a binary value into a + string representing the same value in decimal format. + """ + strdec = "0" + for i in range(1, strbin.__len__()+1): + strdec = str(int(strdec)+int(strbin[-i])*int(pow(2, i-1))) + return strdec + +def dec2bin(ndec): + """ + Convert a decimal number into a string representing + the same value in binary format. + """ + if ndec < 1: + return "0" + binary = [] + while ndec != 0: + binary.append(ndec%2) + ndec = ndec/2 + strbin = "" + binary.reverse() + for i in binary: + strbin = strbin+str(i) + return strbin + +def bin2hex(strbin): + """ + Convert a string representing a binary number into a string + representing the same value in hexadecimal format. + """ + dic = { "0000":"0", + "0001":"1", + "0010":"2", + "0011":"3", + "0100":"4", + "0101":"5", + "0110":"6", + "0111":"7", + "1000":"8", + "1001":"9", + "1010":"A", + "1011":"B", + "1100":"C", + "1101":"D", + "1110":"E", + "1111":"F" + } + while strbin.__len__()%4 != 0: + strbin = '0' + strbin + strh = "" + for i in range(0, strbin.__len__()/4): + strh = strh + dic[str(strbin[i*4:i*4+4])] + return strh diff --git a/plugins/rfid/serial/__init__.py b/plugins/rfid/serial/__init__.py new file mode 100644 index 0000000..681ad5c --- /dev/null +++ b/plugins/rfid/serial/__init__.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +#portable serial port access with python +#this is a wrapper module for different platform implementations +# +# (C)2001-2002 Chris Liechti +# this is distributed under a free software license, see license.txt + +VERSION = '2.4' + +import sys + +if sys.platform == 'cli': + from serialcli import * +else: + import os + #chose an implementation, depending on os + if os.name == 'nt': #sys.platform == 'win32': + from serialwin32 import * + elif os.name == 'posix': + from serialposix import * + elif os.name == 'java': + from serialjava import * + else: + raise Exception("Sorry: no implementation for your platform ('%s') available" % os.name) + diff --git a/plugins/rfid/serial/serialposix.py b/plugins/rfid/serial/serialposix.py new file mode 100644 index 0000000..174e2f7 --- /dev/null +++ b/plugins/rfid/serial/serialposix.py @@ -0,0 +1,492 @@ +#!/usr/bin/env python +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# module for serial IO for POSIX compatible systems, like Linux +# see __init__.py +# +# (C) 2001-2008 Chris Liechti +# this is distributed under a free software license, see license.txt +# +# parts based on code from Grant B. Edwards : +# ftp://ftp.visi.com/users/grante/python/PosixSerial.py +# references: http://www.easysw.com/~mike/serial/serial.html + +import sys, os, fcntl, termios, struct, select, errno +from serialutil import * + +#Do check the Python version as some constants have moved. +if (sys.hexversion < 0x020100f0): + import TERMIOS +else: + TERMIOS = termios + +if (sys.hexversion < 0x020200f0): + import FCNTL +else: + FCNTL = fcntl + +#try to detect the os so that a device can be selected... +plat = sys.platform.lower() + +if plat[:5] == 'linux': #Linux (confirmed) + def device(port): + return '/dev/ttyS%d' % port + +elif plat == 'cygwin': #cywin/win32 (confirmed) + def device(port): + return '/dev/com%d' % (port + 1) + +elif plat == 'openbsd3': #BSD (confirmed) + def device(port): + return '/dev/ttyp%d' % port + +elif plat[:3] == 'bsd' or \ + plat[:7] == 'freebsd' or \ + plat[:7] == 'openbsd' or \ + plat[:6] == 'darwin': #BSD (confirmed for freebsd4: cuaa%d) + def device(port): + return '/dev/cuad%d' % port + +elif plat[:6] == 'netbsd': #NetBSD 1.6 testing by Erk + def device(port): + return '/dev/dty%02d' % port + +elif plat[:4] == 'irix': #IRIX (partialy tested) + def device(port): + return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control + +elif plat[:2] == 'hp': #HP-UX (not tested) + def device(port): + return '/dev/tty%dp0' % (port+1) + +elif plat[:5] == 'sunos': #Solaris/SunOS (confirmed) + def device(port): + return '/dev/tty%c' % (ord('a')+port) + +elif plat[:3] == 'aix': #aix + def device(port): + return '/dev/tty%d' % (port) + +else: + #platform detection has failed... + print """don't know how to number ttys on this system. +! Use an explicit path (eg /dev/ttyS1) or send this information to +! the author of this module: + +sys.platform = %r +os.name = %r +serialposix.py version = %s + +also add the device name of the serial port and where the +counting starts for the first serial port. +e.g. 'first serial port: /dev/ttyS0' +and with a bit luck you can get this module running... +""" % (sys.platform, os.name, VERSION) + #no exception, just continue with a brave attempt to build a device name + #even if the device name is not correct for the platform it has chances + #to work using a string with the real device name as port paramter. + def device(portum): + return '/dev/ttyS%d' % portnum + #~ raise Exception, "this module does not run on this platform, sorry." + +#whats up with "aix", "beos", .... +#they should work, just need to know the device names. + + +#load some constants for later use. +#try to use values from TERMIOS, use defaults from linux otherwise +TIOCMGET = hasattr(TERMIOS, 'TIOCMGET') and TERMIOS.TIOCMGET or 0x5415 +TIOCMBIS = hasattr(TERMIOS, 'TIOCMBIS') and TERMIOS.TIOCMBIS or 0x5416 +TIOCMBIC = hasattr(TERMIOS, 'TIOCMBIC') and TERMIOS.TIOCMBIC or 0x5417 +TIOCMSET = hasattr(TERMIOS, 'TIOCMSET') and TERMIOS.TIOCMSET or 0x5418 + +#TIOCM_LE = hasattr(TERMIOS, 'TIOCM_LE') and TERMIOS.TIOCM_LE or 0x001 +TIOCM_DTR = hasattr(TERMIOS, 'TIOCM_DTR') and TERMIOS.TIOCM_DTR or 0x002 +TIOCM_RTS = hasattr(TERMIOS, 'TIOCM_RTS') and TERMIOS.TIOCM_RTS or 0x004 +#TIOCM_ST = hasattr(TERMIOS, 'TIOCM_ST') and TERMIOS.TIOCM_ST or 0x008 +#TIOCM_SR = hasattr(TERMIOS, 'TIOCM_SR') and TERMIOS.TIOCM_SR or 0x010 + +TIOCM_CTS = hasattr(TERMIOS, 'TIOCM_CTS') and TERMIOS.TIOCM_CTS or 0x020 +TIOCM_CAR = hasattr(TERMIOS, 'TIOCM_CAR') and TERMIOS.TIOCM_CAR or 0x040 +TIOCM_RNG = hasattr(TERMIOS, 'TIOCM_RNG') and TERMIOS.TIOCM_RNG or 0x080 +TIOCM_DSR = hasattr(TERMIOS, 'TIOCM_DSR') and TERMIOS.TIOCM_DSR or 0x100 +TIOCM_CD = hasattr(TERMIOS, 'TIOCM_CD') and TERMIOS.TIOCM_CD or TIOCM_CAR +TIOCM_RI = hasattr(TERMIOS, 'TIOCM_RI') and TERMIOS.TIOCM_RI or TIOCM_RNG +#TIOCM_OUT1 = hasattr(TERMIOS, 'TIOCM_OUT1') and TERMIOS.TIOCM_OUT1 or 0x2000 +#TIOCM_OUT2 = hasattr(TERMIOS, 'TIOCM_OUT2') and TERMIOS.TIOCM_OUT2 or 0x4000 +TIOCINQ = hasattr(TERMIOS, 'FIONREAD') and TERMIOS.FIONREAD or 0x541B + +TIOCM_zero_str = struct.pack('I', 0) +TIOCM_RTS_str = struct.pack('I', TIOCM_RTS) +TIOCM_DTR_str = struct.pack('I', TIOCM_DTR) + +TIOCSBRK = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427 +TIOCCBRK = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428 + +ASYNC_SPD_MASK = 0x1030 +ASYNC_SPD_CUST = 0x0030 + +baudrate_constants = { + 0: 0000000, # hang up + 50: 0000001, + 75: 0000002, + 110: 0000003, + 134: 0000004, + 150: 0000005, + 200: 0000006, + 300: 0000007, + 600: 0000010, + 1200: 0000011, + 1800: 0000012, + 2400: 0000013, + 4800: 0000014, + 9600: 0000015, + 19200: 0000016, + 38400: 0000017, + 57600: 0010001, + 115200: 0010002, + 230400: 0010003, + 460800: 0010004, + 500000: 0010005, + 576000: 0010006, + 921600: 0010007, + 1000000: 0010010, + 1152000: 0010011, + 1500000: 0010012, + 2000000: 0010013, + 2500000: 0010014, + 3000000: 0010015, + 3500000: 0010016, + 4000000: 0010017 +} + + +class Serial(SerialBase): + """Serial port class POSIX implementation. Serial port configuration is + done with termios and fcntl. Runs on Linux and many other Un*x like + systems.""" + + def open(self): + """Open port with current settings. This may throw a SerialException + if the port cannot be opened.""" + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + self.fd = None + #open + try: + self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK) + except Exception, msg: + self.fd = None + raise SerialException("could not open port %s: %s" % (self._port, msg)) + #~ fcntl.fcntl(self.fd, FCNTL.F_SETFL, 0) #set blocking + + try: + self._reconfigurePort() + except: + os.close(self.fd) + self.fd = None + else: + self._isOpen = True + #~ self.flushInput() + + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if self.fd is None: + raise SerialException("Can only operate on a valid port handle") + custom_baud = None + + vmin = vtime = 0 #timeout is done via select + if self._interCharTimeout is not None: + vmin = 1 + vtime = int(self._interCharTimeout * 10) + try: + iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self.fd) + except termios.error, msg: #if a port is nonexistent but has a /dev file, it'll fail here + raise SerialException("Could not configure port: %s" % msg) + #set up raw mode / no echo / binary + cflag |= (TERMIOS.CLOCAL|TERMIOS.CREAD) + lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL| + TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT + for flag in ('ECHOCTL', 'ECHOKE'): #netbsd workaround for Erk + if hasattr(TERMIOS, flag): + lflag &= ~getattr(TERMIOS, flag) + + oflag &= ~(TERMIOS.OPOST) + iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK) + if hasattr(TERMIOS, 'IUCLC'): + iflag &= ~TERMIOS.IUCLC + if hasattr(TERMIOS, 'PARMRK'): + iflag &= ~TERMIOS.PARMRK + + #setup baudrate + try: + ispeed = ospeed = getattr(TERMIOS,'B%s' % (self._baudrate)) + except AttributeError: + try: + ispeed = ospeed = baudrate_constants[self._baudrate] + except KeyError: + #~ raise ValueError('Invalid baud rate: %r' % self._baudrate) + # may need custom baud rate, it isnt in our list. + ispeed = ospeed = getattr(TERMIOS, 'B38400') + custom_baud = int(self._baudrate) # store for later + + #setup char len + cflag &= ~TERMIOS.CSIZE + if self._bytesize == 8: + cflag |= TERMIOS.CS8 + elif self._bytesize == 7: + cflag |= TERMIOS.CS7 + elif self._bytesize == 6: + cflag |= TERMIOS.CS6 + elif self._bytesize == 5: + cflag |= TERMIOS.CS5 + else: + raise ValueError('Invalid char len: %r' % self._bytesize) + #setup stopbits + if self._stopbits == STOPBITS_ONE: + cflag &= ~(TERMIOS.CSTOPB) + elif self._stopbits == STOPBITS_TWO: + cflag |= (TERMIOS.CSTOPB) + else: + raise ValueError('Invalid stopit specification: %r' % self._stopbits) + #setup parity + iflag &= ~(TERMIOS.INPCK|TERMIOS.ISTRIP) + if self._parity == PARITY_NONE: + cflag &= ~(TERMIOS.PARENB|TERMIOS.PARODD) + elif self._parity == PARITY_EVEN: + cflag &= ~(TERMIOS.PARODD) + cflag |= (TERMIOS.PARENB) + elif self._parity == PARITY_ODD: + cflag |= (TERMIOS.PARENB|TERMIOS.PARODD) + else: + raise ValueError('Invalid parity: %r' % self._parity) + #setup flow control + #xonxoff + if hasattr(TERMIOS, 'IXANY'): + if self._xonxoff: + iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) #|TERMIOS.IXANY) + else: + iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF|TERMIOS.IXANY) + else: + if self._xonxoff: + iflag |= (TERMIOS.IXON|TERMIOS.IXOFF) + else: + iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF) + #rtscts + if hasattr(TERMIOS, 'CRTSCTS'): + if self._rtscts: + cflag |= (TERMIOS.CRTSCTS) + else: + cflag &= ~(TERMIOS.CRTSCTS) + elif hasattr(TERMIOS, 'CNEW_RTSCTS'): #try it with alternate constant name + if self._rtscts: + cflag |= (TERMIOS.CNEW_RTSCTS) + else: + cflag &= ~(TERMIOS.CNEW_RTSCTS) + #XXX should there be a warning if setting up rtscts (and xonxoff etc) fails?? + + #buffer + #vmin "minimal number of characters to be read. = for non blocking" + if vmin < 0 or vmin > 255: + raise ValueError('Invalid vmin: %r ' % vmin) + cc[TERMIOS.VMIN] = vmin + #vtime + if vtime < 0 or vtime > 255: + raise ValueError('Invalid vtime: %r' % vtime) + cc[TERMIOS.VTIME] = vtime + #activate settings + termios.tcsetattr(self.fd, TERMIOS.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc]) + + # apply custom baud rate, if any + if custom_baud is not None: + import array + buf = array.array('i', [0] * 32) + + # get serial_struct + FCNTL.ioctl(self.fd, TERMIOS.TIOCGSERIAL, buf) + + # set custom divisor + buf[6] = buf[7] / custom_baud + + # update flags + buf[4] &= ~ASYNC_SPD_MASK + buf[4] |= ASYNC_SPD_CUST + + # set serial_struct + try: + res = FCNTL.ioctl(self.fd, TERMIOS.TIOCSSERIAL, buf) + except IOError: + raise ValueError('Failed to set custom baud rate: %r' % self._baudrate) + + def close(self): + """Close port""" + if self._isOpen: + if self.fd is not None: + os.close(self.fd) + self.fd = None + self._isOpen = False + + def makeDeviceName(self, port): + return device(port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str) + s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) + return struct.unpack('I',s)[0] + + def read(self, size=1): + """Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read.""" + if self.fd is None: raise portNotOpenError + read = '' + inp = None + if size > 0: + while len(read) < size: + #print "\tread(): size",size, "have", len(read) #debug + ready,_,_ = select.select([self.fd],[],[], self._timeout) + if not ready: + break #timeout + buf = os.read(self.fd, size-len(read)) + read = read + buf + if (self._timeout >= 0 or self._interCharTimeout > 0) and not buf: + break #early abort on timeout + return read + + def write(self, data): + """Output the given string over the serial port.""" + if self.fd is None: raise portNotOpenError + if not isinstance(data, str): + raise TypeError('expected str, got %s' % type(data)) + t = len(data) + d = data + while t > 0: + try: + if self._writeTimeout is not None and self._writeTimeout > 0: + _,ready,_ = select.select([],[self.fd],[], self._writeTimeout) + if not ready: + raise writeTimeoutError + n = os.write(self.fd, d) + if self._writeTimeout is not None and self._writeTimeout > 0: + _,ready,_ = select.select([],[self.fd],[], self._writeTimeout) + if not ready: + raise writeTimeoutError + d = d[n:] + t = t - n + except OSError,v: + if v.errno != errno.EAGAIN: + raise + + def flush(self): + """Flush of file like objects. In this case, wait until all data + is written.""" + self.drainOutput() + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if self.fd is None: + raise portNotOpenError + termios.tcflush(self.fd, TERMIOS.TCIFLUSH) + + def flushOutput(self): + """Clear output buffer, aborting the current output and + discarding all that is in the buffer.""" + if self.fd is None: + raise portNotOpenError + termios.tcflush(self.fd, TERMIOS.TCOFLUSH) + + def sendBreak(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given duration.""" + if self.fd is None: + raise portNotOpenError + termios.tcsendbreak(self.fd, int(duration/0.25)) + + def setBreak(self, level=1): + """Set break: Controls TXD. When active, to transmitting is possible.""" + if self.fd is None: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCSBRK) + else: + fcntl.ioctl(self.fd, TIOCCBRK) + + def setRTS(self, level=1): + """Set terminal status line: Request To Send""" + if self.fd is None: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str) + + def setDTR(self, level=1): + """Set terminal status line: Data Terminal Ready""" + if self.fd is None: raise portNotOpenError + if level: + fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str) + else: + fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str) + + def getCTS(self): + """Read terminal status line: Clear To Send""" + if self.fd is None: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_CTS != 0 + + def getDSR(self): + """Read terminal status line: Data Set Ready""" + if self.fd is None: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_DSR != 0 + + def getRI(self): + """Read terminal status line: Ring Indicator""" + if self.fd is None: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_RI != 0 + + def getCD(self): + """Read terminal status line: Carrier Detect""" + if self.fd is None: raise portNotOpenError + s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str) + return struct.unpack('I',s)[0] & TIOCM_CD != 0 + + # - - platform specific - - - - + + def drainOutput(self): + """internal - not portable!""" + if self.fd is None: raise portNotOpenError + termios.tcdrain(self.fd) + + def nonblocking(self): + """internal - not portable!""" + if self.fd is None: + raise portNotOpenError + fcntl.fcntl(self.fd, FCNTL.F_SETFL, FCNTL.O_NONBLOCK) + + def fileno(self): + """For easier of the serial port instance with select. + WARNING: this function is not portable to different platforms!""" + if self.fd is None: raise portNotOpenError + return self.fd + +if __name__ == '__main__': + s = Serial(0, + baudrate=19200, #baudrate + bytesize=EIGHTBITS, #number of databits + parity=PARITY_EVEN, #enable parity checking + stopbits=STOPBITS_ONE, #number of stopbits + timeout=3, #set a timeout value, None for waiting forever + xonxoff=0, #enable software flow control + rtscts=0, #enable RTS/CTS flow control + ) + s.setRTS(1) + s.setDTR(1) + s.flushInput() + s.flushOutput() + s.write('hello') + print repr(s.read(5)) + print s.inWaiting() + del s + diff --git a/plugins/rfid/serial/serialutil.py b/plugins/rfid/serial/serialutil.py new file mode 100644 index 0000000..fd466f2 --- /dev/null +++ b/plugins/rfid/serial/serialutil.py @@ -0,0 +1,400 @@ +#! python +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# (C) 2001-2008 Chris Liechti +# this is distributed under a free software license, see license.txt + +PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S' +STOPBITS_ONE, STOPBITS_TWO = (1, 2) +FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5,6,7,8) + +PARITY_NAMES = { + PARITY_NONE: 'None', + PARITY_EVEN: 'Even', + PARITY_ODD: 'Odd', + PARITY_MARK: 'Mark', + PARITY_SPACE:'Space', +} + +XON = chr(17) +XOFF = chr(19) + +#Python < 2.2.3 compatibility +try: + True +except: + True = 1 + False = not True + +class SerialException(Exception): + """Base class for serial port related exceptions.""" + +portNotOpenError = SerialException('Port not open') + +class SerialTimeoutException(SerialException): + """Write timeouts give an exception""" + +writeTimeoutError = SerialTimeoutException("Write timeout") + +class FileLike(object): + """An abstract file like class. + + This class implements readline and readlines based on read and + writelines based on write. + This class is used to provide the above functions for to Serial + port objects. + + Note that when the serial port was opened with _NO_ timeout that + readline blocks until it sees a newline (or the specified size is + reached) and that readlines would never return and therefore + refuses to work (it raises an exception in this case)! + """ + + def read(self, size): raise NotImplementedError + def write(self, s): raise NotImplementedError + + def readline(self, size=None, eol='\n'): + """read a line which is terminated with end-of-line (eol) character + ('\n' by default) or until timeout""" + line = '' + while 1: + c = self.read(1) + if c: + line += c #not very efficient but lines are usually not that long + if c == eol: + break + if size is not None and len(line) >= size: + break + else: + break + return line + + def readlines(self, sizehint=None, eol='\n'): + """read a list of lines, until timeout + sizehint is ignored""" + if self.timeout is None: + raise ValueError, "Serial port MUST have enabled timeout for this function!" + lines = [] + while 1: + line = self.readline(eol=eol) + if line: + lines.append(line) + if line[-1] != eol: #was the line received with a timeout? + break + else: + break + return lines + + def xreadlines(self, sizehint=None): + """just call readlines - here for compatibility""" + return self.readlines() + + def writelines(self, sequence): + for line in sequence: + self.write(line) + + def flush(self): + """flush of file like objects""" + pass + + # iterator for e.g. "for line in Serial(0): ..." usage + def next(self): + line = self.readline() + if not line: raise StopIteration + return line + + def __iter__(self): + return self + + +class SerialBase(FileLike): + """Serial port base class. Provides __init__ function and properties to + get/set port settings.""" + + #default values, may be overriden in subclasses that do not support all values + BAUDRATES = (50,75,110,134,150,200,300,600,1200,1800,2400,4800,9600, + 19200,38400,57600,115200,230400,460800,500000,576000,921600, + 1000000,1152000,1500000,2000000,2500000,3000000,3500000,4000000) + BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS) + PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD) + STOPBITS = (STOPBITS_ONE, STOPBITS_TWO) + + def __init__(self, + port = None, #number of device, numbering starts at + #zero. if everything fails, the user + #can specify a device string, note + #that this isn't portable anymore + #port will be opened if one is specified + baudrate=9600, #baudrate + bytesize=EIGHTBITS, #number of databits + parity=PARITY_NONE, #enable parity checking + stopbits=STOPBITS_ONE, #number of stopbits + timeout=None, #set a timeout value, None to wait forever + xonxoff=0, #enable software flow control + rtscts=0, #enable RTS/CTS flow control + writeTimeout=None, #set a timeout for writes + dsrdtr=None, #None: use rtscts setting, dsrdtr override if true or false + interCharTimeout=None #Inter-character timeout, None to disable + ): + """Initialize comm port object. If a port is given, then the port will be + opened immediately. Otherwise a Serial port object in closed state + is returned.""" + + self._isOpen = False + self._port = None #correct value is assigned below trough properties + self._baudrate = None #correct value is assigned below trough properties + self._bytesize = None #correct value is assigned below trough properties + self._parity = None #correct value is assigned below trough properties + self._stopbits = None #correct value is assigned below trough properties + self._timeout = None #correct value is assigned below trough properties + self._writeTimeout = None #correct value is assigned below trough properties + self._xonxoff = None #correct value is assigned below trough properties + self._rtscts = None #correct value is assigned below trough properties + self._dsrdtr = None #correct value is assigned below trough properties + self._interCharTimeout = None #correct value is assigned below trough properties + + #assign values using get/set methods using the properties feature + self.port = port + self.baudrate = baudrate + self.bytesize = bytesize + self.parity = parity + self.stopbits = stopbits + self.timeout = timeout + self.writeTimeout = writeTimeout + self.xonxoff = xonxoff + self.rtscts = rtscts + self.dsrdtr = dsrdtr + self.interCharTimeout = interCharTimeout + + if port is not None: + self.open() + + def isOpen(self): + """Check if the port is opened.""" + return self._isOpen + + # - - - - - - - - - - - - - - - - - - - - - - - - + + #TODO: these are not realy needed as the is the BAUDRATES etc attribute... + #maybe i remove them before the final release... + + def getSupportedBaudrates(self): + return [(str(b), b) for b in self.BAUDRATES] + + def getSupportedByteSizes(self): + return [(str(b), b) for b in self.BYTESIZES] + + def getSupportedStopbits(self): + return [(str(b), b) for b in self.STOPBITS] + + def getSupportedParities(self): + return [(PARITY_NAMES[b], b) for b in self.PARITIES] + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def setPort(self, port): + """Change the port. The attribute portstr is set to a string that + contains the name of the port.""" + + was_open = self._isOpen + if was_open: self.close() + if port is not None: + if type(port) in [type(''), type(u'')]: #strings are taken directly + self.portstr = port + else: + self.portstr = self.makeDeviceName(port) + else: + self.portstr = None + self._port = port + if was_open: self.open() + + def getPort(self): + """Get the current port setting. The value that was passed on init or using + setPort() is passed back. See also the attribute portstr which contains + the name of the port as a string.""" + return self._port + + port = property(getPort, setPort, doc="Port setting") + + + def setBaudrate(self, baudrate): + """Change baudrate. It raises a ValueError if the port is open and the + baudrate is not possible. If the port is closed, then tha value is + accepted and the exception is raised when the port is opened.""" + #~ if baudrate not in self.BAUDRATES: raise ValueError("Not a valid baudrate: %r" % baudrate) + try: + self._baudrate = int(baudrate) + except TypeError: + raise ValueError("Not a valid baudrate: %r" % (baudrate,)) + else: + if self._isOpen: self._reconfigurePort() + + def getBaudrate(self): + """Get the current baudrate setting.""" + return self._baudrate + + baudrate = property(getBaudrate, setBaudrate, doc="Baudrate setting") + + + def setByteSize(self, bytesize): + """Change byte size.""" + if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: %r" % (bytesize,)) + self._bytesize = bytesize + if self._isOpen: self._reconfigurePort() + + def getByteSize(self): + """Get the current byte size setting.""" + return self._bytesize + + bytesize = property(getByteSize, setByteSize, doc="Byte size setting") + + + def setParity(self, parity): + """Change parity setting.""" + if parity not in self.PARITIES: raise ValueError("Not a valid parity: %r" % (parity,)) + self._parity = parity + if self._isOpen: self._reconfigurePort() + + def getParity(self): + """Get the current parity setting.""" + return self._parity + + parity = property(getParity, setParity, doc="Parity setting") + + + def setStopbits(self, stopbits): + """Change stopbits size.""" + if stopbits not in self.STOPBITS: raise ValueError("Not a valid stopbit size: %r" % (stopbits,)) + self._stopbits = stopbits + if self._isOpen: self._reconfigurePort() + + def getStopbits(self): + """Get the current stopbits setting.""" + return self._stopbits + + stopbits = property(getStopbits, setStopbits, doc="Stopbits setting") + + + def setTimeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,)) + try: + timeout + 1 #test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % (timeout,)) + + self._timeout = timeout + if self._isOpen: self._reconfigurePort() + + def getTimeout(self): + """Get the current timeout setting.""" + return self._timeout + + timeout = property(getTimeout, setTimeout, doc="Timeout setting for read()") + + + def setWriteTimeout(self, timeout): + """Change timeout setting.""" + if timeout is not None: + if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,)) + try: + timeout + 1 #test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % timeout) + + self._writeTimeout = timeout + if self._isOpen: self._reconfigurePort() + + def getWriteTimeout(self): + """Get the current timeout setting.""" + return self._writeTimeout + + writeTimeout = property(getWriteTimeout, setWriteTimeout, doc="Timeout setting for write()") + + + def setXonXoff(self, xonxoff): + """Change XonXoff setting.""" + self._xonxoff = xonxoff + if self._isOpen: self._reconfigurePort() + + def getXonXoff(self): + """Get the current XonXoff setting.""" + return self._xonxoff + + xonxoff = property(getXonXoff, setXonXoff, doc="Xon/Xoff setting") + + def setRtsCts(self, rtscts): + """Change RtsCts flow control setting.""" + self._rtscts = rtscts + if self._isOpen: self._reconfigurePort() + + def getRtsCts(self): + """Get the current RtsCts flow control setting.""" + return self._rtscts + + rtscts = property(getRtsCts, setRtsCts, doc="RTS/CTS flow control setting") + + def setDsrDtr(self, dsrdtr=None): + """Change DsrDtr flow control setting.""" + if dsrdtr is None: + #if not set, keep backwards compatibility and follow rtscts setting + self._dsrdtr = self._rtscts + else: + #if defined independently, follow its value + self._dsrdtr = dsrdtr + if self._isOpen: self._reconfigurePort() + + def getDsrDtr(self): + """Get the current DsrDtr flow control setting.""" + return self._dsrdtr + + dsrdtr = property(getDsrDtr, setDsrDtr, "DSR/DTR flow control setting") + + def setInterCharTimeout(self, interCharTimeout): + """Change inter-character timeout setting.""" + if interCharTimeout is not None: + if interCharTimeout < 0: raise ValueError("Not a valid timeout: %r" % interCharTimeout) + try: + interCharTimeout + 1 #test if it's a number, will throw a TypeError if not... + except TypeError: + raise ValueError("Not a valid timeout: %r" % interCharTimeout) + + self._interCharTimeout = interCharTimeout + if self._isOpen: self._reconfigurePort() + + def getInterCharTimeout(self): + """Get the current inter-character timeout setting.""" + return self._interCharTimeout + + interCharTimeout = property(getInterCharTimeout, setInterCharTimeout, doc="Inter-character timeout setting for read()") + + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def __repr__(self): + """String representation of the current port settings and its state.""" + return "%s(port=%r, baudrate=%r, bytesize=%r, parity=%r, stopbits=%r, timeout=%r, xonxoff=%r, rtscts=%r, dsrdtr=%r)" % ( + self.__class__.__name__, + id(self), + self._isOpen, + self.portstr, + self.baudrate, + self.bytesize, + self.parity, + self.stopbits, + self.timeout, + self.xonxoff, + self.rtscts, + self.dsrdtr, + ) + +if __name__ == '__main__': + s = SerialBase() + print s.portstr + print s.getSupportedBaudrates() + print s.getSupportedByteSizes() + print s.getSupportedParities() + print s.getSupportedStopbits() + print s diff --git a/plugins/rfid/tis2000.py b/plugins/rfid/tis2000.py new file mode 100644 index 0000000..91d1991 --- /dev/null +++ b/plugins/rfid/tis2000.py @@ -0,0 +1,252 @@ +from device import RFIDDevice +from serial import Serial +import dbus +from dbus.mainloop.glib import DBusGMainLoop +import gobject +import re +from time import sleep + +HAL_SERVICE = 'org.freedesktop.Hal' +HAL_MGR_PATH = '/org/freedesktop/Hal/Manager' +HAL_MGR_IFACE = 'org.freedesktop.Hal.Manager' +HAL_DEV_IFACE = 'org.freedesktop.Hal.Device' +REGEXP_SERUSB = '\/org\/freedesktop\/Hal\/devices\/usb_device['\ + 'a-z,A-Z,0-9,_]*serial_usb_[0-9]' + +STATE_WAITING = 0 +STATE_WAITING2 = 1 +STATE_READING = 2 + +class RFIDReader(RFIDDevice): + """ + TIS-2000 interface. + """ + + def __init__(self): + + RFIDDevice.__init__(self) + self.last_tag = "" + self.ser = Serial() + self.device = '' + self.device_path = '' + self._connected = False + self._state = STATE_WAITING + + loop = DBusGMainLoop() + self.bus = dbus.SystemBus(mainloop=loop) + hmgr_iface = dbus.Interface(self.bus.get_object(HAL_SERVICE, + HAL_MGR_PATH), HAL_MGR_IFACE) + + hmgr_iface.connect_to_signal('DeviceRemoved', self._device_removed_cb) + + def get_present(self): + """ + Checks if TI-S2000 device is present. + Returns True if so, False otherwise. + """ + hmgr_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, HAL_MGR_PATH), + HAL_MGR_IFACE) + tiusb_devices = set(hmgr_if.FindDeviceStringMatch('serial.type', + 'usb')) & set(hmgr_if.FindDeviceStringMatch( + 'info.product', 'TUSB3410 Microcontroller')) + for i in tiusb_devices: + tiusb_if = dbus.Interface(self.bus.get_object(HAL_SERVICE, i), + HAL_DEV_IFACE) + if tiusb_if.PropertyExists('linux.device_file'): + self.device = str(tiusb_if.GetProperty('linux.device_file')) + self.device_path = i + return True + return False + + def do_connect(self): + """ + Connects to the device. + Returns True if successfull, False otherwise. + """ + retval = False + if self.get_present(): + try: + self.ser = Serial(self.device, 9600, timeout=0.1) + self._connected = True + self._escape() + self._clear() + self._format() + gobject.idle_add(self._loop) + retval = True + except: + self._connected = False + return retval + + def do_disconnect(self): + """ + Disconnect from the device. + """ + self.ser.close() + self._connected = False + + def read_tag(self): + """ + Returns the last read value. + """ + return self.last_tag + + def write_tag(self, hexval): + """ + Usage: write_tag(hexval) + + Writes the hexadecimal string "hexval" into the tag. + Returns True if successfull, False otherwise. + """ + #self.ser.flushInput() + reg = re.compile('([^0-9A-F]+)') + if not (hexval.__len__() == 16 and reg.findall(hexval) == []): + return False + self.ser.read(100) + self.ser.write('P') + for i in hexval: + self.ser.write(i) + sleep(1) + resp = self.ser.read(64) + resp = resp.split()[0] + if resp == "P0": + return True + else: + return False + + def _escape(self): + """ + Sends the scape command to the TIS-2000 device. + """ + try: + #self.ser.flushInput() + self.ser.read(100) + self.ser.write('\x1B') + resp = self.ser.read() + if resp == 'E': + return True + else: + return False + except: + return False + + def _format(self): + """ + Sends the format command to the TIS-2000 device. + """ + try: + #self.ser.flushInput() + self.ser.read(100) + self.ser.write('F') + resp = self.ser.read() + if resp == 'F': + return True + else: + return False + except: + return False + + def _clear(self): + """ + Sends the clear command to the TIS-2000 device. + """ + try: + #self.ser.flushInput() + self.ser.read(100) + self.ser.write('C') + resp = self.ser.read() + if resp == 'C': + return True + else: + return False + except: + return False + + def get_version(self): + """ + Sends the version command to the TIS-2000 device and returns + a string with the device version. + """ + #self.ser.flushInput() + self.ser.read(100) + self.ser.write('V') + version = [] + tver = "" + while 1: + resp = self.ser.read() + if resp == '\x0A' or resp == '': + break + if resp != '\n' and resp != '\r': + version.append(resp) + for i in version: + tver = tver + i + if tver != "": + return tver + return "Unknown" + + def _device_removed_cb(self, path): + """ + Called when a device is removed. + Checks if the removed device is itself and emits the "disconnected" + signal if so. + """ + if path == self.device_path: + self.device_path = '' + self.ser.close() + self._connected = False + self.emit("disconnected","TIS-2000") + + def _loop(self): + """ + Threaded loop for reading data sent from the TIS-2000. + """ + if not self._connected: + return False + + if self._state is STATE_WAITING: + data = self.ser.read() + if data in ['W', 'R']: + self._state = STATE_WAITING2 + return True + + elif self._state is STATE_WAITING2: + data = self.ser.read() + if data.isspace(): + self._state = STATE_READING + else: + self._clear() + self._state = STATE_WAITING + return True + + elif self._state is STATE_READING: + data = self.ser.read(16) + if data.__len__() < 16: + self._clear() + self._state = STATE_WAITING + else: + reg = re.compile('([^0-9A-F]+)') + if reg.findall(data) == []: + self.emit("tag-read", data) + self.last_tag = data + self._clear() + self._state = STATE_WAITING + return True + return True + +# Testing +#if __name__ == '__main__': +# def handler(device, idhex): +# """ +# Handler for "tag-read" signal. +# Prints the tag id. +# """ +# print "ID: ", idhex +# +# dev = RFIDReader() +# if dev.get_present(): +# dev.do_connect() +# dev.connect('tag-read', handler) +# else: +# print "Not connected" +# +# mloop = gobject.MainLoop() +# mloop.run() diff --git a/plugins/rfid/utils.py b/plugins/rfid/utils.py new file mode 100644 index 0000000..94e5540 --- /dev/null +++ b/plugins/rfid/utils.py @@ -0,0 +1,98 @@ +# utils.py - Helper functions for tis2000.py +# Copyright (C) 2010 Emiliano Pastorino +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +def strhex2bin(strhex): + """ + Convert a string representing an hex value into a + string representing the same value in binary format. + """ + dic = { '0':"0000", + '1':"0001", + '2':"0010", + '3':"0011", + '4':"0100", + '5':"0101", + '6':"0110", + '7':"0111", + '8':"1000", + '9':"1001", + 'A':"1010", + 'B':"1011", + 'C':"1100", + 'D':"1101", + 'E':"1110", + 'F':"1111" + } + binstr = "" + for i in strhex: + binstr = binstr + dic[i.upper()] + return binstr + +def strbin2dec(strbin): + """ + Convert a string representing a binary value into a + string representing the same value in decimal format. + """ + strdec = "0" + for i in range(1, strbin.__len__()+1): + strdec = str(int(strdec)+int(strbin[-i])*int(pow(2, i-1))) + return strdec + +def dec2bin(ndec): + """ + Convert a decimal number into a string representing + the same value in binary format. + """ + if ndec < 1: + return "0" + binary = [] + while ndec != 0: + binary.append(ndec%2) + ndec = ndec/2 + strbin = "" + binary.reverse() + for i in binary: + strbin = strbin+str(i) + return strbin + +def bin2hex(strbin): + """ + Convert a string representing a binary number into a string + representing the same value in hexadecimal format. + """ + dic = { "0000":"0", + "0001":"1", + "0010":"2", + "0011":"3", + "0100":"4", + "0101":"5", + "0110":"6", + "0111":"7", + "1000":"8", + "1001":"9", + "1010":"A", + "1011":"B", + "1100":"C", + "1101":"D", + "1110":"E", + "1111":"F" + } + while strbin.__len__()%4 != 0: + strbin = '0' + strbin + strh = "" + for i in range(0, strbin.__len__()/4): + strh = strh + dic[str(strbin[i*4:i*4+4])] + return strh diff --git a/plugins/turtle_blocks_extras/__init__.py b/plugins/turtle_blocks_extras/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/turtle_blocks_extras/__init__.py diff --git a/plugins/turtle_blocks_extras/__init__.pyc b/plugins/turtle_blocks_extras/__init__.pyc new file mode 100644 index 0000000..d5c417b --- /dev/null +++ b/plugins/turtle_blocks_extras/__init__.pyc Binary files differ diff --git a/plugins/turtle_blocks_extras/icons/extrasoff.svg b/plugins/turtle_blocks_extras/icons/extrasoff.svg new file mode 100644 index 0000000..7975a9d --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/extrasoff.svg @@ -0,0 +1,75 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/extrason.svg b/plugins/turtle_blocks_extras/icons/extrason.svg new file mode 100644 index 0000000..7ee08bf --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/extrason.svg @@ -0,0 +1,158 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/mediaoff.svg b/plugins/turtle_blocks_extras/icons/mediaoff.svg new file mode 100644 index 0000000..b2f460a --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/mediaoff.svg @@ -0,0 +1,46 @@ + + + + + +` + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/mediaon.svg b/plugins/turtle_blocks_extras/icons/mediaon.svg new file mode 100644 index 0000000..27dd516 --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/mediaon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/portfoliooff.svg b/plugins/turtle_blocks_extras/icons/portfoliooff.svg new file mode 100644 index 0000000..d404bab --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/portfoliooff.svg @@ -0,0 +1,72 @@ + + + + + + + image/svg+xml + + + + + + + + + +` + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/portfolioon.svg b/plugins/turtle_blocks_extras/icons/portfolioon.svg new file mode 100644 index 0000000..e174ee2 --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/portfolioon.svg @@ -0,0 +1,284 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/turtle_blocks_extras/icons/sensoroff.svg b/plugins/turtle_blocks_extras/icons/sensoroff.svg new file mode 100644 index 0000000..ec55f03 --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/sensoroff.svg @@ -0,0 +1,41 @@ + + + +image/svg+xml + + + + + \ No newline at end of file diff --git a/plugins/turtle_blocks_extras/icons/sensoron.svg b/plugins/turtle_blocks_extras/icons/sensoron.svg new file mode 100644 index 0000000..e902656 --- /dev/null +++ b/plugins/turtle_blocks_extras/icons/sensoron.svg @@ -0,0 +1,41 @@ + + + +image/svg+xml + + + + + \ No newline at end of file diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py new file mode 100644 index 0000000..fbd5db5 --- /dev/null +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py @@ -0,0 +1,1213 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +from time import time +import os.path + +try: + from sugar.datastore import datastore +except ImportError: + pass + +from plugins.plugin import Plugin +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import primitive_dictionary, logoerror, \ + media_blocks_dictionary +from TurtleArt.taconstants import DEFAULT_SCALE, ICON_SIZE, CONSTANTS +from TurtleArt.tautils import convert, round_int, debug_output +from TurtleArt.tajail import myfunc, myfunc_import + + +def _num_type(x): + """ Is x a number type? """ + if type(x) == int: + return True + if type(x) == float: + return True + if type(x) == ord: + return True + return False + + +def _string_to_num(x): + """ Try to comvert a string to a number """ + xx = convert(x.replace(self.tw.decimal_point, '.'), float) + if type(xx) is float: + return xx + else: + xx, xflag = chr_to_ord(x) + if xflag: + return xx + else: + raise logoerror("#syntaxerror") + + +def _millisecond(): + """ Current time in milliseconds """ + return time() * 1000 + + +class Turtle_blocks_extras(Plugin): + """ a class for defining the extra palettes that distinguish Turtle Blocks + from Turtle Art """ + + def __init__(self, parent): + self.tw = parent + + def setup(self): + self.heap = self.tw.lc.heap + self.keyboard = self.tw.lc.keyboard + self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale) + + # set up Turtle Block palettes + self._flow_palette() + + self._media_palette() + + self._sensor_palette() + + self._extras_palette() + + self._portfolio_palette() + + # Palette definitions + + def _flow_palette(self): + palette = make_palette('flow', + colors=["#FFC000", "#A08000"], + help_string=_('Palette of flow operators')) + + # macro + palette.add_block('while', + style='flow-style-boolean', + label=_('while'), + help_string=_('do-while-True operator that uses \ +boolean operators from Numbers palette')) + + # macro + palette.add_block('until', + style='flow-style-boolean', + label=_('until'), + help_string=_('do-until-True operator that uses \ +boolean operators from Numbers palette')) + + def _media_palette(self): + + palette = make_palette('media', + colors=["#A0FF00", "#80A000"], + help_string=_('Palette of media objects')) + + palette.add_block('journal', + style='box-style-media', + label=' ', + default='None', + special_name=_('journal'), + help_string=_('Sugar Journal media object')) + + palette.add_block('audio', + style='box-style-media', + label=' ', + special_name=_('audio'), + default='None', + help_string=_('Sugar Journal audio object')) + + palette.add_block('video', + style='box-style-media', + label=' ', + special_name=_('video'), + default='None', + help_string=_('Sugar Journal video object')) + + palette.add_block('description', + style='box-style-media', + label=' ', + special_name=_('description'), + default='None', + help_string=_('Sugar Journal description field')) + + palette.add_block('string', + style='box-style', + label=_('text'), + default=_('text'), + special_name='', + help_string=_('string value')) + + primitive_dictionary['show'] = self._prim_show + palette.add_block('show', + style='basic-style-1arg', + label=_('show'), + default=_('text'), + prim_name='show', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('show', 1, + lambda self, x: primitive_dictionary['show'](x, True)) + + palette.add_block('showaligned', + hidden=True, + style='basic-style-1arg', + label=_('show aligned'), + default=_('text'), + prim_name='showaligned', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('showaligned', 1, + lambda self, x: primitive_dictionary['show'](x, False)) + + # deprecated + primitive_dictionary['write'] = self._prim_write + palette.add_block('write', + hidden=True, + style='basic-style-1arg', + label=_('show'), + default=[_('text'), 32], + prim_name='write', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('write', 2, + lambda self, x, y: primitive_dictionary['write'](x, y)) + + primitive_dictionary['setscale'] = self._prim_setscale + palette.add_block('setscale', + style='basic-style-1arg', + label=_('set scale'), + prim_name='setscale', + default=33, + logo_command='setlabelheight', + help_string=_('sets the scale of media')) + self.tw.lc.def_prim('setscale', 1, + lambda self, x: primitive_dictionary['setscale'](x)) + + primitive_dictionary['savepix'] = self._prim_save_picture + palette.add_block('savepix', + style='basic-style-1arg', + label=_('save picture'), + prim_name='savepix', + default=_('picture name'), + help_string=_('saves a picture to the Sugar \ +Journal')) + self.tw.lc.def_prim('savepix', 1, + lambda self, x: primitive_dictionary['savepix'](x)) + + primitive_dictionary['savesvg'] = self._prim_save_svg + palette.add_block('savesvg', + style='basic-style-1arg', + label=_('save SVG'), + prim_name='savesvg', + default=_('picture name'), + help_string=_('saves turtle graphics as an SVG file \ +in the Sugar Journal')) + self.tw.lc.def_prim('savesvg', 1, + lambda self, x: primitive_dictionary['savesvg'](x)) + + palette.add_block('scale', + style='box-style', + label=_('scale'), + prim_name='scale', + value_block=True, + logo_command='labelsize', + help_string=_('holds current scale value')) + self.tw.lc.def_prim('scale', 0, lambda self: self.tw.lc.scale) + + palette.add_block('mediawait', + style='basic-style-extended-vertical', + label=_('media wait'), + prim_name='mediawait', + help_string=_('wait for current video or audio to \ +complete')) + self.tw.lc.def_prim('mediawait', 0, self.tw.lc.media_wait, True) + + def _sensor_palette(self): + + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['kbinput'] = self._prim_kbinput + palette.add_block('kbinput', + style='basic-style-extended-vertical', + label=_('query keyboard'), + prim_name='kbinput', + help_string=_('query for keyboard input (results \ +stored in keyboard block)')) + self.tw.lc.def_prim('kbinput', 0, + lambda self: primitive_dictionary['kbinput']()) + + palette.add_block('keyboard', + style='box-style', + label=_('keyboard'), + prim_name='keyboard', + value_block=True, + logo_command='make "keyboard readchar', + help_string=_('holds results of query-keyboard \ +block')) + self.tw.lc.def_prim('keyboard', 0, lambda self: self.tw.lc.keyboard) + + primitive_dictionary['readpixel'] = self._prim_readpixel + palette.add_block('readpixel', + style='basic-style-extended-vertical', + label=_('read pixel'), + prim_name='readpixel', + logo_command=':keyboard', + help_string=_('RGB color under the turtle is pushed \ +to the stack')) + self.tw.lc.def_prim('readpixel', 0, + lambda self: primitive_dictionary['readpixel']()) + + primitive_dictionary['see'] = self._prim_see + palette.add_block('see', + style='box-style', + label=_('turtle sees'), + prim_name='see', + help_string=_('returns the color that the turtle \ +"sees"')) + self.tw.lc.def_prim('see', 0, + lambda self: primitive_dictionary['see']()) + + primitive_dictionary['time'] = self._prim_time + palette.add_block('time', + style='box-style', + label=_('time'), + prim_name='time', + value_block=True, + help_string=_('elapsed time (in seconds) since \ +program started')) + self.tw.lc.def_prim('time', 0, + lambda self: primitive_dictionary['time']()) + + def _extras_palette(self): + + palette = make_palette('extras', + colors=["#FF0000", "#A00000"], + help_string=_('Palette of extra options')) + + primitive_dictionary['push'] = self._prim_push + palette.add_block('push', + style='basic-style-1arg', + label=_('push'), + prim_name='push', + logo_command='tapush', + help_string=_('pushes value onto FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('push', 1, + lambda self, x: primitive_dictionary['push'](x)) + + primitive_dictionary['printheap'] = self._prim_printheap + palette.add_block('printheap', + style='basic-style-extended-vertical', + label=_('show heap'), + prim_name='printheap', + logo_command='taprintheap', + help_string=_('shows values in FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('printheap', 0, + lambda self: primitive_dictionary['printheap']()) + + primitive_dictionary['clearheap'] = self._prim_emptyheap + palette.add_block('clearheap', + style='basic-style-extended-vertical', + label=_('empty heap'), + prim_name='clearheap', + logo_command='taclearheap', + help_string=_('emptys FILO (first-in-last-out \ +heap)')) + self.tw.lc.def_prim('clearheap', 0, + lambda self: primitive_dictionary['clearheap']()) + + primitive_dictionary['pop'] = self._prim_pop + palette.add_block('pop', + style='box-style', + label=_('pop'), + prim_name='pop', + value_block=True, + logo_command='tapop', + help_string=_('pops value off FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('pop', 0, + lambda self: primitive_dictionary['pop']()) + + primitive_dictionary['print'] = self._prim_print + palette.add_block('comment', + style='basic-style-1arg', + label=_('comment'), + prim_name='comment', + default=_('comment'), + help_string=_('places a comment in your code')) + self.tw.lc.def_prim('comment', 1, + lambda self, x: primitive_dictionary['print'](x, True)) + + palette.add_block('print', + style='basic-style-1arg', + label=_('print'), + prim_name='print', + logo_command='label', + help_string=_('prints value in status block at \ +bottom of the screen')) + self.tw.lc.def_prim('print', 1, + lambda self, x: primitive_dictionary['print'](x, False)) + + primitive_dictionary['myfunction'] = self._prim_myfunction + palette.add_block('myfunc1arg', + style='number-style-var-arg', + label=[_('Python'), 'f(x)', 'x'], + prim_name='myfunction', + default=['x', 100], + help_string=_('a programmable block: used to add \ +advanced single-variable math equations, e.g., sin(x)')) + self.tw.lc.def_prim('myfunction', 2, + lambda self, f, x: primitive_dictionary['myfunction'](f, [x])) + + palette.add_block('myfunc2arg', + hidden=True, + style='number-style-var-arg', + label=[_('Python'), 'f(x,y)', 'x'], + prim_name='myfunction2', + default=['x+y', 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sqrt(x*x+y*y)')) + self.tw.lc.def_prim('myfunction2', 3, + lambda self, f, x, y: primitive_dictionary['myfunction']( + f, [x, y])) + + palette.add_block('myfunc3arg', + hidden=True, + style='number-style-var-arg', + label=[_('Python'), 'f(x,y,z)', 'x'], + prim_name='myfunction3', + default=['x+y+z', 100, 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sin(x+y+z)')) + self.tw.lc.def_prim('myfunction3', 4, + lambda self, f, x, y, z: primitive_dictionary['myfunction']( + f, [x, y, z])) + + primitive_dictionary['userdefined'] = self._prim_myblock + palette.add_block('userdefined', + style='basic-style-var-arg', + label=' ', + prim_name='userdefined', + special_name=_('Python block'), + default=100, + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined', 1, + lambda self, x: primitive_dictionary['userdefined']([x])) + + palette.add_block('userdefined2args', + hidden=True, + style='basic-style-var-arg', + label=' ', + prim_name='userdefined2', + special_name=_('Python block'), + default=[100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined2', 2, + lambda self, x, y: primitive_dictionary['userdefined']([x, y])) + + palette.add_block('userdefined3args', + hidden=True, + style='basic-style-var-arg', + label=' ', + prim_name='userdefined3', + special_name=_('Python block'), + default=[100, 100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined3', 3, + lambda self, x, y, z: primitive_dictionary['userdefined']( + [x, y, z])) + + palette.add_block('cartesian', + style='basic-style-extended-vertical', + label=_('Cartesian'), + prim_name='cartesian', + help_string=_('displays Cartesian coordinates')) + self.tw.lc.def_prim('cartesian', 0, + lambda self: self.tw.set_cartesian(True)) + + palette.add_block('polar', + style='basic-style-extended-vertical', + label=_('polar'), + prim_name='polar', + help_string=_('displays polar coordinates')) + self.tw.lc.def_prim('polar', 0, + lambda self: self.tw.set_polar(True)) + + palette.add_block('addturtle', + style='basic-style-1arg', + label=_('turtle'), + prim_name='turtle', + default=1, + help_string=_('chooses which turtle to command')) + self.tw.lc.def_prim('turtle', 1, + lambda self, x: self.tw.canvas.set_turtle(x)) + + primitive_dictionary['skin'] = self._prim_reskin + palette.add_block('skin', + hidden=True, + style='basic-style-1arg', + label=_('turtle shell'), + prim_name='skin', + help_string=_("put a custom 'shell' on the turtle")) + self.tw.lc.def_prim('skin', 1, + lambda self, x: primitive_dictionary['skin'](x)) + + # macro + palette.add_block('reskin', + style='basic-style-1arg', + label=_('turtle shell'), + help_string=_("put a custom 'shell' on the turtle")) + + palette.add_block('sandwichtop_no_label', + style='collapsible-top-no-label', + label=[' ', ' '], + special_name=_('top'), + prim_name='nop', + help_string=_('top of a collapsed stack')) + + palette.add_block('sandwichbottom', + style='collapsible-bottom', + label=[' ', ' '], + prim_name='nop', + special_name=_('bottom'), + help_string=_('bottom of a collapsible stack')) + + palette.add_block('sandwichcollapsed', + hidden=True, + style='invisible', + label=' ', + prim_name='nop', + help_string=_('bottom block in a collapsed stack: \ +click to open')) + + # deprecated blocks + palette.add_block('sandwichtop', + hidden=True, + style='collapsible-top', + label=_('top of stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm', + hidden=True, + style='collapsible-top-no-arm', + label=_('top of a collapsible stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm_no_label', + hidden=True, + style='collapsible-top-no-arm-no-label', + label=[' ', _('click to open')], + prim_name='nop', + help_string=_('top of stack')) + + def _portfolio_palette(self): + + palette = make_palette('portfolio', + colors=["#0606FF", "#0606A0"], + help_string=_('Palette of presentation templates')) + + palette.add_block('hideblocks', + style='basic-style-extended-vertical', + label=_('hide blocks'), + prim_name='hideblocks', + help_string=_('declutters canvas by hiding blocks')) + self.tw.lc.def_prim('hideblocks', 0, lambda self: self.tw.hideblocks()) + + palette.add_block('showblocks', + style='basic-style-extended-vertical', + label=_('show blocks'), + prim_name='showblocks', + help_string=_('restores hidden blocks')) + self.tw.lc.def_prim('showblocks', 0, lambda self: self.tw.showblocks()) + + palette.add_block('fullscreen', + style='basic-style-extended-vertical', + label=_('full screen'), + prim_name='fullscreen', + help_string=_('hides the Sugar toolbars')) + self.tw.lc.def_prim('fullscreen', 0, + lambda self: self.tw.set_fullscreen()) + + primitive_dictionary['bulletlist'] = self._prim_list + palette.add_block('list', + hidden=True, + style='bullet-style', + label=_('list'), + prim_name='bulletlist', + default=['∙ ', '∙ '], + help_string=_('presentation bulleted list')) + self.tw.lc.def_prim('bulletlist', 1, + primitive_dictionary['bulletlist'], True) + + # macros + palette.add_block('picturelist', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: list of \ +bullets')) + + palette.add_block('picture1x1a', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (no description)')) + + palette.add_block('picture1x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (with description)')) + + palette.add_block('picture2x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select four \ +Journal objects')) + + palette.add_block('picture2x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + palette.add_block('picture1x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + # Display-dependent constants + palette.add_block('leftpos', + style='box-style', + label=_('left'), + prim_name='lpos', + logo_command='lpos', + help_string=_('xcor of left of screen')) + self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos']) + + palette.add_block('bottompos', + style='box-style', + label=_('bottom'), + prim_name='bpos', + logo_command='bpos', + help_string=_('ycor of bottom of screen')) + self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos']) + + palette.add_block('width', + style='box-style', + label=_('width'), + prim_name='hres', + logo_command='width', + help_string=_('the canvas width')) + self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width']) + + palette.add_block('rightpos', + style='box-style', + label=_('right'), + prim_name='rpos', + logo_command='rpos', + help_string=_('xcor of right of screen')) + self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos']) + + palette.add_block('toppos', + style='box-style', + label=_('top'), + prim_name='tpos', + logo_command='tpos', + help_string=_('ycor of top of screen')) + self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos']) + + palette.add_block('height', + style='box-style', + label=_('height'), + prim_name='vres', + logo_command='height', + help_string=_('the canvas height')) + self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height']) + + palette.add_block('titlex', + hidden=True, + style='box-style', + label=_('title x'), + logo_command='titlex', + prim_name='titlex') + self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex']) + + palette.add_block('titley', + hidden=True, + style='box-style', + label=_('title y'), + logo_command='titley', + prim_name='titley') + self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley']) + + palette.add_block('leftx', + hidden=True, + style='box-style', + label=_('left x'), + prim_name='leftx', + logo_command='leftx') + self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx']) + + palette.add_block('topy', + hidden=True, + style='box-style', + label=_('top y'), + prim_name='topy', + logo_command='topy') + self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy']) + + palette.add_block('rightx', + hidden=True, + style='box-style', + label=_('right x'), + prim_name='rightx', + logo_command='rightx') + self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx']) + + palette.add_block('bottomy', + hidden=True, + style='box-style', + label=_('bottom y'), + prim_name='bottomy', + logo_command='bottomy') + self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy']) + + # deprecated blocks + + primitive_dictionary['t1x1'] = self._prim_t1x1 + palette.add_block('template1x1', + hidden=True, + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (with description)')) + self.tw.lc.def_prim('t1x1', 2, + lambda self, a, b: primitive_dictionary['t1x1'](a, b)) + + primitive_dictionary['t1x1a'] = self._prim_t1x1a + palette.add_block('template1x1a', + hidden=True, + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1a', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (no description)')) + self.tw.lc.def_prim('t1x1a', 2, + lambda self, a, b: primitive_dictionary['t1x1a'](a, b)) + + primitive_dictionary['2x1'] = self._prim_t2x1 + palette.add_block('template2x1', + hidden=True, + style='portfolio-style-2x1', + label=' ', + prim_name='t2x1', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 2x1'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t2x1', 3, + lambda self, a, b, c: primitive_dictionary['t2x1'](a, b, c)) + + primitive_dictionary['1x2'] = self._prim_t1x2 + palette.add_block('template1x2', + hidden=True, + style='portfolio-style-1x2', + label=' ', + prim_name='t1x2', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 1x2'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t1x2', 3, + lambda self, a, b, c: primitive_dictionary['t1x2'](a, b, c)) + + primitive_dictionary['t2x2'] = self._prim_t2x2 + palette.add_block('template2x2', + hidden=True, + style='portfolio-style-2x2', + label=' ', + prim_name='t2x2', + default=[_('Title'), 'None', 'None', 'None', 'None'], + special_name=_('presentation 2x2'), + help_string=_("presentation template: select four \ +Journal objects")) + self.tw.lc.def_prim('t2x2', 5, + lambda self, a, b, c, d, e: primitive_dictionary['t2x2']( + a, b, c, d, e)) + + palette.add_block('templatelist', + hidden=True, + style='bullet-style', + label=' ', + prim_name='bullet', + default=[_('Title'), '∙ '], + special_name=_('presentation bulleted list'), + help_string=_('presentation template: list of \ +bullets')) + self.tw.lc.def_prim('bullet', 1, self._prim_list, True) + + # Block primitives + + def _prim_emptyheap(self): + """ Empty FILO """ + self.tw.lc.heap = [] + + def _prim_kbinput(self): + """ Query keyboard """ + if len(self.tw.keypress) == 1: + self.tw.lc.keyboard = ord(self.tw.keypress[0]) + else: + try: + self.tw.lc.keyboard = {'Escape': 27, 'space': 32, ' ': 32, + 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, + 'KP_Right': 3}[self.tw.keypress] + except KeyError: + self.tw.lc.keyboard = 0 + self.tw.lc.update_label_value('keyboard', self.tw.lc.keyboard) + self.tw.keypress = '' + + def _prim_list(self, blklist): + """ Expandable list block """ + self._prim_showlist(blklist) + self.tw.lc.ireturn() + yield True + + def _prim_myblock(self, x): + """ Run Python code imported from Journal """ + if self.tw.lc.bindex is not None and \ + self.tw.lc.bindex in self.tw.myblock: + try: + if len(x) == 1: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], + x[0]) + else: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], x) + except: + raise logoerror("#syntaxerror") + + def _prim_myfunction(self, f, x): + """ Programmable block """ + try: + y = myfunc(f, x) + if str(y) == 'nan': + debug_output('Python function returned NAN', + self.tw.running_sugar) + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + else: + return y + except ZeroDivisionError: + self.tw.lc.stop_logo() + raise logoerror("#zerodivide") + except ValueError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except SyntaxError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except NameError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except OverflowError: + self.tw.lc.stop_logo() + raise logoerror("#overflowerror") + except TypeError: + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + + def _prim_pop(self): + """ Pop value off of FILO """ + if len(self.tw.lc.heap) == 0: + raise logoerror("#emptyheap") + else: + if len(self.tw.lc.heap) == 1: + self.tw.lc.update_label_value('pop') + else: + self.tw.lc.update_label_value('pop', self.tw.lc.heap[-2]) + return self.tw.lc.heap.pop(-1) + + def _prim_print(self, n, flag): + """ Print object n """ + if flag and (self.tw.hide or self.tw.step_time == 0): + return + if type(n) == str or type(n) == unicode: + if n[0:6] == 'media_' and \ + n[6:].lower not in media_blocks_dictionary: + try: + if self.tw.running_sugar: + try: + dsobject = datastore.get(n[6:]) + except: + debug_output("Couldn't open %s" % (n[6:]), + self.tw.running_sugar) + self.tw.showlabel('status', dsobject.metadata['title']) + dsobject.destroy() + else: + self.tw.showlabel('status', n[6:]) + except IOError: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', n) + elif type(n) == int: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', + str(round_int(n)).replace('.', self.tw.decimal_point)) + + def _prim_printheap(self): + """ Display contents of heap """ + heap_as_string = str(self.tw.lc.heap) + if len(heap_as_string) > 80: + self.tw.showlabel('status', str(self.tw.lc.heap)[0:79] + '…') + else: + self.tw.showlabel('status', str(self.tw.lc.heap)) + + def _prim_push(self, val): + """ Push value onto FILO """ + self.tw.lc.heap.append(val) + self.tw.lc.update_label_value('pop', val) + + def _prim_readpixel(self): + """ Read r, g, b, a from the canvas and push b, g, r to the stack """ + r, g, b, a = self.tw.canvas.get_pixel() + self.tw.lc.heap.append(b) + self.tw.lc.heap.append(g) + self.tw.lc.heap.append(r) + + def _prim_reskin(self, media): + """ Reskin the turtle with an image from a file """ + scale = int(ICON_SIZE * float(self.tw.lc.scale) / DEFAULT_SCALE) + if scale < 1: + return + self.tw.lc.filepath = None + dsobject = None + if os.path.exists(media[6:]): # is it a path? + self.tw.lc.filepath = media[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + dsobject = datastore.get(media[6:]) + except: + debug_output("Couldn't open skin %s" % (media[6:]), + self.tw.running_sugar) + if dsobject is not None: + self.tw.lc.filepath = dsobject.file_path + if self.tw.lc.filepath == None: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + return + pixbuf = None + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.tw.lc.filepath, + scale, scale) + except: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + debug_output("Couldn't open skin %s" % (self.tw.lc.filepath), + self.tw.running_sugar) + if pixbuf is not None: + self.tw.active_turtle.set_shapes([pixbuf]) + pen_state = self.tw.active_turtle.get_pen_state() + if pen_state: + self.tw.canvas.setpen(False) + self.tw.canvas.forward(0) + if pen_state: + self.tw.canvas.setpen(True) + + def _prim_save_picture(self, name): + """ Save canvas to file as PNG """ + self.tw.save_as_image(name) + + def _prim_save_svg(self, name): + """ Save SVG to file """ + self.tw.canvas.svg_close() + self.tw.save_as_image(name, True) + + def _prim_see(self): + """ Read r, g, b from the canvas and return a corresponding palette + color """ + r, g, b, a = self.tw.canvas.get_pixel() + color_index = self.tw.canvas.get_color_index(r, g, b) + self.tw.lc.update_label_value('see', color_index) + return color_index + + def _prim_setscale(self, scale): + """ Set the scale used by the show block """ + self.tw.lc.scale = scale + self.tw.lc.update_label_value('scale', scale) + + def _prim_show(self, string, center=False): + """ Show is the general-purpose media-rendering block. """ + if type(string) == str or type(string) == unicode: + if string in ['media_', 'descr_', 'audio_', 'video_', + 'media_None', 'descr_None', 'audio_None', + 'video_None']: + pass + elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: + self.tw.lc.filepath = None + self.tw.lc.dsobject = None + if string[6:].lower() in media_blocks_dictionary: + media_blocks_dictionary[string[6:].lower()]() + elif os.path.exists(string[6:]): # is it a path? + self.tw.lc.filepath = string[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + self.tw.lc.dsobject = datastore.get(string[6:]) + except: + debug_output("Couldn't find dsobject %s" % ( + string[6:]), self.tw.running_sugar) + if self.tw.lc.dsobject is not None: + self.tw.lc.filepath = self.tw.lc.dsobject.file_path + if self.tw.lc.filepath == None: + if self.tw.lc.dsobject is not None: + self.tw.showlabel('nojournal', + self.tw.lc.dsobject.metadata['title']) + else: + self.tw.showlabel('nojournal', string[6:]) + debug_output("Couldn't open %s" % (string[6:]), + self.tw.running_sugar) + elif string[0:6] == 'media_': + self.tw.lc.insert_image(center) + elif string[0:6] == 'descr_': + mimetype = None + if self.tw.lc.dsobject is not None and \ + 'mime_type' in self.tw.lc.dsobject.metadata: + mimetype = self.tw.lc.dsobject.metadata['mime_type'] + description = None + if self.tw.lc.dsobject is not None and \ + 'description' in self.tw.lc.dsobject.metadata: + description = self.tw.lc.dsobject.metadata[ + 'description'] + self.tw.lc.insert_desc(mimetype, description) + elif string[0:6] == 'audio_': + self.tw.lc.play_sound() + elif string[0:6] == 'video_': + self.tw.lc.play_video() + if self.tw.lc.dsobject is not None: + self.tw.lc.dsobject.destroy() + else: # assume it is text to display + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + elif type(string) == float or type(string) == int: + string = round_int(string) + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + + def _prim_showlist(self, sarray): + """ Display list of media objects """ + x = self.tw.canvas.xcor / self.tw.coord_scale + y = self.tw.canvas.ycor / self.tw.coord_scale + for s in sarray: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(s) + y -= int(self.tw.canvas.textsize * self.tw.lead) + + def _prim_time(self): + """ Number of seconds since program execution has started or + clean (prim_clear) block encountered """ + elapsed_time = int(time() - self.tw.lc.start_time) + self.tw.lc.update_label_value('time', elapsed_time) + return elapsed_time + + # Deprecated blocks + + def _prim_t1x1(self, title, media): + """ title, one image, and description """ + xo = self.tw.calc_position('t1x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \ + / self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x1(self, title, media1, media2): + """ title, two images (horizontal), two descriptions """ + xo = self.tw.calc_position('t2x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + if self.tw.running_sugar: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x2(self, title, media1, media2): + """ title, two images (vertical), two desciptions """ + xo = self.tw.calc_position('t1x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x2(self, title, media1, media2, media3, media4): + """ title and four images """ + xo = self.tw.calc_position('t2x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media4) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media3) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x1a(self, title, media1): + """ title, one media object """ + xo = self.tw.calc_position('t1x1a')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_write(self, string, fsize): + """ Write string at size """ + x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor) + y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor) + self.tw.canvas.draw_text(string, x, y - 15, int(fsize), + self.tw.canvas.width) diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.pyc b/plugins/turtle_blocks_extras/turtle_blocks_extras.pyc new file mode 100644 index 0000000..ffa7609 --- /dev/null +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.pyc Binary files differ diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py~ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py~ new file mode 100644 index 0000000..5158c97 --- /dev/null +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py~ @@ -0,0 +1,1213 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2011, Walter Bender +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gtk +from time import time +import os.path + +try: + from sugar.datastore import datastore +except ImportError: + pass + +from plugins.plugin import Plugin +from TurtleArt.tapalette import make_palette +from TurtleArt.talogo import primitive_dictionary, logoerror, \ + media_blocks_dictionary +from TurtleArt.taconstants import DEFAULT_SCALE, ICON_SIZE, CONSTANTS +from TurtleArt.tautils import convert, round_int, debug_output +from TurtleArt.tajail import myfunc, myfunc_import + + +def _num_type(x): + """ Is x a number type? """ + if type(x) == int: + return True + if type(x) == float: + return True + if type(x) == ord: + return True + return False + + +def _string_to_num(x): + """ Try to comvert a string to a number """ + xx = convert(x.replace(self.tw.decimal_point, '.'), float) + if type(xx) is float: + return xx + else: + xx, xflag = chr_to_ord(x) + if xflag: + return xx + else: + raise logoerror("#syntaxerror") + + +def _millisecond(): + """ Current time in milliseconds """ + return time() * 1000 + + +class Turtle_blocks(Plugin): + """ a class for defining the extra palettes that distinguish Turtle Blocks + from Turtle Art """ + + def __init__(self, parent): + self.tw = parent + + def setup(self): + self.heap = self.tw.lc.heap + self.keyboard = self.tw.lc.keyboard + self.title_height = int((self.tw.canvas.height / 20) * self.tw.scale) + + # set up Turtle Block palettes + self._flow_palette() + + self._media_palette() + + self._sensor_palette() + + self._extras_palette() + + self._portfolio_palette() + + # Palette definitions + + def _flow_palette(self): + palette = make_palette('flow', + colors=["#FFC000", "#A08000"], + help_string=_('Palette of flow operators')) + + # macro + palette.add_block('while', + style='flow-style-boolean', + label=_('while'), + help_string=_('do-while-True operator that uses \ +boolean operators from Numbers palette')) + + # macro + palette.add_block('until', + style='flow-style-boolean', + label=_('until'), + help_string=_('do-until-True operator that uses \ +boolean operators from Numbers palette')) + + def _media_palette(self): + + palette = make_palette('media', + colors=["#A0FF00", "#80A000"], + help_string=_('Palette of media objects')) + + palette.add_block('journal', + style='box-style-media', + label=' ', + default='None', + special_name=_('journal'), + help_string=_('Sugar Journal media object')) + + palette.add_block('audio', + style='box-style-media', + label=' ', + special_name=_('audio'), + default='None', + help_string=_('Sugar Journal audio object')) + + palette.add_block('video', + style='box-style-media', + label=' ', + special_name=_('video'), + default='None', + help_string=_('Sugar Journal video object')) + + palette.add_block('description', + style='box-style-media', + label=' ', + special_name=_('description'), + default='None', + help_string=_('Sugar Journal description field')) + + palette.add_block('string', + style='box-style', + label=_('text'), + default=_('text'), + special_name='', + help_string=_('string value')) + + primitive_dictionary['show'] = self._prim_show + palette.add_block('show', + style='basic-style-1arg', + label=_('show'), + default=_('text'), + prim_name='show', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('show', 1, + lambda self, x: primitive_dictionary['show'](x, True)) + + palette.add_block('showaligned', + hidden=True, + style='basic-style-1arg', + label=_('show aligned'), + default=_('text'), + prim_name='showaligned', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('showaligned', 1, + lambda self, x: primitive_dictionary['show'](x, False)) + + # deprecated + primitive_dictionary['write'] = self._prim_write + palette.add_block('write', + hidden=True, + style='basic-style-1arg', + label=_('show'), + default=[_('text'), 32], + prim_name='write', + logo_command='label', + help_string=_('draws text or show media from the \ +Journal')) + self.tw.lc.def_prim('write', 2, + lambda self, x, y: primitive_dictionary['write'](x, y)) + + primitive_dictionary['setscale'] = self._prim_setscale + palette.add_block('setscale', + style='basic-style-1arg', + label=_('set scale'), + prim_name='setscale', + default=33, + logo_command='setlabelheight', + help_string=_('sets the scale of media')) + self.tw.lc.def_prim('setscale', 1, + lambda self, x: primitive_dictionary['setscale'](x)) + + primitive_dictionary['savepix'] = self._prim_save_picture + palette.add_block('savepix', + style='basic-style-1arg', + label=_('save picture'), + prim_name='savepix', + default=_('picture name'), + help_string=_('saves a picture to the Sugar \ +Journal')) + self.tw.lc.def_prim('savepix', 1, + lambda self, x: primitive_dictionary['savepix'](x)) + + primitive_dictionary['savesvg'] = self._prim_save_svg + palette.add_block('savesvg', + style='basic-style-1arg', + label=_('save SVG'), + prim_name='savesvg', + default=_('picture name'), + help_string=_('saves turtle graphics as an SVG file \ +in the Sugar Journal')) + self.tw.lc.def_prim('savesvg', 1, + lambda self, x: primitive_dictionary['savesvg'](x)) + + palette.add_block('scale', + style='box-style', + label=_('scale'), + prim_name='scale', + value_block=True, + logo_command='labelsize', + help_string=_('holds current scale value')) + self.tw.lc.def_prim('scale', 0, lambda self: self.tw.lc.scale) + + palette.add_block('mediawait', + style='basic-style-extended-vertical', + label=_('media wait'), + prim_name='mediawait', + help_string=_('wait for current video or audio to \ +complete')) + self.tw.lc.def_prim('mediawait', 0, self.tw.lc.media_wait, True) + + def _sensor_palette(self): + + palette = make_palette('sensor', + colors=["#FF6060", "#A06060"], + help_string=_('Palette of sensor blocks')) + + primitive_dictionary['kbinput'] = self._prim_kbinput + palette.add_block('kbinput', + style='basic-style-extended-vertical', + label=_('query keyboard'), + prim_name='kbinput', + help_string=_('query for keyboard input (results \ +stored in keyboard block)')) + self.tw.lc.def_prim('kbinput', 0, + lambda self: primitive_dictionary['kbinput']()) + + palette.add_block('keyboard', + style='box-style', + label=_('keyboard'), + prim_name='keyboard', + value_block=True, + logo_command='make "keyboard readchar', + help_string=_('holds results of query-keyboard \ +block')) + self.tw.lc.def_prim('keyboard', 0, lambda self: self.tw.lc.keyboard) + + primitive_dictionary['readpixel'] = self._prim_readpixel + palette.add_block('readpixel', + style='basic-style-extended-vertical', + label=_('read pixel'), + prim_name='readpixel', + logo_command=':keyboard', + help_string=_('RGB color under the turtle is pushed \ +to the stack')) + self.tw.lc.def_prim('readpixel', 0, + lambda self: primitive_dictionary['readpixel']()) + + primitive_dictionary['see'] = self._prim_see + palette.add_block('see', + style='box-style', + label=_('turtle sees'), + prim_name='see', + help_string=_('returns the color that the turtle \ +"sees"')) + self.tw.lc.def_prim('see', 0, + lambda self: primitive_dictionary['see']()) + + primitive_dictionary['time'] = self._prim_time + palette.add_block('time', + style='box-style', + label=_('time'), + prim_name='time', + value_block=True, + help_string=_('elapsed time (in seconds) since \ +program started')) + self.tw.lc.def_prim('time', 0, + lambda self: primitive_dictionary['time']()) + + def _extras_palette(self): + + palette = make_palette('extras', + colors=["#FF0000", "#A00000"], + help_string=_('Palette of extra options')) + + primitive_dictionary['push'] = self._prim_push + palette.add_block('push', + style='basic-style-1arg', + label=_('push'), + prim_name='push', + logo_command='tapush', + help_string=_('pushes value onto FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('push', 1, + lambda self, x: primitive_dictionary['push'](x)) + + primitive_dictionary['printheap'] = self._prim_printheap + palette.add_block('printheap', + style='basic-style-extended-vertical', + label=_('show heap'), + prim_name='printheap', + logo_command='taprintheap', + help_string=_('shows values in FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('printheap', 0, + lambda self: primitive_dictionary['printheap']()) + + primitive_dictionary['clearheap'] = self._prim_emptyheap + palette.add_block('clearheap', + style='basic-style-extended-vertical', + label=_('empty heap'), + prim_name='clearheap', + logo_command='taclearheap', + help_string=_('emptys FILO (first-in-last-out \ +heap)')) + self.tw.lc.def_prim('clearheap', 0, + lambda self: primitive_dictionary['clearheap']()) + + primitive_dictionary['pop'] = self._prim_pop + palette.add_block('pop', + style='box-style', + label=_('pop'), + prim_name='pop', + value_block=True, + logo_command='tapop', + help_string=_('pops value off FILO (first-in \ +last-out heap)')) + self.tw.lc.def_prim('pop', 0, + lambda self: primitive_dictionary['pop']()) + + primitive_dictionary['print'] = self._prim_print + palette.add_block('comment', + style='basic-style-1arg', + label=_('comment'), + prim_name='comment', + default=_('comment'), + help_string=_('places a comment in your code')) + self.tw.lc.def_prim('comment', 1, + lambda self, x: primitive_dictionary['print'](x, True)) + + palette.add_block('print', + style='basic-style-1arg', + label=_('print'), + prim_name='print', + logo_command='label', + help_string=_('prints value in status block at \ +bottom of the screen')) + self.tw.lc.def_prim('print', 1, + lambda self, x: primitive_dictionary['print'](x, False)) + + primitive_dictionary['myfunction'] = self._prim_myfunction + palette.add_block('myfunc1arg', + style='number-style-var-arg', + label=[_('Python'), 'f(x)', 'x'], + prim_name='myfunction', + default=['x', 100], + help_string=_('a programmable block: used to add \ +advanced single-variable math equations, e.g., sin(x)')) + self.tw.lc.def_prim('myfunction', 2, + lambda self, f, x: primitive_dictionary['myfunction'](f, [x])) + + palette.add_block('myfunc2arg', + hidden=True, + style='number-style-var-arg', + label=[_('Python'), 'f(x,y)', 'x'], + prim_name='myfunction2', + default=['x+y', 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sqrt(x*x+y*y)')) + self.tw.lc.def_prim('myfunction2', 3, + lambda self, f, x, y: primitive_dictionary['myfunction']( + f, [x, y])) + + palette.add_block('myfunc3arg', + hidden=True, + style='number-style-var-arg', + label=[_('Python'), 'f(x,y,z)', 'x'], + prim_name='myfunction3', + default=['x+y+z', 100, 100, 100], + help_string=_('a programmable block: used to add \ +advanced multi-variable math equations, e.g., sin(x+y+z)')) + self.tw.lc.def_prim('myfunction3', 4, + lambda self, f, x, y, z: primitive_dictionary['myfunction']( + f, [x, y, z])) + + primitive_dictionary['userdefined'] = self._prim_myblock + palette.add_block('userdefined', + style='basic-style-var-arg', + label=' ', + prim_name='userdefined', + special_name=_('Python block'), + default=100, + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined', 1, + lambda self, x: primitive_dictionary['userdefined']([x])) + + palette.add_block('userdefined2args', + hidden=True, + style='basic-style-var-arg', + label=' ', + prim_name='userdefined2', + special_name=_('Python block'), + default=[100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined2', 2, + lambda self, x, y: primitive_dictionary['userdefined']([x, y])) + + palette.add_block('userdefined3args', + hidden=True, + style='basic-style-var-arg', + label=' ', + prim_name='userdefined3', + special_name=_('Python block'), + default=[100, 100, 100], + help_string=_('runs code found in the tamyblock.py \ +module found in the Journal')) + self.tw.lc.def_prim('userdefined3', 3, + lambda self, x, y, z: primitive_dictionary['userdefined']( + [x, y, z])) + + palette.add_block('cartesian', + style='basic-style-extended-vertical', + label=_('Cartesian'), + prim_name='cartesian', + help_string=_('displays Cartesian coordinates')) + self.tw.lc.def_prim('cartesian', 0, + lambda self: self.tw.set_cartesian(True)) + + palette.add_block('polar', + style='basic-style-extended-vertical', + label=_('polar'), + prim_name='polar', + help_string=_('displays polar coordinates')) + self.tw.lc.def_prim('polar', 0, + lambda self: self.tw.set_polar(True)) + + palette.add_block('addturtle', + style='basic-style-1arg', + label=_('turtle'), + prim_name='turtle', + default=1, + help_string=_('chooses which turtle to command')) + self.tw.lc.def_prim('turtle', 1, + lambda self, x: self.tw.canvas.set_turtle(x)) + + primitive_dictionary['skin'] = self._prim_reskin + palette.add_block('skin', + hidden=True, + style='basic-style-1arg', + label=_('turtle shell'), + prim_name='skin', + help_string=_("put a custom 'shell' on the turtle")) + self.tw.lc.def_prim('skin', 1, + lambda self, x: primitive_dictionary['skin'](x)) + + # macro + palette.add_block('reskin', + style='basic-style-1arg', + label=_('turtle shell'), + help_string=_("put a custom 'shell' on the turtle")) + + palette.add_block('sandwichtop_no_label', + style='collapsible-top-no-label', + label=[' ', ' '], + special_name=_('top'), + prim_name='nop', + help_string=_('top of a collapsed stack')) + + palette.add_block('sandwichbottom', + style='collapsible-bottom', + label=[' ', ' '], + prim_name='nop', + special_name=_('bottom'), + help_string=_('bottom of a collapsible stack')) + + palette.add_block('sandwichcollapsed', + hidden=True, + style='invisible', + label=' ', + prim_name='nop', + help_string=_('bottom block in a collapsed stack: \ +click to open')) + + # deprecated blocks + palette.add_block('sandwichtop', + hidden=True, + style='collapsible-top', + label=_('top of stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm', + hidden=True, + style='collapsible-top-no-arm', + label=_('top of a collapsible stack'), + default=_('label'), + prim_name='comment', + help_string=_('top of stack')) + + palette.add_block('sandwichtop_no_arm_no_label', + hidden=True, + style='collapsible-top-no-arm-no-label', + label=[' ', _('click to open')], + prim_name='nop', + help_string=_('top of stack')) + + def _portfolio_palette(self): + + palette = make_palette('portfolio', + colors=["#0606FF", "#0606A0"], + help_string=_('Palette of presentation templates')) + + palette.add_block('hideblocks', + style='basic-style-extended-vertical', + label=_('hide blocks'), + prim_name='hideblocks', + help_string=_('declutters canvas by hiding blocks')) + self.tw.lc.def_prim('hideblocks', 0, lambda self: self.tw.hideblocks()) + + palette.add_block('showblocks', + style='basic-style-extended-vertical', + label=_('show blocks'), + prim_name='showblocks', + help_string=_('restores hidden blocks')) + self.tw.lc.def_prim('showblocks', 0, lambda self: self.tw.showblocks()) + + palette.add_block('fullscreen', + style='basic-style-extended-vertical', + label=_('full screen'), + prim_name='fullscreen', + help_string=_('hides the Sugar toolbars')) + self.tw.lc.def_prim('fullscreen', 0, + lambda self: self.tw.set_fullscreen()) + + primitive_dictionary['bulletlist'] = self._prim_list + palette.add_block('list', + hidden=True, + style='bullet-style', + label=_('list'), + prim_name='bulletlist', + default=['∙ ', '∙ '], + help_string=_('presentation bulleted list')) + self.tw.lc.def_prim('bulletlist', 1, + primitive_dictionary['bulletlist'], True) + + # macros + palette.add_block('picturelist', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: list of \ +bullets')) + + palette.add_block('picture1x1a', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (no description)')) + + palette.add_block('picture1x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select \ +Journal object (with description)')) + + palette.add_block('picture2x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select four \ +Journal objects')) + + palette.add_block('picture2x1', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + palette.add_block('picture1x2', + style='basic-style-extended', + label=' ', + help_string=_('presentation template: select two \ +Journal objects')) + + # Display-dependent constants + palette.add_block('leftpos', + style='box-style', + label=_('left'), + prim_name='lpos', + logo_command='lpos', + help_string=_('xcor of left of screen')) + self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos']) + + palette.add_block('bottompos', + style='box-style', + label=_('bottom'), + prim_name='bpos', + logo_command='bpos', + help_string=_('ycor of bottom of screen')) + self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos']) + + palette.add_block('width', + style='box-style', + label=_('width'), + prim_name='hres', + logo_command='width', + help_string=_('the canvas width')) + self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width']) + + palette.add_block('rightpos', + style='box-style', + label=_('right'), + prim_name='rpos', + logo_command='rpos', + help_string=_('xcor of right of screen')) + self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos']) + + palette.add_block('toppos', + style='box-style', + label=_('top'), + prim_name='tpos', + logo_command='tpos', + help_string=_('ycor of top of screen')) + self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos']) + + palette.add_block('height', + style='box-style', + label=_('height'), + prim_name='vres', + logo_command='height', + help_string=_('the canvas height')) + self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height']) + + palette.add_block('titlex', + hidden=True, + style='box-style', + label=_('title x'), + logo_command='titlex', + prim_name='titlex') + self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex']) + + palette.add_block('titley', + hidden=True, + style='box-style', + label=_('title y'), + logo_command='titley', + prim_name='titley') + self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley']) + + palette.add_block('leftx', + hidden=True, + style='box-style', + label=_('left x'), + prim_name='leftx', + logo_command='leftx') + self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx']) + + palette.add_block('topy', + hidden=True, + style='box-style', + label=_('top y'), + prim_name='topy', + logo_command='topy') + self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy']) + + palette.add_block('rightx', + hidden=True, + style='box-style', + label=_('right x'), + prim_name='rightx', + logo_command='rightx') + self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx']) + + palette.add_block('bottomy', + hidden=True, + style='box-style', + label=_('bottom y'), + prim_name='bottomy', + logo_command='bottomy') + self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy']) + + # deprecated blocks + + primitive_dictionary['t1x1'] = self._prim_t1x1 + palette.add_block('template1x1', + hidden=True, + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (with description)')) + self.tw.lc.def_prim('t1x1', 2, + lambda self, a, b: primitive_dictionary['t1x1'](a, b)) + + primitive_dictionary['t1x1a'] = self._prim_t1x1a + palette.add_block('template1x1a', + hidden=True, + style='portfolio-style-1x1', + label=' ', + prim_name='t1x1a', + default=[_('Title'), 'None'], + special_name=_('presentation 1x1'), + help_string=_('presentation template: select \ +Journal object (no description)')) + self.tw.lc.def_prim('t1x1a', 2, + lambda self, a, b: primitive_dictionary['t1x1a'](a, b)) + + primitive_dictionary['2x1'] = self._prim_t2x1 + palette.add_block('template2x1', + hidden=True, + style='portfolio-style-2x1', + label=' ', + prim_name='t2x1', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 2x1'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t2x1', 3, + lambda self, a, b, c: primitive_dictionary['t2x1'](a, b, c)) + + primitive_dictionary['1x2'] = self._prim_t1x2 + palette.add_block('template1x2', + hidden=True, + style='portfolio-style-1x2', + label=' ', + prim_name='t1x2', + default=[_('Title'), 'None', 'None'], + special_name=_('presentation 1x2'), + help_string=_("presentation template: select two \ +Journal objects")) + self.tw.lc.def_prim('t1x2', 3, + lambda self, a, b, c: primitive_dictionary['t1x2'](a, b, c)) + + primitive_dictionary['t2x2'] = self._prim_t2x2 + palette.add_block('template2x2', + hidden=True, + style='portfolio-style-2x2', + label=' ', + prim_name='t2x2', + default=[_('Title'), 'None', 'None', 'None', 'None'], + special_name=_('presentation 2x2'), + help_string=_("presentation template: select four \ +Journal objects")) + self.tw.lc.def_prim('t2x2', 5, + lambda self, a, b, c, d, e: primitive_dictionary['t2x2']( + a, b, c, d, e)) + + palette.add_block('templatelist', + hidden=True, + style='bullet-style', + label=' ', + prim_name='bullet', + default=[_('Title'), '∙ '], + special_name=_('presentation bulleted list'), + help_string=_('presentation template: list of \ +bullets')) + self.tw.lc.def_prim('bullet', 1, self._prim_list, True) + + # Block primitives + + def _prim_emptyheap(self): + """ Empty FILO """ + self.tw.lc.heap = [] + + def _prim_kbinput(self): + """ Query keyboard """ + if len(self.tw.keypress) == 1: + self.tw.lc.keyboard = ord(self.tw.keypress[0]) + else: + try: + self.tw.lc.keyboard = {'Escape': 27, 'space': 32, ' ': 32, + 'Return': 13, 'KP_Up': 2, 'KP_Down': 4, 'KP_Left': 1, + 'KP_Right': 3}[self.tw.keypress] + except KeyError: + self.tw.lc.keyboard = 0 + self.tw.lc.update_label_value('keyboard', self.tw.lc.keyboard) + self.tw.keypress = '' + + def _prim_list(self, blklist): + """ Expandable list block """ + self._prim_showlist(blklist) + self.tw.lc.ireturn() + yield True + + def _prim_myblock(self, x): + """ Run Python code imported from Journal """ + if self.tw.lc.bindex is not None and \ + self.tw.lc.bindex in self.tw.myblock: + try: + if len(x) == 1: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], + x[0]) + else: + myfunc_import(self, self.tw.myblock[self.tw.lc.bindex], x) + except: + raise logoerror("#syntaxerror") + + def _prim_myfunction(self, f, x): + """ Programmable block """ + try: + y = myfunc(f, x) + if str(y) == 'nan': + debug_output('Python function returned NAN', + self.tw.running_sugar) + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + else: + return y + except ZeroDivisionError: + self.tw.lc.stop_logo() + raise logoerror("#zerodivide") + except ValueError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except SyntaxError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except NameError, e: + self.tw.lc.stop_logo() + raise logoerror('#' + str(e)) + except OverflowError: + self.tw.lc.stop_logo() + raise logoerror("#overflowerror") + except TypeError: + self.tw.lc.stop_logo() + raise logoerror("#notanumber") + + def _prim_pop(self): + """ Pop value off of FILO """ + if len(self.tw.lc.heap) == 0: + raise logoerror("#emptyheap") + else: + if len(self.tw.lc.heap) == 1: + self.tw.lc.update_label_value('pop') + else: + self.tw.lc.update_label_value('pop', self.tw.lc.heap[-2]) + return self.tw.lc.heap.pop(-1) + + def _prim_print(self, n, flag): + """ Print object n """ + if flag and (self.tw.hide or self.tw.step_time == 0): + return + if type(n) == str or type(n) == unicode: + if n[0:6] == 'media_' and \ + n[6:].lower not in media_blocks_dictionary: + try: + if self.tw.running_sugar: + try: + dsobject = datastore.get(n[6:]) + except: + debug_output("Couldn't open %s" % (n[6:]), + self.tw.running_sugar) + self.tw.showlabel('status', dsobject.metadata['title']) + dsobject.destroy() + else: + self.tw.showlabel('status', n[6:]) + except IOError: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', n) + elif type(n) == int: + self.tw.showlabel('status', n) + else: + self.tw.showlabel('status', + str(round_int(n)).replace('.', self.tw.decimal_point)) + + def _prim_printheap(self): + """ Display contents of heap """ + heap_as_string = str(self.tw.lc.heap) + if len(heap_as_string) > 80: + self.tw.showlabel('status', str(self.tw.lc.heap)[0:79] + '…') + else: + self.tw.showlabel('status', str(self.tw.lc.heap)) + + def _prim_push(self, val): + """ Push value onto FILO """ + self.tw.lc.heap.append(val) + self.tw.lc.update_label_value('pop', val) + + def _prim_readpixel(self): + """ Read r, g, b, a from the canvas and push b, g, r to the stack """ + r, g, b, a = self.tw.canvas.get_pixel() + self.tw.lc.heap.append(b) + self.tw.lc.heap.append(g) + self.tw.lc.heap.append(r) + + def _prim_reskin(self, media): + """ Reskin the turtle with an image from a file """ + scale = int(ICON_SIZE * float(self.tw.lc.scale) / DEFAULT_SCALE) + if scale < 1: + return + self.tw.lc.filepath = None + dsobject = None + if os.path.exists(media[6:]): # is it a path? + self.tw.lc.filepath = media[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + dsobject = datastore.get(media[6:]) + except: + debug_output("Couldn't open skin %s" % (media[6:]), + self.tw.running_sugar) + if dsobject is not None: + self.tw.lc.filepath = dsobject.file_path + if self.tw.lc.filepath == None: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + return + pixbuf = None + try: + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.tw.lc.filepath, + scale, scale) + except: + self.tw.showlabel('nojournal', self.tw.lc.filepath) + debug_output("Couldn't open skin %s" % (self.tw.lc.filepath), + self.tw.running_sugar) + if pixbuf is not None: + self.tw.active_turtle.set_shapes([pixbuf]) + pen_state = self.tw.active_turtle.get_pen_state() + if pen_state: + self.tw.canvas.setpen(False) + self.tw.canvas.forward(0) + if pen_state: + self.tw.canvas.setpen(True) + + def _prim_save_picture(self, name): + """ Save canvas to file as PNG """ + self.tw.save_as_image(name) + + def _prim_save_svg(self, name): + """ Save SVG to file """ + self.tw.canvas.svg_close() + self.tw.save_as_image(name, True) + + def _prim_see(self): + """ Read r, g, b from the canvas and return a corresponding palette + color """ + r, g, b, a = self.tw.canvas.get_pixel() + color_index = self.tw.canvas.get_color_index(r, g, b) + self.tw.lc.update_label_value('see', color_index) + return color_index + + def _prim_setscale(self, scale): + """ Set the scale used by the show block """ + self.tw.lc.scale = scale + self.tw.lc.update_label_value('scale', scale) + + def _prim_show(self, string, center=False): + """ Show is the general-purpose media-rendering block. """ + if type(string) == str or type(string) == unicode: + if string in ['media_', 'descr_', 'audio_', 'video_', + 'media_None', 'descr_None', 'audio_None', + 'video_None']: + pass + elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: + self.tw.lc.filepath = None + self.tw.lc.dsobject = None + if string[6:].lower() in media_blocks_dictionary: + media_blocks_dictionary[string[6:].lower()]() + elif os.path.exists(string[6:]): # is it a path? + self.tw.lc.filepath = string[6:] + elif self.tw.running_sugar: # is it a datastore object? + try: + self.tw.lc.dsobject = datastore.get(string[6:]) + except: + debug_output("Couldn't find dsobject %s" % ( + string[6:]), self.tw.running_sugar) + if self.tw.lc.dsobject is not None: + self.tw.lc.filepath = self.tw.lc.dsobject.file_path + if self.tw.lc.filepath == None: + if self.tw.lc.dsobject is not None: + self.tw.showlabel('nojournal', + self.tw.lc.dsobject.metadata['title']) + else: + self.tw.showlabel('nojournal', string[6:]) + debug_output("Couldn't open %s" % (string[6:]), + self.tw.running_sugar) + elif string[0:6] == 'media_': + self.tw.lc.insert_image(center) + elif string[0:6] == 'descr_': + mimetype = None + if self.tw.lc.dsobject is not None and \ + 'mime_type' in self.tw.lc.dsobject.metadata: + mimetype = self.tw.lc.dsobject.metadata['mime_type'] + description = None + if self.tw.lc.dsobject is not None and \ + 'description' in self.tw.lc.dsobject.metadata: + description = self.tw.lc.dsobject.metadata[ + 'description'] + self.tw.lc.insert_desc(mimetype, description) + elif string[0:6] == 'audio_': + self.tw.lc.play_sound() + elif string[0:6] == 'video_': + self.tw.lc.play_video() + if self.tw.lc.dsobject is not None: + self.tw.lc.dsobject.destroy() + else: # assume it is text to display + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + elif type(string) == float or type(string) == int: + string = round_int(string) + x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.canvas.draw_text(string, x, y, + int(self.tw.canvas.textsize * \ + self.tw.lc.scale / 100.), + self.tw.canvas.width - x) + + def _prim_showlist(self, sarray): + """ Display list of media objects """ + x = self.tw.canvas.xcor / self.tw.coord_scale + y = self.tw.canvas.ycor / self.tw.coord_scale + for s in sarray: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(s) + y -= int(self.tw.canvas.textsize * self.tw.lead) + + def _prim_time(self): + """ Number of seconds since program execution has started or + clean (prim_clear) block encountered """ + elapsed_time = int(time() - self.tw.lc.start_time) + self.tw.lc.update_label_value('time', elapsed_time) + return elapsed_time + + # Deprecated blocks + + def _prim_t1x1(self, title, media): + """ title, one image, and description """ + xo = self.tw.calc_position('t1x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) \ + / self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x1(self, title, media1, media2): + """ title, two images (horizontal), two descriptions """ + xo = self.tw.calc_position('t2x1')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + if self.tw.running_sugar: + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x2(self, title, media1, media2): + """ title, two images (vertical), two desciptions """ + xo = self.tw.calc_position('t1x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + if self.tw.running_sugar: + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1.replace('media_', 'descr_')) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2.replace('media_', 'descr_')) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t2x2(self, title, media1, media2, media3, media4): + """ title and four images """ + xo = self.tw.calc_position('t2x2')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 45 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render four quadrents + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + x = 0 + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media2) + y = -self.title_height + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media4) + x = -(self.tw.canvas.width / 2) + xo + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media3) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_t1x1a(self, title, media1): + """ title, one media object """ + xo = self.tw.calc_position('t1x1a')[2] + x = -(self.tw.canvas.width / 2) + xo + y = self.tw.canvas.height / 2 + self.tw.canvas.setxy(x, y, pendown=False) + # save the text size so we can restore it later + save_text_size = self.tw.canvas.textsize + # set title text + self.tw.canvas.settextsize(self.title_height) + self._prim_show(title) + # calculate and set scale for media blocks + myscale = 90 * (self.tw.canvas.height - self.title_height * 2) / \ + self.tw.canvas.height + self._prim_setscale(myscale) + # set body text size + self.tw.canvas.settextsize(self.tw.lc.body_height) + # render media object + # leave some space below the title + y -= int(self.title_height * 2 * self.tw.lead) + self.tw.canvas.setxy(x, y, pendown=False) + self._prim_show(media1) + # restore text size + self.tw.canvas.settextsize(save_text_size) + + def _prim_write(self, string, fsize): + """ Write string at size """ + x = self.tw.canvas.width / 2 + int(self.tw.canvas.xcor) + y = self.tw.canvas.height / 2 - int(self.tw.canvas.ycor) + self.tw.canvas.draw_text(string, x, y - 15, int(fsize), + self.tw.canvas.width) -- cgit v0.9.1