.sconf_temp/
.sconsign.dblite
+/http
+node_modules
*~
\#*
+*.pyc
+__pycache__
+++ /dev/null
- 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
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
-#!/usr/bin/env python
+#!/usr/bin/env python3
## Change this to match your local settings
SERIAL_PORT = '/dev/ttyAMA0'
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 = []
def readSerial(self):
- return self.sp.readline().replace("\n", "")
+ return self.sp.readline().replace(b"\n", b"")
def run(self):
data = self.readSerial()
# send it back to tornado
self.output_queue.put(data)
- print data
+ print(data.decode('utf-8'))
clients.append(self)
self.send(state)
+
def on_close(self):
clients.remove(self)
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)
# 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)
--- /dev/null
+# 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
+
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
+
--- /dev/null
+# 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
--- /dev/null
+# 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()
--- /dev/null
+# 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()
--- /dev/null
+# 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)
--- /dev/null
+# 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()
+
--- /dev/null
+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()
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
+
--- /dev/null
+#!/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)
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
#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