jog updates
authorJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 12 Mar 2016 00:02:00 +0000 (16:02 -0800)
committerJoseph Coffland <joseph@cauldrondevelopment.com>
Sat, 12 Mar 2016 00:02:00 +0000 (16:02 -0800)
19 files changed:
.gitignore
CODE_TAG [deleted file]
Makefile
bbctrl.py
inevent/AbsAxisScaling.py [new file with mode: 0644]
inevent/Constants.py [new file with mode: 0644]
inevent/Event.py [new file with mode: 0644]
inevent/EventHandler.py [new file with mode: 0644]
inevent/EventState.py [new file with mode: 0644]
inevent/EventStream.py [new file with mode: 0644]
inevent/FindDevices.py [new file with mode: 0644]
inevent/Format.py [new file with mode: 0644]
inevent/InEvent.py [new file with mode: 0644]
inevent/JogHandler.py [new file with mode: 0644]
inevent/Keys.py [new file with mode: 0644]
inevent/__init__.py [new file with mode: 0644]
inevent/ioctl.py [new file with mode: 0644]
jogtest.py [new file with mode: 0755]
setup_rpi.sh

index 99a7751f278b4f7f6ebba86dbc3439d94f721c38..6f2b850c5a6d7c2bd78d3d9327f73bebfa01595c 100644 (file)
@@ -1,4 +1,8 @@
 .sconf_temp/
 .sconsign.dblite
+/http
+node_modules
 *~
 \#*
