From 72cd0bfa86b933de1e3367718b3658afe18acccb Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Fri, 11 Mar 2016 16:02:00 -0800 Subject: [PATCH] jog updates --- .gitignore | 4 + CODE_TAG | 27 --- Makefile | 8 +- bbctrl.py | 52 ++++- inevent/AbsAxisScaling.py | 80 ++++++++ inevent/Constants.py | 97 +++++++++ inevent/Event.py | 115 +++++++++++ inevent/EventHandler.py | 120 +++++++++++ inevent/EventState.py | 118 +++++++++++ inevent/EventStream.py | 184 +++++++++++++++++ inevent/FindDevices.py | 215 ++++++++++++++++++++ inevent/Format.py | 32 +++ inevent/InEvent.py | 252 +++++++++++++++++++++++ inevent/JogHandler.py | 81 ++++++++ inevent/Keys.py | 418 ++++++++++++++++++++++++++++++++++++++ inevent/__init__.py | 30 +++ inevent/ioctl.py | 101 +++++++++ jogtest.py | 22 ++ setup_rpi.sh | 5 +- 19 files changed, 1927 insertions(+), 34 deletions(-) delete mode 100644 CODE_TAG create mode 100644 inevent/AbsAxisScaling.py create mode 100644 inevent/Constants.py create mode 100644 inevent/Event.py create mode 100644 inevent/EventHandler.py create mode 100644 inevent/EventState.py create mode 100644 inevent/EventStream.py create mode 100644 inevent/FindDevices.py create mode 100644 inevent/Format.py create mode 100644 inevent/InEvent.py create mode 100644 inevent/JogHandler.py create mode 100644 inevent/Keys.py create mode 100644 inevent/__init__.py create mode 100644 inevent/ioctl.py create mode 100755 jogtest.py diff --git a/.gitignore b/.gitignore index 99a7751..6f2b850 100644 --- a/.gitignore +++ b/.gitignore @@ -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 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 - . - - 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 diff --git a/Makefile b/Makefile index ebbe0d9..ff09db9 100644 --- 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 diff --git a/bbctrl.py b/bbctrl.py index c6f8a48..623f077 100755 --- 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 index 0000000..bd4baa1 --- /dev/null +++ b/inevent/AbsAxisScaling.py @@ -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 index 0000000..00be4cb --- /dev/null +++ b/inevent/Constants.py @@ -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 index 0000000..c14e9d4 --- /dev/null +++ b/inevent/Event.py @@ -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 index 0000000..7a93a35 --- /dev/null +++ b/inevent/EventHandler.py @@ -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 index 0000000..a87ae7b --- /dev/null +++ b/inevent/EventState.py @@ -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 index 0000000..ab4b7e1 --- /dev/null +++ b/inevent/EventStream.py @@ -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 index 0000000..17a667a --- /dev/null +++ b/inevent/FindDevices.py @@ -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 index 0000000..d91665e --- /dev/null +++ b/inevent/Format.py @@ -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 index 0000000..fab068a --- /dev/null +++ b/inevent/InEvent.py @@ -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 index 0000000..5ab3fb6 --- /dev/null +++ b/inevent/JogHandler.py @@ -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 index 0000000..52b8543 --- /dev/null +++ b/inevent/Keys.py @@ -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 index 0000000..6ab1928 --- /dev/null +++ b/inevent/__init__.py @@ -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 index 0000000..6e91b40 --- /dev/null +++ b/inevent/ioctl.py @@ -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 index 0000000..c58daa3 --- /dev/null +++ b/jogtest.py @@ -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) diff --git a/setup_rpi.sh b/setup_rpi.sh index b916da5..5bbf703 100755 --- a/setup_rpi.sh +++ b/setup_rpi.sh @@ -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 -- 2.27.0