summaryrefslogtreecommitdiff
path: root/gr-wxgui/src/python/powermate.py
diff options
context:
space:
mode:
authorjcorgan2006-08-03 04:51:51 +0000
committerjcorgan2006-08-03 04:51:51 +0000
commit5d69a524f81f234b3fbc41d49ba18d6f6886baba (patch)
treeb71312bf7f1e8d10fef0f3ac6f28784065e73e72 /gr-wxgui/src/python/powermate.py
downloadgnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.gz
gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.bz2
gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.zip
Houston, we have a trunk.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3122 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gr-wxgui/src/python/powermate.py')
-rwxr-xr-xgr-wxgui/src/python/powermate.py437
1 files changed, 437 insertions, 0 deletions
diff --git a/gr-wxgui/src/python/powermate.py b/gr-wxgui/src/python/powermate.py
new file mode 100755
index 000000000..36be408c4
--- /dev/null
+++ b/gr-wxgui/src/python/powermate.py
@@ -0,0 +1,437 @@
+#!/usr/bin/env python
+#
+# Copyright 2005 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio 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, or (at your option)
+# any later version.
+#
+# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+#
+
+"""
+Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs
+
+This is Linux and wxPython specific.
+"""
+import select
+import os
+import fcntl
+import struct
+import exceptions
+import threading
+import sys
+import wx
+from gnuradio import gru
+
+# First a little bit of background:
+#
+# The Griffin PowerMate has
+# * a single knob which rotates
+# * a single button (pressing the knob)
+#
+# The Contour ShuttleXpress (aka SpaceShuttle) has
+# * "Jog Wheel" -- the knob (rotary encoder) on the inside
+# * "Shuttle Ring" -- the spring loaded rubber covered ring
+# * 5 buttons
+#
+# The Contour ShuttlePro has
+# * "Jog Wheel" -- the knob (rotary encoder) on the inside
+# * "Shuttle Ring" -- the spring loaded rubber covered ring
+# * 13 buttons
+#
+# The Contour ShuttlePro V2 has
+# *"Jog Wheel" -- the knob (rotary encoder) on the inside
+# * "Shuttle Ring" -- the spring loaded rubber covered ring
+# * 15 buttons
+
+# We remap all the buttons on the devices so that they start at zero.
+
+# For the ShuttleXpress the buttons are 0 to 4 (left to right)
+
+# For the ShuttlePro, we number the buttons immediately above
+# the ring 0 to 4 (left to right) so that they match our numbering
+# on the ShuttleXpress. The top row is 5, 6, 7, 8. The first row below
+# the ring is 9, 10, and the bottom row is 11, 12.
+
+# For the ShuttlePro V2, buttons 13 & 14 are to the
+# left and right of the wheel respectively.
+
+# We generate 3 kinds of events:
+#
+# button press/release (button_number, press/release)
+# knob rotation (relative_clicks) # typically -1, +1
+# shuttle position (absolute_position) # -7,-6,...,0,...,6,7
+
+# ----------------------------------------------------------------
+# Our ID's for the devices:
+# Not to be confused with anything related to magic hardware numbers.
+
+ID_POWERMATE = 'powermate'
+ID_SHUTTLE_XPRESS = 'shuttle xpress'
+ID_SHUTTLE_PRO = 'shuttle pro'
+ID_SHUTTLE_PRO_V2 = 'shuttle pro v2'
+
+# ------------------------------------------------------------------------
+# format of messages that we read from /dev/input/event*
+# See /usr/include/linux/input.h for more info
+#
+#struct input_event {
+# struct timeval time; = {long seconds, long microseconds}
+# unsigned short type;
+# unsigned short code;
+# unsigned int value;
+#};
+
+input_event_struct = "@llHHi"
+input_event_size = struct.calcsize(input_event_struct)
+
+# ------------------------------------------------------------------------
+# input_event types
+# ------------------------------------------------------------------------
+
+IET_SYN = 0x00 # aka RESET
+IET_KEY = 0x01 # key or button press/release
+IET_REL = 0x02 # relative movement (knob rotation)
+IET_ABS = 0x03 # absolute position (graphics pad, etc)
+IET_MSC = 0x04
+IET_LED = 0x11
+IET_SND = 0x12
+IET_REP = 0x14
+IET_FF = 0x15
+IET_PWR = 0x16
+IET_FF_STATUS = 0x17
+IET_MAX = 0x1f
+
+# ------------------------------------------------------------------------
+# input_event codes (there are a zillion of them, we only define a few)
+# ------------------------------------------------------------------------
+
+# these are valid for IET_KEY
+
+IEC_BTN_0 = 0x100
+IEC_BTN_1 = 0x101
+IEC_BTN_2 = 0x102
+IEC_BTN_3 = 0x103
+IEC_BTN_4 = 0x104
+IEC_BTN_5 = 0x105
+IEC_BTN_6 = 0x106
+IEC_BTN_7 = 0x107
+IEC_BTN_8 = 0x108
+IEC_BTN_9 = 0x109
+IEC_BTN_10 = 0x10a
+IEC_BTN_11 = 0x10b
+IEC_BTN_12 = 0x10c
+IEC_BTN_13 = 0x10d
+IEC_BTN_14 = 0x10e
+IEC_BTN_15 = 0x10f
+
+# these are valid for IET_REL (Relative axes)
+
+IEC_REL_X = 0x00
+IEC_REL_Y = 0x01
+IEC_REL_Z = 0x02
+IEC_REL_HWHEEL = 0x06
+IEC_REL_DIAL = 0x07 # rotating the knob
+IEC_REL_WHEEL = 0x08 # moving the shuttle ring
+IEC_REL_MISC = 0x09
+IEC_REL_MAX = 0x0f
+
+# ------------------------------------------------------------------------
+
+class powermate(threading.Thread):
+ """
+ Interface to Griffin PowerMate and Contour Shuttles
+ """
+ def __init__(self, event_receiver=None, filename=None, **kwargs):
+ self.event_receiver = event_receiver
+ self.handle = -1
+ if filename:
+ if not self._open_device(filename):
+ raise exceptions.RuntimeError, 'Unable to find powermate'
+ else:
+ ok = False
+ for d in range(0, 16):
+ if self._open_device("/dev/input/event%d" % d):
+ ok = True
+ break
+ if not ok:
+ raise exceptions.RuntimeError, 'Unable to find powermate'
+
+ threading.Thread.__init__(self, **kwargs)
+ self.setDaemon (1)
+ self.keep_running = True
+ self.start ()
+
+ def __del__(self):
+ self.keep_running = False
+ if self.handle >= 0:
+ os.close(self.handle)
+ self.handle = -1
+
+ def _open_device(self, filename):
+ try:
+ self.handle = os.open(filename, os.O_RDWR)
+ if self.handle < 0:
+ return False
+
+ # read event device name
+ name = fcntl.ioctl(self.handle, gru.hexint(0x80ff4506), chr(0) * 256)
+ name = name.replace(chr(0), '')
+
+ # do we see anything we recognize?
+ if name == 'Griffin PowerMate' or name == 'Griffin SoundKnob':
+ self.id = ID_POWERMATE
+ self.mapper = _powermate_remapper()
+ elif name == 'CAVS SpaceShuttle A/V' or name == 'Contour Design ShuttleXpress':
+ self.id = ID_SHUTTLE_XPRESS
+ self.mapper = _contour_remapper()
+ elif name == 'Contour Design ShuttlePRO':
+ self.id = ID_SHUTTLE_PRO
+ self.mapper = _contour_remapper()
+ elif name == 'Contour Design ShuttlePRO v2':
+ self.id = ID_SHUTTLE_PRO_V2
+ self.mapper = _contour_remapper()
+ else:
+ os.close(self.handle)
+ self.handle = -1
+ return False
+
+ # get exclusive control of the device, using ioctl EVIOCGRAB
+ # there may be an issue with this on non x86 platforms and if
+ # the _IOW,_IOC,... macros in <asm/ioctl.h> are changed
+ fcntl.ioctl(self.handle,gru.hexint(0x40044590), 1)
+ return True
+ except exceptions.OSError:
+ return False
+
+
+ def set_event_receiver(self, obj):
+ self.event_receiver = obj
+
+
+ def set_led_state(self, static_brightness, pulse_speed=0,
+ pulse_table=0, pulse_on_sleep=0, pulse_on_wake=0):
+ """
+ What do these magic values mean...
+ """
+ if self.id != ID_POWERMATE:
+ return False
+
+ static_brightness &= 0xff;
+ if pulse_speed < 0:
+ pulse_speed = 0
+ if pulse_speed > 510:
+ pulse_speed = 510
+ if pulse_table < 0:
+ pulse_table = 0
+ if pulse_table > 2:
+ pulse_table = 2
+ pulse_on_sleep = not not pulse_on_sleep # not not = convert to 0/1
+ pulse_on_wake = not not pulse_on_wake
+ magic = (static_brightness
+ | (pulse_speed << 8)
+ | (pulse_table << 17)
+ | (pulse_on_sleep << 19)
+ | (pulse_on_wake << 20))
+ data = struct.pack(input_event_struct, 0, 0, 0x04, 0x01, magic)
+ os.write(self.handle, data)
+ return True
+
+ def run (self):
+ while (self.keep_running):
+ s = os.read (self.handle, input_event_size)
+ if not s:
+ self.keep_running = False
+ break
+
+ raw_input_event = struct.unpack(input_event_struct,s)
+ sec, usec, type, code, val = self.mapper(raw_input_event)
+
+ if self.event_receiver is None:
+ continue
+
+ if type == IET_SYN: # ignore
+ pass
+ elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness)
+ pass
+ elif type == IET_REL and code == IEC_REL_DIAL:
+ #print "Dial: %d" % (val,)
+ wx.PostEvent(self.event_receiver, PMRotateEvent(val))
+ elif type == IET_REL and code == IEC_REL_WHEEL:
+ #print "Shuttle: %d" % (val,)
+ wx.PostEvent(self.event_receiver, PMShuttleEvent(val))
+ elif type == IET_KEY:
+ #print "Key: Btn%d %d" % (code - IEC_BTN_0, val)
+ wx.PostEvent(self.event_receiver,
+ PMButtonEvent(code - IEC_BTN_0, val))
+ else:
+ print "powermate: unrecognized event: type = 0x%x code = 0x%x val = %d" % (type, code, val)
+
+
+class _powermate_remapper(object):
+ def __init__(self):
+ pass
+ def __call__(self, event):
+ """
+ Notice how nice and simple this is...
+ """
+ return event
+
+class _contour_remapper(object):
+ def __init__(self):
+ self.prev = None
+ def __call__(self, event):
+ """
+ ...and how screwed up this is
+ """
+ sec, usec, type, code, val = event
+ if type == IET_REL and code == IEC_REL_WHEEL:
+ # === Shuttle ring ===
+ # First off, this really ought to be IET_ABS, not IET_REL!
+ # They never generate a zero value so you can't
+ # tell when the shuttle ring is back in the center.
+ # We kludge around this by calling both -1 and 1 zero.
+ if val == -1 or val == 1:
+ return (sec, usec, type, code, 0)
+ return event
+
+ if type == IET_REL and code == IEC_REL_DIAL:
+ # === Jog knob (rotary encoder) ===
+ # Dim wits got it wrong again! This one should return a
+ # a relative value, e.g., -1, +1. Instead they return
+ # a total that runs modulo 256 (almost!). For some
+ # reason they count like this 253, 254, 255, 1, 2, 3
+
+ if self.prev is None: # first time call
+ self.prev = val
+ return (sec, usec, IET_SYN, 0, 0) # will be ignored above
+
+ diff = val - self.prev
+ if diff == 0: # sometimes it just sends stuff...
+ return (sec, usec, IET_SYN, 0, 0) # will be ignored above
+
+ if abs(diff) > 100: # crossed into the twilight zone
+ if self.prev > val: # we've wrapped going forward
+ self.prev = val
+ return (sec, usec, type, code, +1)
+ else: # we've wrapped going backward
+ self.prev = val
+ return (sec, usec, type, code, -1)
+
+ self.prev = val
+ return (sec, usec, type, code, diff)
+
+ if type == IET_KEY:
+ # remap keys so that all 3 gadgets have buttons 0 to 4 in common
+ return (sec, usec, type,
+ (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8,
+ IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4,
+ IEC_BTN_9, IEC_BTN_10,
+ IEC_BTN_11, IEC_BTN_12,
+ IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val)
+
+ return event
+
+# ------------------------------------------------------------------------
+# new wxPython event classes
+# ------------------------------------------------------------------------
+
+grEVT_POWERMATE_BUTTON = wx.NewEventType()
+grEVT_POWERMATE_ROTATE = wx.NewEventType()
+grEVT_POWERMATE_SHUTTLE = wx.NewEventType()
+
+EVT_POWERMATE_BUTTON = wx.PyEventBinder(grEVT_POWERMATE_BUTTON, 0)
+EVT_POWERMATE_ROTATE = wx.PyEventBinder(grEVT_POWERMATE_ROTATE, 0)
+EVT_POWERMATE_SHUTTLE = wx.PyEventBinder(grEVT_POWERMATE_SHUTTLE, 0)
+
+class PMButtonEvent(wx.PyEvent):
+ def __init__(self, button, value):
+ wx.PyEvent.__init__(self)
+ self.SetEventType(grEVT_POWERMATE_BUTTON)
+ self.button = button
+ self.value = value
+
+ def Clone (self):
+ self.__class__(self.GetId())
+
+
+class PMRotateEvent(wx.PyEvent):
+ def __init__(self, delta):
+ wx.PyEvent.__init__(self)
+ self.SetEventType (grEVT_POWERMATE_ROTATE)
+ self.delta = delta
+
+ def Clone (self):
+ self.__class__(self.GetId())
+
+
+class PMShuttleEvent(wx.PyEvent):
+ def __init__(self, position):
+ wx.PyEvent.__init__(self)
+ self.SetEventType (grEVT_POWERMATE_SHUTTLE)
+ self.position = position
+
+ def Clone (self):
+ self.__class__(self.GetId())
+
+# ------------------------------------------------------------------------
+# Example usage
+# ------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ class Frame(wx.Frame):
+ def __init__(self,parent=None,id=-1,title='Title',
+ pos=wx.DefaultPosition, size=(400,200)):
+ wx.Frame.__init__(self,parent,id,title,pos,size)
+ EVT_POWERMATE_BUTTON(self, self.on_button)
+ EVT_POWERMATE_ROTATE(self, self.on_rotate)
+ EVT_POWERMATE_SHUTTLE(self, self.on_shuttle)
+ self.brightness = 128
+ self.pulse_speed = 0
+
+ try:
+ self.pm = powermate(self)
+ except:
+ sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n")
+ sys.exit(1)
+
+ self.pm.set_led_state(self.brightness, self.pulse_speed)
+
+
+ def on_button(self, evt):
+ print "Button %d %s" % (evt.button,
+ ("Released", "Pressed")[evt.value])
+
+ def on_rotate(self, evt):
+ print "Rotated %d" % (evt.delta,)
+ if 0:
+ new = max(0, min(255, self.brightness + evt.delta))
+ if new != self.brightness:
+ self.brightness = new
+ self.pm.set_led_state(self.brightness, self.pulse_speed)
+
+ def on_shuttle(self, evt):
+ print "Shuttle %d" % (evt.position,)
+
+ class App(wx.App):
+ def OnInit(self):
+ title='PowerMate Demo'
+ self.frame = Frame(parent=None,id=-1,title=title)
+ self.frame.Show()
+ self.SetTopWindow(self.frame)
+ return True
+
+ app = App()
+ app.MainLoop ()