+*.pyc
+__pycache__
diff --git a/CODE_TAG b/CODE_TAG
deleted file mode 100644 (file)
index 1bb4d7c..0000000
--- a/CODE_TAG
+++ /dev/null
@@ -1,27 +0,0 @@
-             This file is part of the Buildbotics Machine Controller.
-
-                    Copyright (c) 2015-2016, Buildbotics LLC
-                               All rights reserved.
-
-        The Buildbotics Machine Controller 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.
-
-        The Buildbotics Webserver 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 software.  If not, see
-        <http://www.gnu.org/licenses/>.
-
-        In addition, BSD licensing may be granted on a case by case basis
-        by written permission from at least one of the copyright holders.
-        You may request written permission by emailing the authors.
-
-                For information regarding this software email:
-                               Joseph Coffland
-                        joseph@cauldrondevelopment.com
index ebbe0d9a1be625ea82723015ab93bc63ec521fe7..ff09db98012986c690a1aa5bd534584fb08ed452 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,14 +17,18 @@ TEMPLS    := $(wildcard src/jade/templates/*.jade)
 STATIC    := $(shell find src/resources -type f)
 STATIC    := $(patsubst src/resources/%,http/%,$(STATIC))
 
+ifndef DEST
+DEST=bbctrl/
+endif
+
 WATCH := src/jade src/stylus src/js Makefile
 
 TARGETS := $(HTML) $(CSS) $(JS_ASSETS) $(STATIC)
 
-all: node_modules $(TARGETS) copy
+all: node_modules $(TARGETS)
 
 copy: $(TARGETS)
-       cp -r bbctrl.py http/ bbctrl/
+       cp -r *.py inevent http/ $(DEST)
 
 $(HTTP)/admin.html: build/templates.jade
 
index c6f8a486678c9f8d158ac92e3580e1ae15c96a70..623f077e262d389f742d4ace14f904130e59b867 100755 (executable)
--- a/bbctrl.py
+++ b/bbctrl.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 
 ## Change this to match your local settings
 SERIAL_PORT = '/dev/ttyAMA0'
@@ -10,9 +10,23 @@ from sockjs.tornado import SockJSRouter, SockJSConnection
 import json
 import serial
 import multiprocessing
+import time
+
+import inevent
+from inevent.Constants import *
+
 
 DIR = os.path.dirname(__file__)
 
+config = {
+    "deadband": 0.1,
+    "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
+    "arrows": [ABS_HAT0X, ABS_HAT0Y],
+    "speed": [0x120, 0x121, 0x122, 0x123],
+    "activate": [0x124, 0x126, 0x125, 0x127],
+    "verbose": False
+    }
+
 state = {}
 clients = []
 
@@ -38,7 +52,7 @@ class SerialProcess(multiprocessing.Process):
 
 
     def readSerial(self):
-        return self.sp.readline().replace("\n", "")
+        return self.sp.readline().replace(b"\n", b"")
 
 
     def run(self):
@@ -57,7 +71,7 @@ class SerialProcess(multiprocessing.Process):
                 data = self.readSerial()
                 # send it back to tornado
                 self.output_queue.put(data)
-                print data
+                print(data.decode('utf-8'))
 
 
 
@@ -66,6 +80,7 @@ class Connection(SockJSConnection):
         clients.append(self)
         self.send(state)
 
+
     def on_close(self):
         clients.remove(self)
 
@@ -94,6 +109,36 @@ handlers = [
 router = SockJSRouter(Connection, '/ws')
 
 
+# Listen for input events
+class JogHandler(inevent.JogHandler):
+    def __init__(self, config):
+        super().__init__(config)
+
+        self.lastV = [0.0] * 4
+
+
+    def changed(self):
+        if self.speed == 1: scale = 1.0 / 128.0
+        if self.speed == 2: scale = 1.0 / 32.0
+        if self.speed == 3: scale = 1.0 / 4.0
+        if self.speed == 4: scale = 1.0
+
+        v = [x * scale for x in self.axes]
+
+        if v != self.lastV:
+            self.lastV = v
+
+            v = ["{:6.5f}".format(x) for x in v]
+            cmd = '$jog ' + ' '.join(v) + '\n'
+            input_queue.put(cmd)
+
+
+def checkEvents(): eventProcessor.process_events(eventHandler)
+eventProcessor = inevent.InEvent(types = "js kbd".split())
+eventHandler = JogHandler(config)
+
+
+
 if __name__ == "__main__":
     import logging
     logging.getLogger().setLevel(logging.DEBUG)
@@ -105,6 +150,7 @@ if __name__ == "__main__":
 
     # Adjust the interval according to frames sent by serial port
     ioloop.PeriodicCallback(checkQueue, 100).start()
+    ioloop.PeriodicCallback(checkEvents, 100).start()
 
     # Start the web server
     app = web.Application(router.urls + handlers)
diff --git a/inevent/AbsAxisScaling.py b/inevent/AbsAxisScaling.py
new file mode 100644 (file)
index 0000000..bd4baa1
--- /dev/null
@@ -0,0 +1,80 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 array
+import fcntl
+import struct
+from inevent import ioctl
+
+
+def EVIOCGABS(axis):
+  return ioctl._IOR(ord('E'), 0x40 + axis, "ffffff") # get abs value/limits
+
+
+
+class AbsAxisScaling(object):
+  """
+  Fetches and implements the EV_ABS axis scaling.
+
+  The constructor fetches the scaling values from the given stream for the
+  given axis using an ioctl.
+
+  There is a scale method, which scales a given value to the range -1..+1.
+  """
+
+  def __init__(self, stream, axis):
+    """
+    Fetch the scale values for this stream and fill in the instance
+    variables accordingly.
+    """
+    s = array.array("i", [1, 2, 3, 4, 5, 6])
+    try:
+      fcntl.ioctl(stream.filehandle, EVIOCGABS(axis), s)
+
+    except IOError:
+      self.value = self.minimum = self.maximum = self.fuzz = self.flat = \
+          self.resolution = 1
+
+    else:
+      self.value, self.minimum, self.maximum, self.fuzz, self.flat, \
+          self.resolution = struct.unpack("iiiiii", s)
+
+
+  def __str__(self):
+    return "Value {0} Min {1}, Max {2}, Fuzz {3}, Flat {4}, Res {5}".format(
+      self.value, self.minimum, self.maximum, self.fuzz, self.flat,
+      self.resolution)
+
+
+  def scale(self, value):
+    """
+    scales the given value into the range -1..+1
+    """
+    return (float(value) - float(self.minimum)) / \
+        float(self.maximum - self.minimum) * 2.0 - 1.0
+
diff --git a/inevent/Constants.py b/inevent/Constants.py
new file mode 100644 (file)
index 0000000..00be4cb
--- /dev/null
@@ -0,0 +1,97 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+
+EV_SYN = 0x00
+EV_KEY = 0x01
+EV_REL = 0x02
+EV_ABS = 0x03
+EV_MSC = 0x04
+EV_SW  = 0x05
+EV_LED = 0x11
+EV_SND = 0x12
+EV_REP = 0x14
+EV_FF  = 0x15
+EV_PWR = 0x16
+EV_FF_STATUS = 0x17
+
+ev_type_name = {}
+ev_type_name[EV_SYN] = "SYN"
+ev_type_name[EV_KEY] = "KEY"
+ev_type_name[EV_REL] = "REL"
+ev_type_name[EV_ABS] = "ABS"
+ev_type_name[EV_MSC] = "MSC"
+ev_type_name[EV_SW]  = "SW"
+ev_type_name[EV_LED] = "LED"
+ev_type_name[EV_SND] = "SND"
+ev_type_name[EV_REP] = "REP"
+ev_type_name[EV_FF]  = "FF"
+ev_type_name[EV_PWR] = "PWR"
+ev_type_name[EV_FF_STATUS] = "FF_STATUS"
+
+SYN_REPORT = 0
+SYN_CONFIG = 1
+
+REL_X = 0x00
+REL_Y = 0x01
+REL_Z = 0x02
+REL_RX = 0x03
+REL_RY = 0x04
+REL_RZ = 0x05
+REL_HWHEEL = 0x06
+REL_DIAL = 0x07
+REL_WHEEL = 0x08
+REL_MISC = 0x09
+REL_MAX = 0x0f
+
+ABS_X = 0x00
+ABS_Y = 0x01
+ABS_Z = 0x02
+ABS_RX = 0x03
+ABS_RY = 0x04
+ABS_RZ = 0x05
+ABS_THROTTLE = 0x06
+ABS_RUDDER = 0x07
+ABS_WHEEL = 0x08
+ABS_GAS = 0x09
+ABS_BRAKE = 0x0a
+ABS_HAT0X = 0x10
+ABS_HAT0Y = 0x11
+ABS_HAT1X = 0x12
+ABS_HAT1Y = 0x13
+ABS_HAT2X = 0x14
+ABS_HAT2Y = 0x15
+ABS_HAT3X = 0x16
+ABS_HAT3Y = 0x17
+ABS_PRESSURE = 0x18
+ABS_DISTANCE = 0x19
+ABS_TILT_X = 0x1a
+ABS_TILT_Y = 0x1b
+ABS_TOOL_WIDTH = 0x1c
+ABS_VOLUME = 0x20
+ABS_MISC = 0x28
+ABS_MAX = 0x3f
diff --git a/inevent/Event.py b/inevent/Event.py
new file mode 100644 (file)
index 0000000..c14e9d4
--- /dev/null
@@ -0,0 +1,115 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 struct
+
+from inevent import Format
+from inevent.Constants import *
+
+
+
+class Event(object):
+  """
+  A single event from the linux input event system.
+
+  Events are tuples: (Time, Type, Code, Value)
+  In addition we remember the stream it came from.
+
+  Externally, only the unhandled event handler gets passed the whole event,
+  but the SYN handler gets the code and value. (Also the keyboard handler, but
+  those are renamed to key and value.)
+
+  This class is responsible for converting the Linux input event structure into
+  one of these objects and back again.
+  """
+  def __init__(self, stream, time = None, type = None, code = None,
+               value = None):
+    """
+    Create a new event.
+
+    Generally all but the stream parameter are left out; we will want to
+    populate the object from a Linux input event using decode.
+    """
+    self.stream = stream
+    self.time = time
+    self.type = type
+    self.code = code
+    self.value = value
+
+
+  def get_type_name(self):
+    if self.type not in ev_type_name: return '0x%x' % self.type
+    return ev_type_name[self.type]
+
+
+  def get_source(self):
+    return "%s[%d]" % (self.stream.devType, self.stream.devIndex)
+
+
+  def __str__(self):
+    """
+    Uses the stream to give the device type and whether it is currently grabbed.
+    """
+    grabbed = "grabbed" if self.stream.grabbed else "ungrabbed"
+
+    return "Event %s %s @%f: %s 0x%x=0x%x" % (
+      self.get_source(), grabbed, self.time, self.get_type_name(), self.code,
+      self.value)
+
+
+  def __repr__(self):
+    return "Event(%s, %f, 0x%x, 0x%x, 0x%x)" % (
+      repr(self.stream), self.time, self.type, self.code, self.value)
+
+
+  def encode(self):
+    """
+    Encode this event into a Linux input event structure.
+
+    The output is packed into a string. It is unlikely that this function
+    will be required, but it might as well be here.
+    """
+    tint = long(self.time)
+    tfrac = long((self.time - tint) * 1000000)
+
+    return \
+        struct.pack(Format.Event, tsec, tfrac, self.type, self.code, self.value)
+
+
+  def decode(self, s):
+    """
+    Decode a Linux input event into the fields of this object.
+
+    Arguments:
+      *s*
+        A binary structure packed into a string.
+    """
+    tsec, tfrac, self.type, self.code, self.value = \
+        struct.unpack(Format.Event, s)
+
+    self.time = tsec + tfrac / 1000000.0
diff --git a/inevent/EventHandler.py b/inevent/EventHandler.py
new file mode 100644 (file)
index 0000000..7a93a35
--- /dev/null
@@ -0,0 +1,120 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+
+from inevent.Constants import *
+from inevent.EventStream import EventStream
+
+
+class EventHandler(object):
+  """
+  A class to handle events.
+
+  Four types of events are handled: REL (mouse movement), KEY (keybaord keys and
+  other device buttons), ABS (joysticks and gamepad analogue sticks) and SYN
+  (delimits simultaneous events such as mouse movements)
+  """
+  def __init__(self):
+    self.buttons = dict()
+
+
+  def event(self, event, handler):
+    """
+    Handles the given event.
+
+    If the event is passed to a handler or otherwise handled then returns None,
+    else returns the event. All handlers are optional.
+
+    All key events are handled by putting them in the self.buttons dict, and
+    optionally by calling the supplied handler.
+
+    REL X, Y and wheel V and H events are all accumulated internally and
+    also optionally passed to the supplied handler. All these events are
+    handled.
+
+    ABS X, Y, Z, RX, RY, RZ, Hat0X, Hat0Y are all accumulated internally and
+    also optionally passed to the supplied handler. Other ABS events are not
+    handled.
+
+    All SYN events are passed to the supplied handler.
+
+    There are several ABS events that we do not handle. In particular:
+    THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
+    DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
+    for the interested reader.
+
+    Likewise, since one handler is handling all events for all devices, we
+    may get the situation where two devices return the same button. The only
+    way to handle that would seem to be to have a key dict for every device,
+    which seems needlessly profligate for a situation that may never arise.
+    """
+
+    state = event.stream.state
+
+    if event.type == EV_KEY: self.buttons[event.code] = event.value
+    elif event.type == EV_REL: state.rel[event.code] += event.value
+    elif event.type == EV_ABS:
+      state.abs[event.code] = event.stream.scale(event.code, event.value)
+
+    if handler: handler(event, state)
+
+
+  def key_state(self, code):
+    """
+    Returns the last event value for the given key code.
+
+    Key names can be converted to key codes using codeOf[str].
+    If the key is pressed the returned value will be 1 (pressed) or 2 (held).
+    If the key is not pressed, the returned value will be 0.
+    """
+    return self.buttons.get(code, 0)
+
+
+  def clear_key(self, code):
+    """
+    Clears the event value for the given key code.
+
+    Key names can be converted to key codes using codeOf[str].
+    This emulates a key-up but does not generate any events.
+    """
+    self.buttons[code] = 0
+
+
+  def get_keys(self):
+    """
+    Returns the first of whichever keys have been pressed.
+
+    Key names can be converted to key codes using codeOf[str].
+    This emulates a key-up but does not generate any events.
+    """
+    k_list = []
+
+    for k in self.buttons:
+      if self.buttons[k] != 0: k_list.append(k)
+
+    return k_list
+
diff --git a/inevent/EventState.py b/inevent/EventState.py
new file mode 100644 (file)
index 0000000..a87ae7b
--- /dev/null
@@ -0,0 +1,118 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+
+from inevent.Constants import *
+
+
+class EventState:
+  def __init__(self):
+    self.abs = [0.0] * ABS_MAX
+    self.rel = [0.0] * REL_MAX
+
+
+  def __str__(self):
+    return ("({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystick3d()) +
+            "({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystickR3d()) +
+            "({:2.0f}, {:2.0f}) ".format(*self.get_hat()) +
+            "({:d}, {:d}) ".format(*self.get_mouse()) +
+            "({:d}, {:d})".format(*self.get_wheel()))
+
+
+  def get_joystick(self):
+    """
+    Returns the x,y coordinates for a joystick or left gamepad analogue stick.
+
+    The values are returned as a tuple. All values are -1.0 to +1.0 with
+    0.0 being centred.
+    """
+    return self.abs[ABS_X], self.abs[ABS_Y]
+
+
+  def get_joystick3d(self):
+    """
+    Returns the x,y,z coordinates for a joystick or left gamepad analogue stick
+
+    The values are returned as a tuple. All values are -1.0 to +1.0 with
+    0.0 being centred.
+    """
+    return self.abs[ABS_X], self.abs[ABS_Y], self.abs[ABS_Z]
+
+
+  def get_joystickR(self):
+    """
+    Returns the x,y coordinates for a right gamepad analogue stick.
+
+    The values are returned as a tuple. For some odd reason, the gamepad
+    returns values in the Z axes of both joysticks, with y being the first.
+
+    All values are -1.0 to +1.0 with 0.0 being centred.
+    """
+    return self.abs[ABS_RZ], self.abs[ABS_Z]
+
+
+  def get_joystickR3d(self):
+    """
+    Returns the x,y,z coordinates for a 2nd joystick control
+
+    The values are returned as a tuple. All values are -1.0 to +1.0 with
+    0.0 being centred.
+    """
+    return self.abs[ABS_RX], self.abs[ABS_RY], self.abs[ABS_RZ]
+
+
+  def get_hat(self):
+    """
+    Returns the x,y coordinates for a joystick hat or gamepad direction pad
+
+    The values are returned as a tuple.  All values are -1.0 to +1.0 with
+    0.0 being centred.
+    """
+    return self.abs[ABS_HAT0X], self.abs[ABS_HAT0Y]
+
+
+  def get_mouse(self):
+    return self.rel[REL_X], self.rel[REL_Y]
+
+
+  def get_wheel(self):
+    return self.rel[REL_WHEEL], self.rel[REL_HWHEEL]
+
+
+  def get_mouse_movement(self):
+    """
+    Returns the accumulated REL (mouse or other relative device) movements
+    since the last call.
+
+    The returned value is a tuple: (X, Y, WHEEL, H-WHEEL)
+    """
+    ret = self.get_mouse() + self.get_wheel()
+
+    self.rel[REL_X] = self.rel[REL_Y] = 0
+    self.rel[REL_WHEEL] = self.rel[REL_HWHEEL] = 0
+
+    return ret
diff --git a/inevent/EventStream.py b/inevent/EventStream.py
new file mode 100644 (file)
index 0000000..ab4b7e1
--- /dev/null
@@ -0,0 +1,184 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 fcntl
+import os
+import select
+
+from inevent.Constants import *
+from inevent import ioctl
+from inevent.AbsAxisScaling import AbsAxisScaling
+from inevent import Event
+from inevent import Format
+from inevent.EventState import EventState
+
+
+EVIOCGRAB = ioctl._IOW(ord('E'), 0x90, "i") # Grab/Release device
+
+
+
+class EventStream(object):
+  """
+  encapsulates the event* file handling
+
+  Each device is represented by a file in /dev/input called eventN, where N is
+  a small number. (Actually, a keybaord can be represented by two such files.)
+  Instances of this class open one of these files and provide means to read
+  events from them.
+
+  Class methods also exist to read from multiple files simultaneously, and
+  also to grab and ungrab all instances of a given type.
+  """
+  axisX = 0
+  axisY = 1
+  axisZ = 2
+  axisRX = 3
+  axisRY = 4
+  axisRZ = 5
+  axisHat0X = 6
+  axisHat0Y = 7
+  axisHat1X = 8
+  axisHat1Y = 9
+  axisHat2X = 10
+  axisHat2Y = 11
+  axisHat3X = 12
+  axisHat3Y = 13
+  axisThrottle = 14
+  axisRudder = 15
+  axisWheel = 16
+  axisGas = 17
+  axisBrake = 18
+  axisPressure = 19
+  axisDistance = 20
+  axisTiltX = 21
+  axisTiltY = 22
+  axisToolWidth = 23
+  numAxes = 24
+
+  axisToEvent = [
+    ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ, ABS_HAT0X, ABS_HAT0Y,
+    ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y, ABS_HAT3X, ABS_HAT3Y,
+    ABS_THROTTLE, ABS_RUDDER, ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_PRESSURE,
+    ABS_DISTANCE, ABS_TILT_X, ABS_TILT_Y, ABS_TOOL_WIDTH]
+
+
+  def __init__(self, devIndex, devType):
+    """
+    Opens the given /dev/input/event file and grabs it.
+
+    Also adds it to a class-global list of all existing streams.
+    """
+    self.devIndex = devIndex
+    self.devType = devType
+    self.filename = "/dev/input/event" + str(devIndex)
+    self.filehandle = os.open(self.filename, os.O_RDWR)
+    self.state = EventState()
+    self.grab(True)
+    self.absInfo = [None] * ABS_MAX
+
+    if devType == "js":
+      for axis in range(ABS_MAX):
+        self.absInfo[axis] = AbsAxisScaling(self, axis)
+
+
+  def scale(self, axis, value):
+    """
+    Scale the given value according to the given axis.
+
+    acquire_abs_info must have been previously called to acquire the data to
+    do the scaling.
+    """
+    assert axis < ABS_MAX, "Axis number out of range"
+
+    if self.absInfo[axis]: return self.absInfo[axis].scale(value)
+    else: return value
+
+
+  def grab(self, grab = True):
+    """
+    Grab (or release) exclusive access to all devices of the given type.
+
+    The devices are grabbed if grab is True and released if grab is False.
+
+    All devices are grabbed to begin with. We might want to ungrab the
+    keyboard for example to use it for text entry. While not grabbed, all
+    key-down and key-hold events are filtered out.
+    """
+    fcntl.ioctl(self.filehandle, EVIOCGRAB, 1 if grab else 0)
+    self.grabbed = grab
+
+
+  def __iter__(self):
+    """
+    Required to make this class an iterator
+    """
+    return self
+
+
+  def next(self):
+    """
+    Returns the next waiting event.
+
+    If no event is waiting, returns None.
+    """
+    ready = select.select([self.filehandle], [], [], 0)[0]
+    if ready: return self.read()
+
+
+  def read(self):
+    """
+    Read and return the next waiting event.
+    """
+    try:
+      s = os.read(self.filehandle, Format.EventSize)
+      if s:
+        event = Event.Event(self)
+        event.decode(s)
+        return event
+
+    except: pass
+
+
+  def __enter__(self): return self
+
+
+  def release(self):
+    "Ungrabs the file and closes it."
+
+    try:
+      self.grab(False)
+      os.close(self.filehandle)
+
+    except:
+      pass
+
+
+  def __exit__(self, type, value, traceback):
+    "Ungrabs the file and closes it."
+
+    self.release()
diff --git a/inevent/FindDevices.py b/inevent/FindDevices.py
new file mode 100644 (file)
index 0000000..17a667a
--- /dev/null
@@ -0,0 +1,215 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 re
+from inevent.Constants import *
+
+
+def test_bit(nlst, b):
+  index = b / 32
+  bit = b % 32
+  return index < len(nlst) and nlst[index] & (1 << bit)
+
+
+def EvToStr(events):
+  s = []
+
+  if test_bit(events, EV_SYN):       s.append("EV_SYN")
+  if test_bit(events, EV_KEY):       s.append("EV_KEY")
+  if test_bit(events, EV_REL):       s.append("EV_REL")
+  if test_bit(events, EV_ABS):       s.append("EV_ABS")
+  if test_bit(events, EV_MSC):       s.append("EV_MSC")
+  if test_bit(events, EV_LED):       s.append("EV_LED")
+  if test_bit(events, EV_SND):       s.append("EV_SND")
+  if test_bit(events, EV_REP):       s.append("EV_REP")
+  if test_bit(events, EV_FF):        s.append("EV_FF" )
+  if test_bit(events, EV_PWR):       s.append("EV_PWR")
+  if test_bit(events, EV_FF_STATUS): s.append("EV_FF_STATUS")
+    
+  return s
+
+
+class DeviceCapabilities(object):
+  def __init__(self, firstLine, filehandle):
+    self.EV_SYNevents = []
+    self.EV_KEYevents = []
+    self.EV_RELevents = []
+    self.EV_ABSevents = []
+    self.EV_MSCevents = []
+    self.EV_LEDevents = []
+    self.EV_SNDevents = []
+    self.EV_REPevents = []
+    self.EV_FFevents = []
+    self.EV_PWRevents = []
+    self.EV_FF_STATUSevents = []
+    self.eventTypes = []
+    
+    match = re.search(".*Bus=([0-9A-Fa-f]+).*Vendor=([0-9A-Fa-f]+).*"
+                      "Product=([0-9A-Fa-f]+).*Version=([0-9A-Fa-f]+).*",
+                      firstLine)
+
+    if not match:
+      print("Do not understand device ID:", line)
+      self.bus = 0
+      self.vendor = 0
+      self.product = 0
+      self.version = 0
+
+    else:
+      self.bus = int(match.group(1), base = 16)
+      self.vendor = int(match.group(2), base = 16)
+      self.product = int(match.group(3), base = 16)
+      self.version = int(match.group(4), base = 16)
+
+    for line in filehandle:
+      if not line.strip(): break
+
+      if line[0] == "N":
+        match = re.search('Name="([^"]+)"', line)
+        if match: self.name = match.group(1)
+        else: self.name = "UNKNOWN"
+
+      elif line[0] == "P":
+        match = re.search('Phys=(.+)', line)
+        if match: self.phys = match.group(1)
+        else: self.phys = "UNKNOWN"
+
+      elif line[0] == "S":
+          match = re.search('Sysfs=(.+)', line)
+          if match: self.sysfs = match.group(1)
+          else: self.sysfs = "UNKNOWN"
+
+      elif line[0] == "U":
+        match = re.search('Uniq=(.*)', line)
+        if match: self.uniq = match.group(1)
+        else: self.uniq = "UNKNOWN"
+
+      elif line[0] == "H":
+        match = re.search('Handlers=(.+)', line)
+        if match: self.handlers = match.group(1).split()
+        else: self.handlers = []
+
+      elif line[:5] == "B: EV":
+        eventsNums = [int(x, base = 16) for x in line[6:].split()]
+        eventsNums.reverse()
+        self.eventTypes = eventsNums
+
+      elif line[:6] == "B: KEY":
+        eventsNums = [int(x, base = 16) for x in line[7:].split()]
+        eventsNums.reverse()
+        self.EV_KEYevents = eventsNums
+
+      elif line[:6] == "B: ABS":
+        eventsNums = [int(x, base = 16) for x in line[7:].split()]
+        eventsNums.reverse()
+        self.EV_ABSevents = eventsNums
+
+      elif line[:6] == "B: MSC":
+        eventsNums = [int(x, base = 16) for x in line[7:].split()]
+        eventsNums.reverse()
+        self.EV_MSCevents = eventsNums
+
+      elif line[:6] == "B: REL":
+        eventsNums = [int(x, base = 16) for x in line[7:].split()]
+        eventsNums.reverse()
+        self.EV_RELevents = eventsNums
+
+      elif line[:6] == "B: LED":
+        eventsNums = [int(x, base = 16) for x in line[7:].split()]
+        eventsNums.reverse()
+        self.EV_LEDevents = eventsNums
+
+    for handler in self.handlers:
+      if handler[:5] == "event": self.eventIndex = int(handler[5:])
+
+    self.isMouse = False
+    self.isKeyboard = False
+    self.isJoystick = False
+
+
+  def doesProduce(self, eventType, eventCode):
+    return test_bit(self.eventTypes, eventType) and (
+      (eventType == EV_SYN and test_bit(self.EV_SYNevents, eventCode)) or
+      (eventType == EV_KEY and test_bit(self.EV_KEYevents, eventCode)) or
+      (eventType == EV_REL and test_bit(self.EV_RELevents, eventCode)) or
+      (eventType == EV_ABS and test_bit(self.EV_ABSevents, eventCode)) or
+      (eventType == EV_MSC and test_bit(self.EV_MSCevents, eventCode)) or
+      (eventType == EV_LED and test_bit(self.EV_LEDevents, eventCode)) or
+      (eventType == EV_SND and test_bit(self.EV_SNDevents, eventCode)) or
+      (eventType == EV_REP and test_bit(self.EV_REPevents, eventCode)) or
+      (eventType == EV_FF  and test_bit(self.EV_FFevents,  eventCode)) or
+      (eventType == EV_PWR and test_bit(self.EV_PWRevents, eventCode)) or
+      (eventType == EV_FF_STATUS and
+       test_bit(self.EV_FF_STATUSevents, eventCode)))
+
+
+  def __str__(self):
+    return (
+      "%s\n"
+      "Bus: %s Vendor: %s Product: %s Version: %s\n"
+      "Phys: %s\n"
+      "Sysfs: %s\n"
+      "Uniq: %s\n"
+      "Handlers: %s Event Index: %s\n"
+      "Keyboard: %s Mouse: %s Joystick: %s\n"
+      "Events: %s" % (
+        self.name, self.bus. self.vendor, self.product, self.version, self.phys,
+        self.sysfs, self.uniq, self.handlers, self.eventIndex, self.isKeyboard,
+        self.isMouse, self.isJoystick, EvToStr(self.eventTypes)))
+
+        
+deviceCapabilities = []
+
+
+def get_devices(filename = "/proc/bus/input/devices"):
+  global deviceCapabilities
+
+  with open("/proc/bus/input/devices", "r") as filehandle:
+    for line in filehandle:
+      if line[0] == "I":
+        deviceCapabilities.append(DeviceCapabilities(line, filehandle))
+              
+  return deviceCapabilities
+
+
+def print_devices():
+  devs = get_devices()
+
+  for dev in devs:
+    print(str(dev))
+    print("   ABS: {}"
+          .format([x for x in range(64) if test_bit(dev.EV_ABSevents, x)]))
+    print("   REL: {}"
+          .format([x for x in range(64) if test_bit(dev.EV_RELevents, x)]))
+    print("   MSC: {}"
+          .format([x for x in range(64) if test_bit(dev.EV_MSCevents, x)]))
+    print("   KEY: {}"
+          .format([x for x in range(512) if test_bit(dev.EV_KEYevents, x)]))
+    print("   LED: {}"
+          .format([x for x in range(64) if test_bit(dev.EV_LEDevents, x)]))
+    print()
diff --git a/inevent/Format.py b/inevent/Format.py
new file mode 100644 (file)
index 0000000..d91665e
--- /dev/null
@@ -0,0 +1,32 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 struct
+
+Event = 'llHHi'
+EventSize = struct.calcsize(Event)
diff --git a/inevent/InEvent.py b/inevent/InEvent.py
new file mode 100644 (file)
index 0000000..fab068a
--- /dev/null
@@ -0,0 +1,252 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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 pyudev
+import re
+import select
+import errno
+
+from inevent.EventHandler import EventHandler
+from inevent import Keys
+from inevent.Constants import *
+from inevent.EventStream import EventStream
+
+
+_KEYS = (k for k in vars(Keys) if not k.startswith('_'))
+KEY_CODE = dict((k, getattr(Keys, k)) for k in _KEYS)
+CODE_KEY = {}
+for v in KEY_CODE: CODE_KEY[KEY_CODE[v]] = v
+
+
+def key_to_code(key):
+  return KEY_CODE.get(str(key), -1) \
+      if isinstance(key, str) else key
+
+
+def code_to_key(code): return CODE_KEY.get(code, '')
+
+
+def find_devices(types):
+  """Finds the event indices of all devices of the specified types.
+
+  A type is a string on the handlers line of /proc/bus/input/devices.
+  Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
+
+  Returns a list of integer indexes N, where /dev/input/eventN is the event
+  stream for each device.
+
+  If butNot is given it holds a list of tuples which the returned values should
+  not match.
+
+  All devices of each type are returned; if you have two mice, they will both
+  be used.
+  """
+  with open("/proc/bus/input/devices", "r") as filehandle:
+    for line in filehandle:
+      if line[0] == "H":
+        for type in types:
+          if type in line:
+            match = re.search("event([0-9]+)", line)
+            index = match and match.group(1)
+            if index: yield int(index), type
+            break
+
+
+
+class InEvent(object):
+  """Encapsulates the entire InEvent subsystem.
+
+  This is generally all you need to import.
+
+  On instantiation, we open all devices that are keyboards, mice or joysticks.
+  That means we might have two of one sort of another, and that might be a
+  problem, but it would be rather rare.
+
+  There are several ABS (joystick, touch) events that we do not handle,
+  specifically THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
+  DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
+  for the interested reader. Similarly, we make no attempt to handle
+  multi-touch.
+
+  Handlers can be supplied, in which case they are called for each event, but
+  it isn't necessary; API exists for all the events.
+
+  The handler signature is:
+
+    def handler_func(event, state)
+
+  where:
+    event:
+      An Event object describing the event.
+
+    state:
+      An EventState object describing the current state.
+
+  Use key_to_code() to convert from the name of a key to its code,
+  and code_to_key() to convert a code to a name.
+
+  The keys are listed in inevent.Constants.py or /usr/include/linux/input.h
+  Note that the key names refer to a US keyboard.
+  """
+  def __init__(self, types = ["kbd", "mouse", "js"]):
+    self.streams = []
+    self.handler = EventHandler()
+    self.types = types
+
+    devs = list(find_devices(types))
+    for index, type in devs: self.add_stream(index, type)
+
+    self.udevCtx = pyudev.Context()
+    self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
+    self.udevMon.filter_by(subsystem = 'input')
+    self.udevMon.start()
+
+
+  def process_udev_event(self):
+    action, device = self.udevMon.receive_device()
+
+    match = re.search(r"/dev/input/event([0-9]+)", str(device.device_node))
+    devIndex = match and match.group(1)
+    if not devIndex: return
+    devIndex = int(devIndex)
+
+    if action == 'add':
+      for index, devType in find_devices(self.types):
+        if index == devIndex:
+          self.add_stream(devIndex, devType)
+          break
+
+    if action == 'remove':
+      self.remove_stream(devIndex)
+
+
+  def process_events(self, cb = None):
+    """
+    Handle all events that have been triggered since the last call.
+    """
+
+    # Gather list of file descriptors to watch
+    selectlist = [x.filehandle for x in self.streams]
+    udevFD = self.udevMon.fileno()
+    selectlist.append(udevFD)
+
+    processedEvent = True
+    while processedEvent:
+      processedEvent = False
+
+      # Select
+      ready = select.select(selectlist, [], [], 0)[0]
+
+      # Handle events
+      for fd in ready:
+        if fd == udevFD:
+          self.process_udev_event()
+          processedEvent = True
+
+        for stream in self.streams:
+          if stream.filehandle == fd:
+            event = stream.read()
+            if event:
+              self.handler.event(event, cb)
+              processedEvent = True
+
+
+  def add_stream(self, devIndex, devType):
+    try:
+      self.streams.append(EventStream(devIndex, devType))
+      print('Added {}[{:d}]'.format(devType, devIndex))
+
+    except OSError as e:
+      if not e.errno in [errno.EPERM, errno.EACCES]: raise e
+
+
+  def remove_stream(self, devIndex):
+    for stream in self.streams:
+      if stream.devIndex == devIndex:
+        self.streams.remove(stream)
+        print('Removed {}[{:d}]'.format(stream.devType, devIndex))
+        break
+
+
+  def key_state(self, key):
+    """
+    Returns the state of the given key.
+
+    The returned value will be 0 for key-up, or 1 for key-down. This method
+    returns a key-held(2) as 1 to aid in using the returned value as a
+    movement distance.
+
+    This function accepts either the key code or the string name of the key.
+    It would be more efficient to look-up and store the code of
+    the key with KEY_CODE[], rather than using the string every time. (Which
+    involves a dict look-up keyed with a string for every key_state call, every
+    time around the loop.)
+
+    Gamepad keys are:
+      Select = BTN_BASE3, Start = BTN_BASE4
+      L1 = BTN_TOP       R1 = BTN_BASE
+      L2 = BTN_PINKIE    R2 = BTN_BASE2
+
+    The action buttons are:
+      BTN_THUMB
+      BTN_TRIGGER
+      BTN_TOP
+      BTN_THUMB2
+
+    Analogue Left Button = BTN_BASE5
+    Analogue Right Button = BTN_BASE6
+
+    Some of those may clash with extended mouse buttons, so if you are using
+    both at once, you'll see some overlap.
+
+    The direction pad is hat0 (see get_hat)
+    """
+    return self.handler.key_state(key_to_code(key))
+
+
+  def clear_key(self, key):
+    """
+    Clears the state of the given key.
+
+    Emulates a key-up, but does not call any handlers.
+    """
+    return self.handler.clear_key(key_to_code(key))
+
+
+  def get_keys(self):
+    return [code_to_key(k) for k in self.handler.get_keys()]
+
+
+  def release(self):
+    """
+    Ungrabs all streams and closes all files.
+
+    Only do this when you're finished with this object. You can't use it again.
+    """
+    for s in self.streams: s.release()
+
diff --git a/inevent/JogHandler.py b/inevent/JogHandler.py
new file mode 100644 (file)
index 0000000..5ab3fb6
--- /dev/null
@@ -0,0 +1,81 @@
+from inevent.Constants import *
+
+
+def axes_to_string(axes):
+    return "({:6.3f}, {:6.3f}, {:6.3f}, {:6.3f})".format(*axes)
+
+
+def print_event(event, state):
+    print("{} {}: ".format(event.get_source(), event.get_type_name()), end = '')
+
+    if event.type == EV_ABS:
+        print(axes_to_string(state.get_joystick3d()) + " " +
+              axes_to_string(state.get_joystickR3d()) + " " +
+              "({:2.0f}, {:2.0f}) ".format(*state.get_hat()))
+
+    if event.type == EV_REL:
+        print("({:d}, {:d}) ".format(*state.get_mouse()) +
+              "({:d}, {:d})".format(*state.get_wheel()))
+
+    if event.type == EV_KEY:
+        state = "pressed" if event.value else "released"
+        print("0x{:x} {}".format(event.code, state))
+
+
+class JogHandler:
+    def __init__(self, config):
+        self.config = config
+        self.axes = [0.0, 0.0, 0.0, 0.0]
+        self.speed = 1
+        self.activate = 0
+
+
+    def changed(self):
+        print(axes_to_string(self.axes) + " x {:d}".format(self.speed))
+
+
+    def __call__(self, event, state):
+        if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
+
+        changed = False
+
+        # Process event
+        if event.type == EV_ABS and event.code in self.config['axes']:
+            pass
+
+        elif event.type == EV_ABS and event.code in self.config['arrows']:
+            axis = self.config['arrows'].index(event.code)
+
+            if event.value < 0:
+                if axis == 1: print('up')
+                else: print('left')
+
+            elif 0 < event.value:
+                if axis == 1: print('down')
+                else: print('right')
+
+        elif event.type == EV_KEY and event.code in self.config['speed']:
+            old_speed = self.speed
+            self.speed = self.config['speed'].index(event.code) + 1
+            if self.speed != old_speed: changed = True
+
+        elif event.type == EV_KEY and event.code in self.config['activate']:
+            index = self.config['activate'].index(event.code)
+
+            if event.value: self.activate |= 1 << index
+            else: self.activate &= ~(1 << index)
+
+        if self.config.get('verbose', False): print_event(event, state)
+
+        # Update axes
+        old_axes = list(self.axes)
+
+        for axis in range(4):
+            self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
+            if abs(self.axes[axis]) < self.config['deadband']:
+                self.axes[axis] = 0
+            if not (1 << axis) & self.activate: self.axes[axis] = 0
+
+        if old_axes != self.axes: changed = True
+
+        if changed: self.changed()
diff --git a/inevent/Keys.py b/inevent/Keys.py
new file mode 100644 (file)
index 0000000..52b8543
--- /dev/null
@@ -0,0 +1,418 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+
+KEY_ESC = 1
+KEY_1 = 2
+KEY_2 = 3
+KEY_3 = 4
+KEY_4 = 5
+KEY_5 = 6
+KEY_6 = 7
+KEY_7 = 8
+KEY_8 = 9
+KEY_9 = 10
+KEY_0 = 11
+KEY_MINUS = 12
+KEY_EQUAL = 13
+KEY_BACKSPACE = 14
+KEY_TAB = 15
+KEY_Q = 16
+KEY_W = 17
+KEY_E = 18
+KEY_R = 19
+KEY_T = 20
+KEY_Y = 21
+KEY_U = 22
+KEY_I = 23
+KEY_O = 24
+KEY_P = 25
+KEY_LEFTBRACE = 26
+KEY_RIGHTBRACE = 27
+KEY_ENTER = 28
+KEY_LEFTCTRL = 29
+KEY_A = 30
+KEY_S = 31
+KEY_D = 32
+KEY_F = 33
+KEY_G = 34
+KEY_H = 35
+KEY_J = 36
+KEY_K = 37
+KEY_L = 38
+KEY_SEMICOLON = 39
+KEY_APOSTROPHE = 40
+KEY_GRAVE = 41
+KEY_LEFTSHIFT = 42
+KEY_BACKSLASH = 43
+KEY_Z = 44
+KEY_X = 45
+KEY_C = 46
+KEY_V = 47
+KEY_B = 48
+KEY_N = 49
+KEY_M = 50
+KEY_COMMA = 51
+KEY_DOT = 52
+KEY_SLASH = 53
+KEY_RIGHTSHIFT = 54
+KEY_KPASTERISK = 55
+KEY_LEFTALT = 56
+KEY_SPACE = 57
+KEY_CAPSLOCK = 58
+KEY_F1 = 59
+KEY_F2 = 60
+KEY_F3 = 61
+KEY_F4 = 62
+KEY_F5 = 63
+KEY_F6 = 64
+KEY_F7 = 65
+KEY_F8 = 66
+KEY_F9 = 67
+KEY_F10 = 68
+KEY_NUMLOCK = 69
+KEY_SCROLLLOCK = 70
+KEY_KP7 = 71
+KEY_KP8 = 72
+KEY_KP9 = 73
+KEY_KPMINUS = 74
+KEY_KP4 = 75
+KEY_KP5 = 76
+KEY_KP6 = 77
+KEY_KPPLUS = 78
+KEY_KP1 = 79
+KEY_KP2 = 80
+KEY_KP3 = 81
+KEY_KP0 = 82
+KEY_KPDOT = 83
+
+KEY_ZENKAKUHANKAKU = 85
+KEY_102ND = 86
+KEY_F11 = 87
+KEY_F12 = 88
+KEY_RO = 89
+KEY_KATAKANA = 90
+KEY_HIRAGANA = 91
+KEY_HENKAN = 92
+KEY_KATAKANAHIRAGANA = 93
+KEY_MUHENKAN = 94
+KEY_KPJPCOMMA = 95
+KEY_KPENTER = 96
+KEY_RIGHTCTRL = 97
+KEY_KPSLASH = 98
+KEY_SYSRQ = 99
+KEY_RIGHTALT = 100
+KEY_LINEFEED = 101
+KEY_HOME = 102
+KEY_UP = 103
+KEY_PAGEUP = 104
+KEY_LEFT = 105
+KEY_RIGHT = 106
+KEY_END = 107
+KEY_DOWN = 108
+KEY_PAGEDOWN = 109
+KEY_INSERT = 110
+KEY_DELETE = 111
+KEY_MACRO = 112
+KEY_MUTE = 113
+KEY_VOLUMEDOWN = 114
+KEY_VOLUMEUP = 115
+KEY_POWER = 116
+KEY_KPEQUAL = 117
+KEY_KPPLUSMINUS = 118
+KEY_PAUSE = 119
+
+KEY_KPCOMMA = 121
+KEY_HANGUEL = 122
+KEY_HANJA = 123
+KEY_YEN = 124
+KEY_LEFTMETA = 125
+KEY_RIGHTMETA = 126
+KEY_COMPOSE = 127
+
+KEY_STOP = 128
+KEY_AGAIN = 129
+KEY_PROPS = 130
+KEY_UNDO = 131
+KEY_FRONT = 132
+KEY_COPY = 133
+KEY_OPEN = 134
+KEY_PASTE = 135
+KEY_FIND = 136
+KEY_CUT = 137
+KEY_HELP = 138
+KEY_MENU = 139
+KEY_CALC = 140
+KEY_SETUP = 141
+KEY_SLEEP = 142
+KEY_WAKEUP = 143
+KEY_FILE = 144
+KEY_SENDFILE = 145
+KEY_DELETEFILE = 146
+KEY_XFER = 147
+KEY_PROG1 = 148
+KEY_PROG2 = 149
+KEY_WWW = 150
+KEY_MSDOS = 151
+KEY_COFFEE = 152
+KEY_DIRECTION = 153
+KEY_CYCLEWINDOWS = 154
+KEY_MAIL = 155
+KEY_BOOKMARKS = 156
+KEY_COMPUTER = 157
+KEY_BACK = 158
+KEY_FORWARD = 159
+KEY_CLOSECD = 160
+KEY_EJECTCD = 161
+KEY_EJECTCLOSECD = 162
+KEY_NEXTSONG = 163
+KEY_PLAYPAUSE = 164
+KEY_PREVIOUSSONG = 165
+KEY_STOPCD = 166
+KEY_RECORD = 167
+KEY_REWIND = 168
+KEY_PHONE = 169
+KEY_ISO = 170
+KEY_CONFIG = 171
+KEY_HOMEPAGE = 172
+KEY_REFRESH = 173
+KEY_EXIT = 174
+KEY_MOVE = 175
+KEY_EDIT = 176
+KEY_SCROLLUP = 177
+KEY_SCROLLDOWN = 178
+KEY_KPLEFTPAREN = 179
+KEY_KPRIGHTPAREN = 180
+
+KEY_F13 = 183
+KEY_F14 = 184
+KEY_F15 = 185
+KEY_F16 = 186
+KEY_F17 = 187
+KEY_F18 = 188
+KEY_F19 = 189
+KEY_F20 = 190
+KEY_F21 = 191
+KEY_F22 = 192
+KEY_F23 = 193
+KEY_F24 = 194
+
+KEY_PLAYCD = 200
+KEY_PAUSECD = 201
+KEY_PROG3 = 202
+KEY_PROG4 = 203
+KEY_SUSPEND = 205
+KEY_CLOSE = 206
+KEY_PLAY = 207
+KEY_FASTFORWARD = 208
+KEY_BASSBOOST = 209
+KEY_PRINT = 210
+KEY_HP = 211
+KEY_CAMERA = 212
+KEY_SOUND = 213
+KEY_QUESTION = 214
+KEY_EMAIL = 215
+KEY_CHAT = 216
+KEY_SEARCH = 217
+KEY_CONNECT = 218
+KEY_FINANCE = 219
+KEY_SPORT = 220
+KEY_SHOP = 221
+KEY_ALTERASE = 222
+KEY_CANCEL = 223
+KEY_BRIGHTNESSDOWN = 224
+KEY_BRIGHTNESSUP = 225
+KEY_MEDIA = 226
+
+KEY_UNKNOWN = 240
+
+BTN_MISC = 0x100
+BTN_0 = 0x100
+BTN_1 = 0x101
+BTN_2 = 0x102
+BTN_3 = 0x103
+BTN_4 = 0x104
+BTN_5 = 0x105
+BTN_6 = 0x106
+BTN_7 = 0x107
+BTN_8 = 0x108
+BTN_9 = 0x109
+
+BTN_MOUSE = 0x110
+BTN_LEFT = 0x110
+BTN_RIGHT = 0x111
+BTN_MIDDLE = 0x112
+BTN_SIDE = 0x113
+BTN_EXTRA = 0x114
+BTN_FORWARD = 0x115
+BTN_BACK = 0x116
+BTN_TASK = 0x117
+
+BTN_JOYSTICK = 0x120
+BTN_TRIGGER = 0x120
+BTN_THUMB = 0x121
+BTN_THUMB2 = 0x122
+BTN_TOP = 0x123
+BTN_TOP2 = 0x124
+BTN_PINKIE = 0x125
+BTN_BASE = 0x126
+BTN_BASE2 = 0x127
+BTN_BASE3 = 0x128
+BTN_BASE4 = 0x129
+BTN_BASE5 = 0x12a
+BTN_BASE6 = 0x12b
+BTN_DEAD = 0x12f
+
+BTN_GAMEPAD = 0x130
+BTN_A = 0x130
+BTN_B = 0x131
+BTN_C = 0x132
+BTN_X = 0x133
+BTN_Y = 0x134
+BTN_Z = 0x135
+BTN_TL = 0x136
+BTN_TR = 0x137
+BTN_TL2 = 0x138
+BTN_TR2 = 0x139
+BTN_SELECT = 0x13a
+BTN_START = 0x13b
+BTN_MODE = 0x13c
+BTN_THUMBL = 0x13d
+BTN_THUMBR = 0x13e
+
+BTN_DIGI = 0x140
+BTN_TOOL_PEN = 0x140
+BTN_TOOL_RUBBER = 0x141
+BTN_TOOL_BRUSH = 0x142
+BTN_TOOL_PENCIL = 0x143
+BTN_TOOL_AIRBRUSH = 0x144
+BTN_TOOL_FINGER = 0x145
+BTN_TOOL_MOUSE = 0x146
+BTN_TOOL_LENS = 0x147
+BTN_TOUCH = 0x14a
+BTN_STYLUS = 0x14b
+BTN_STYLUS2 = 0x14c
+BTN_TOOL_DOUBLETAP = 0x14d
+BTN_TOOL_TRIPLETAP = 0x14e
+
+BTN_WHEEL = 0x150
+BTN_GEAR_DOWN = 0x150
+BTN_GEAR_UP = 0x151
+
+KEY_OK = 0x160
+KEY_SELECT = 0x161
+KEY_GOTO = 0x162
+KEY_CLEAR = 0x163
+KEY_POWER2 = 0x164
+KEY_OPTION = 0x165
+KEY_INFO = 0x166
+KEY_TIME = 0x167
+KEY_VENDOR = 0x168
+KEY_ARCHIVE = 0x169
+KEY_PROGRAM = 0x16a
+KEY_CHANNEL = 0x16b
+KEY_FAVORITES = 0x16c
+KEY_EPG = 0x16d
+KEY_PVR = 0x16e
+KEY_MHP = 0x16f
+KEY_LANGUAGE = 0x170
+KEY_TITLE = 0x171
+KEY_SUBTITLE = 0x172
+KEY_ANGLE = 0x173
+KEY_ZOOM = 0x174
+KEY_MODE = 0x175
+KEY_KEYBOARD = 0x176
+KEY_SCREEN = 0x177
+KEY_PC = 0x178
+KEY_TV = 0x179
+KEY_TV2 = 0x17a
+KEY_VCR = 0x17b
+KEY_VCR2 = 0x17c
+KEY_SAT = 0x17d
+KEY_SAT2 = 0x17e
+KEY_CD = 0x17f
+KEY_TAPE = 0x180
+KEY_RADIO = 0x181
+KEY_TUNER = 0x182
+KEY_PLAYER = 0x183
+KEY_TEXT = 0x184
+KEY_DVD = 0x185
+KEY_AUX = 0x186
+KEY_MP3 = 0x187
+KEY_AUDIO = 0x188
+KEY_VIDEO = 0x189
+KEY_DIRECTORY = 0x18a
+KEY_LIST = 0x18b
+KEY_MEMO = 0x18c
+KEY_CALENDAR = 0x18d
+KEY_RED = 0x18e
+KEY_GREEN = 0x18f
+KEY_YELLOW = 0x190
+KEY_BLUE = 0x191
+KEY_CHANNELUP = 0x192
+KEY_CHANNELDOWN = 0x193
+KEY_FIRST = 0x194
+KEY_LAST = 0x195
+KEY_AB = 0x196
+KEY_NEXT = 0x197
+KEY_RESTART = 0x198
+KEY_SLOW = 0x199
+KEY_SHUFFLE = 0x19a
+KEY_BREAK = 0x19b
+KEY_PREVIOUS = 0x19c
+KEY_DIGITS = 0x19d
+KEY_TEEN = 0x19e
+KEY_TWEN = 0x19f
+
+KEY_DEL_EOL = 0x1c0
+KEY_DEL_EOS = 0x1c1
+KEY_INS_LINE = 0x1c2
+KEY_DEL_LINE = 0x1c3
+
+KEY_FN = 0x1d0
+KEY_FN_ESC = 0x1d1
+KEY_FN_F1 = 0x1d2
+KEY_FN_F2 = 0x1d3
+KEY_FN_F3 = 0x1d4
+KEY_FN_F4 = 0x1d5
+KEY_FN_F5 = 0x1d6
+KEY_FN_F6 = 0x1d7
+KEY_FN_F7 = 0x1d8
+KEY_FN_F8 = 0x1d9
+KEY_FN_F9 = 0x1da
+KEY_FN_F10 = 0x1db
+KEY_FN_F11 = 0x1dc
+KEY_FN_F12 = 0x1dd
+KEY_FN_1 = 0x1de
+KEY_FN_2 = 0x1df
+KEY_FN_D = 0x1e0
+KEY_FN_E = 0x1e1
+KEY_FN_F = 0x1e2
+KEY_FN_S = 0x1e3
+KEY_FN_B = 0x1e4
+
+KEY_MAX = 0x1ff
diff --git a/inevent/__init__.py b/inevent/__init__.py
new file mode 100644 (file)
index 0000000..6ab1928
--- /dev/null
@@ -0,0 +1,30 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+
+from .InEvent import InEvent
+from .JogHandler import JogHandler
diff --git a/inevent/ioctl.py b/inevent/ioctl.py
new file mode 100644 (file)
index 0000000..6e91b40
--- /dev/null
@@ -0,0 +1,101 @@
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# 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.
+#
+# IOCTL macros
+#
+# ioctl command encoding: 32 bits total, command in lower 16 bits,
+# size of the parameter structure in the lower 14 bits of the
+# upper 16 bits.
+#
+# Encoding the size of the parameter structure in the ioctl request
+# is useful for catching programs compiled with old versions
+# and to avoid overwriting user space outside the user buffer area.
+# The highest 2 bits are reserved for indicating the ``access mode''.
+# NOTE: This limits the max parameter size to 16kB - 1
+#
+# The following is for compatibility across the various Linux
+# platforms.  The generic ioctl numbering scheme doesn't really enforce
+# a type field.  De facto, however, the top 8 bits of the lower 16
+# bits are indeed used as a type field, so we might just as well make
+# this explicit here.
+
+import struct
+
+
+sizeof = struct.calcsize
+
+_IOC_NRBITS    = 8
+_IOC_TYPEBITS  = 8
+_IOC_SIZEBITS  = 14
+_IOC_DIRBITS   = 2
+
+_IOC_NRMASK    = (1 << _IOC_NRBITS)   - 1
+_IOC_TYPEMASK  = (1 << _IOC_TYPEBITS) - 1
+_IOC_SIZEMASK  = (1 << _IOC_SIZEBITS) - 1
+_IOC_DIRMASK   = (1 << _IOC_DIRBITS)  - 1
+
+_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
+_IOC_RW        = _IOC_READ | _IOC_WRITE
+
+
+def _IOC(dir, type, nr, size):
+  return int(
+    (dir  << _IOC_DIRSHIFT)  |
+    (type << _IOC_TYPESHIFT) |
+    (nr   << _IOC_NRSHIFT)   |
+    (size << _IOC_SIZESHIFT))
+
+# encode ioctl numbers
+def _IO(type, nr):            return _IOC(_IOC_NONE,  type, nr, 0)
+def _IOR(type, nr, fmt):      return _IOC(_IOC_READ,  type, nr, sizeof(fmt))
+def _IOW(type, nr, fmt):      return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
+def _IOWR(type, nr, fmt):     return _IOC(_IOC_RW,    type, nr, sizeof(fmt))
+def _IOR_BAD(type, nr, fmt):  return _IOC(_IOC_READ,  type, nr, sizeof(fmt))
+def _IOW_BAD(type, nr, fmt):  return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
+def _IOWR_BAD(type, nr, fmt): return _IOC(_IOC_RW,    type, nr, sizeof(fmt))
+
+# decode ioctl numbers
+def _IOC_DIR(nr):  return (nr >> _IOC_DIRSHIFT)  & _IOC_DIRMASK
+def _IOC_TYPE(nr): return (nr >> _IOC_TYPESHIFT) & _IOC_TYPEMASK
+def _IOC_NR(nr):   return (nr >> _IOC_NRSHIFT)   & _IOC_NRMASK
+def _IOC_SIZE(nr): return (nr >> _IOC_SIZESHIFT) & _IOC_SIZEMASK
+
+# for drivers/sound files
+IOC_IN        = _IOC_WRITE    << _IOC_DIRSHIFT
+IOC_OUT       = _IOC_READ     << _IOC_DIRSHIFT
+IOC_INOUT     = _IOC_RW       << _IOC_DIRSHIFT
+IOCSIZE_MASK  = _IOC_SIZEMASK << _IOC_SIZESHIFT
+IOCSIZE_SHIFT = _IOC_SIZESHIFT
+
diff --git a/jogtest.py b/jogtest.py
new file mode 100755 (executable)
index 0000000..c58daa3
--- /dev/null
@@ -0,0 +1,22 @@
+#!/usr/bin/env python3
+
+from inevent import InEvent, JogHandler
+from inevent.Constants import *
+
+
+if __name__ == "__main__":
+  # Load config
+  config = {
+    "deadband": 0.1,
+    "axes": [ABS_X, ABS_Y, ABS_RZ],
+    "arrows": [ABS_HAT0X, ABS_HAT0Y],
+    "speed": [0x120, 0x121, 0x122, 0x123],
+    "activate": [0x124, 0x126, 0x125]
+    }
+
+  # Listen for input events
+  events = InEvent(types = "js kbd".split())
+  handler = JogHandler(config)
+
+  while not events.key_state("KEY_ESC"):
+    events.process_events(handler)
index b916da589b542a20677b061a0d462c293d126d2a..5bbf703f9f9816671f47c703db254eec39a23116 100755 (executable)
@@ -62,8 +62,8 @@ EOF
 fi
 
 # Install pacakges
-apt-get install -y avahi-daemon avrdude minicom python-pip
-pip install tornado sockjs-tornado pyserial
+apt-get install -y avahi-daemon avrdude minicom python3-pip
+pip-3.2 install tornado sockjs-tornado pyserial
 
 # Clean
 apt-get autoclean
@@ -79,6 +79,7 @@ echo "bbmc ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
 #sed -i 's/^\(.*ttyAMA0.*\)$/# \1/' /etc/inittab
 sed -i 's/console=ttyAMA0,115200 //' /boot/cmdline.txt
 
+# TODO setup input and serial device permissions in udev
 # TODO install bbctrl w/ init.d script
 
 reboot