Complete rebuild of gamepad support, from scratch.

This commit is contained in:
David Carley
2022-08-31 05:24:32 +00:00
parent 53a0f75188
commit 3a68076022
18 changed files with 483 additions and 2238 deletions

View File

@@ -9,7 +9,7 @@ RUN apt update \
&& apt install -y \ && apt install -y \
build-essential git wget binfmt-support qemu gcc-9 \ build-essential git wget binfmt-support qemu gcc-9 \
parted gcc-avr avr-libc avrdude python3 python3-pip python3-tornado \ parted gcc-avr avr-libc avrdude python3 python3-pip python3-tornado \
curl unzip python3-setuptools gcc-arm-linux-gnueabihf bc vim sudo \ curl unzip python3-setuptools gcc-arm-linux-gnueabihf bc vim locate sudo \
&& update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9 \ && update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 9 \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt install -y nodejs && apt install -y nodejs

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b10", "version": "1.0.10b11",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b10", "version": "1.0.10b11",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0+", "license": "GPL-3.0+",
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "bbctrl", "name": "bbctrl",
"version": "1.0.10b10", "version": "1.0.10b11",
"homepage": "https://onefinitycnc.com/", "homepage": "https://onefinitycnc.com/",
"repository": "https://github.com/OneFinityCNC/onefinity", "repository": "https://github.com/OneFinityCNC/onefinity",
"license": "GPL-3.0+", "license": "GPL-3.0+",
@@ -16,4 +16,4 @@
"lodash.merge": "4.6.2", "lodash.merge": "4.6.2",
"pug-cli": "^1.0.0-alpha6" "pug-cli": "^1.0.0-alpha6"
} }
} }

View File

@@ -18,7 +18,6 @@ setup(
package_dir={'': 'src/py'}, package_dir={'': 'src/py'},
packages=[ packages=[
'bbctrl', 'bbctrl',
'inevent',
'camotics', 'camotics',
'iw_parse' 'iw_parse'
], ],

View File

@@ -1,87 +1,492 @@
################################################################################ from bbctrl.Ctrl import Ctrl
# # from bbctrl.Log import Logger
# This file is part of the Buildbotics firmware. # from evdev.ecodes import EV_ABS, EV_KEY
# # import errno
# Copyright (c) 2015 - 2018, Buildbotics LLC # import evdev
# All rights reserved. # import functools
# # import hashlib
# This file ("the software") is free software: you can redistribute it # import json
# and/or modify it under the terms of the GNU General Public License, # import os
# version 2 as published by the Free Software Foundation. You should # import pyudev
# have received a copy of the GNU General Public License, version 2 # import re
# along with the software. If not, see <http://www.gnu.org/licenses/>. # import traceback
# # import typing
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import inevent userGamepadConfigs = {}
from inevent.Constants import *
gamepadConfigs = {
"default": {
"sign-x": 1,
"sign-y": -1,
"sign-z": -1,
"deadband": 0.15
},
"5C936FF2": {
"description": "Logitech 710, X mode",
"EV_KEY:308": "speed-4",
"EV_KEY:305": "speed-3",
"EV_KEY:304": "speed-2",
"EV_KEY:307": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:4": "axis-z",
"EV_KEY:310": "lock-y",
"EV_KEY:311": "lock-x",
},
"10E159EC": {
"description": "Logitech 710, D mode",
"EV_KEY:307": "speed-4",
"EV_KEY:306": "speed-3",
"EV_KEY:305": "speed-2",
"EV_KEY:304": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:5": "axis-z",
"EV_KEY:308": "lock-y",
"EV_KEY:309": "lock-x",
},
"5443E73C": {
"description": "EasySMX ESM-9013, top lights mode",
"EV_KEY:308": "speed-4",
"EV_KEY:305": "speed-3",
"EV_KEY:304": "speed-2",
"EV_KEY:307": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:4": "axis-z",
"EV_KEY:310": "lock-y",
"EV_KEY:311": "lock-x",
},
"951CA031": {
"description": "EasySMX ESM-9013, left lights mode",
"EV_KEY:304": "speed-4",
"EV_KEY:305": "speed-3",
"EV_KEY:306": "speed-2",
"EV_KEY:307": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:5": "axis-z",
"EV_KEY:308": "lock-y",
"EV_KEY:309": "lock-x",
},
"0BF75ED2": {
"description": "EasySMX ESM-9013, bottom lights mode",
"EV_KEY:308": "speed-4",
"EV_KEY:305": "speed-3",
"EV_KEY:304": "speed-2",
"EV_KEY:307": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:5": "axis-z",
"EV_KEY:310": "lock-y",
"EV_KEY:311": "lock-x",
},
"B3907139": {
"description": "Sony Playstation 4 Dual-Shock Controller",
"EV_KEY:307": "speed-4",
"EV_KEY:306": "speed-3",
"EV_KEY:305": "speed-2",
"EV_KEY:304": "speed-1",
"EV_ABS:0": "axis-x",
"EV_ABS:16": "axis-x",
"EV_ABS:1": "axis-y",
"EV_ABS:17": "axis-y",
"EV_ABS:5": "axis-z",
"EV_KEY:308": "lock-y",
"EV_KEY:309": "lock-x",
}
}
udevPropertiesToLog = ["ID_MODEL", "ID_SERIAL", "ID_SERIAL_SHORT", "ID_VENDOR"]
udevPropertiesToIgnore = [
".INPUT_CLASS", "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "ID_BUS",
"ID_FOR_SEAT", "ID_INPUT", "ID_INPUT_JOYSTICK", "ID_MODEL_ID",
"ID_MODEL_ENC", "ID_PATH", "ID_PATH_TAG", "ID_REVISION", "ID_TYPE",
"ID_USB_DRIVER", "ID_USB_INTERFACES", "ID_USB_INTERFACE_NUM",
"ID_VENDOR_ENC", "ID_VENDOR_ID", "LIBINPUT_DEVICE_GROUP", "MAJOR", "MINOR",
"SEQNUM", "SUBSYSTEM", "TAGS", "USEC_INITIALIZED"
]
# Listen for input events def safe_int(s, base=10, val=None):
class Jog(inevent.JogHandler): try:
def __init__(self, ctrl): return int(s, base)
self.ctrl = ctrl except ValueError:
self.log = ctrl.log.get('Jog') return val
config = {
"Logitech Logitech RumblePad 2 USB": {
"deadband": 0.15,
"axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
"dir": [1, -1, -1, 1],
"arrows": [ABS_HAT0X, ABS_HAT0Y],
"speed": [0x120, 0x121, 0x122, 0x123],
"lock": [0x124, 0x125],
},
"default": { def get_udev_prop(device: pyudev.Device, propertyName: str):
"deadband": 0.15, try:
"axes": [ABS_X, ABS_Y, ABS_RY, ABS_RX], return device.properties[propertyName]
"dir": [1, -1, -1, 1], except:
"arrows": [ABS_HAT0X, ABS_HAT0Y], return None
"speed": [0x133, 0x130, 0x131, 0x134],
"lock": [0x136, 0x137],
def to_sorted_json(value):
return json.dumps(value, sort_keys=True)
AbsMinMax = typing.NamedTuple('AbsMinMax', [('min', float), ('max', float)])
def processCapabilities(capabilities):
result = {}
for (type, details) in capabilities.items():
if type == EV_KEY:
result[type] = details
if type == EV_ABS:
result[type] = {
code: AbsMinMax(float(info.min), float(info.max))
for (code, info) in details
} }
return result
class Gamepad(object):
_logRecord = set()
def __init__(self, log: Logger, _evdev: evdev.InputDevice,
_udev: pyudev.Device):
self._log = log
self._evdev = _evdev
self._udev = _udev
self._capabilities = processCapabilities(_evdev.capabilities())
self._details = {
"evdev": {
"name": _evdev.name,
"vendor": _evdev.info.vendor,
"product": _evdev.info.product,
"version": _evdev.info.version,
"capabilities": self._capabilities,
},
"udev":
{key: get_udev_prop(_udev, key)
for key in udevPropertiesToLog}
} }
super().__init__(config) json = to_sorted_json(self._details)
self.hash = hashlib.sha256(json.encode()).hexdigest()[-8:].upper()
self.v = [0.0] * 4 self.config = {
self.lastV = self.v **gamepadConfigs.get("default"),
self.callback() **userGamepadConfigs.get("default", {}),
**gamepadConfigs.get(self.hash, {}),
**userGamepadConfigs.get(self.hash, {})
}
self.processor = inevent.InEvent(ctrl.ioloop, self, types = ['js']) def read(self):
return self._evdev.read()
@property
def fd(self):
return self._evdev.fd
@property
def devicePath(self):
return self._evdev.path
def scaleAndClampValue(self, event: evdev.InputEvent):
if event.type != EV_ABS:
return event.value
info = self._capabilities[EV_ABS].get(event.code) # type: AbsMinMax
if not info:
return 0
# Clamp the value to the device's min/max range
value = float(max(info.min, min(info.max, event.value)))
# Remap the value from the device range to -1..1
value = ((value - info.min) / (info.max - info.min)) * 2.0 - 1.0
sign = -1 if value < 0 else 1
value = abs(value)
deadband = self.config.get("deadband", 0.15)
if value < deadband:
return 0
# Remap the value to use the full range, with the "deadband" range removed
# e.g. if value == deadband, the new value will be zero
delta = value - deadband
range = 1 - deadband
return (delta * sign) / range
def log(self, msg):
self._log.info("{}: {}".format(self.hash, msg))
def logOnce(self, msg):
if self.config.get("debug") or msg not in self._logRecord:
self._logRecord.add(msg)
self.log(msg)
def logDebug(self, msg):
if self.config.get("debug"):
self.log(msg)
def __str__(self) -> str:
return to_sorted_json({
"devicePath": self.devicePath,
"bustype": self._evdev.info.bustype,
"details": self._details,
"hash": self.hash
})
def callback(self): class Command(object):
if self.v != self.lastV:
self.lastV = self.v def __init__(self, id: str, value: int, gamepad: Gamepad):
self.id = id
self.value = value
self.gamepad = gamepad
def __str__(self):
return "Command(id='{}', value={}, gamepad='{}')".format(
self.id, self.value, self.gamepad.hash)
class Jog(object):
gamepads = {} # type: dict[typing.Union[int, str], Gamepad]
lock = {"x": False, "y": False}
axes = {"x": 0, "y": 0, "z": 0}
speed = 3 # a resonable default speed, not too fast, not too slow
changed = False
def __init__(self, ctrl: Ctrl):
self.ctrl = ctrl
self.ioloop = ctrl.ioloop
self.log = ctrl.log.get('Jog')
self._loadUserGamepadConfigs()
self._startMonitoring()
self._discoverGamepads()
self._updateJogging()
def _loadUserGamepadConfigs(self):
path = self.ctrl.get_path('gamepads.json')
if os.path.exists(path):
with open(path, 'r') as f:
global userGamepadConfigs
userGamepadConfigs = json.load(f)
def _startMonitoring(self):
self.udev_context = pyudev.Context()
self.monitor = pyudev.Monitor.from_netlink(self.udev_context)
self.monitor.filter_by(subsystem='input')
self.ctrl.ioloop.add_handler(self.monitor, self._udevHandler,
self.ctrl.ioloop.READ)
self.monitor.start()
def _udevHandler(self, fd, events):
for udev in iter(functools.partial(self.monitor.poll, 0), None):
isEventDevice = re.search(r"/event\d+$", udev.device_node or "")
if not isEventDevice:
continue
inputJoystick = safe_int(udev.properties["ID_INPUT_JOYSTICK"])
if inputJoystick != 1:
self.log.info("Ignoring non-gamepad device: {}".format(
to_sorted_json(
{key: udev.properties[key]
for key in udev.properties})))
continue
uniqueProperties = set(udev.properties).difference(
udevPropertiesToIgnore).difference(udevPropertiesToLog)
if len(uniqueProperties) > 0:
self.log.info("Unique properties: {}".format(
to_sorted_json({
key: udev.properties[key]
for key in uniqueProperties
})))
if udev.action == 'add':
self._listen(udev.device_node)
elif udev.action == 'remove':
self._forget(udev.device_node)
def _discoverGamepads(self):
with open("/proc/bus/input/devices", "r") as file:
for line in file:
# Matches lines from '/proc/bus/input/devices' that look like:
# H: Handlers=js1 event0
if not re.match(r"H:\s*Handlers\s*=.*\bjs\d+\b", line):
continue
match = re.search(r"\bevent\d+\b", line)
if not match:
continue
self._listen("/dev/input/{}".format(match.group()))
def _listen(self, devicePath: str):
gamepad = Gamepad(
self.log, evdev.InputDevice(devicePath),
pyudev.Devices.from_device_file(self.udev_context, devicePath))
self.log.info("Found gamepad: {}".format(str(gamepad)))
self.gamepads[gamepad.fd] = self.gamepads[devicePath] = gamepad
self.ioloop.add_handler(gamepad.fd, self._gamepadHandler,
self.ioloop.READ)
def _forget(self, devicePath: str):
gamepad = self.gamepads.get(devicePath)
if not gamepad:
return
self.log.info("Device removed: {}, {}".format(gamepad.hash,
devicePath))
self.ioloop.remove_handler(gamepad.fd)
del self.gamepads[gamepad.devicePath]
del self.gamepads[gamepad.fd]
def _gamepadHandler(self, fd, events):
gamepad = self.gamepads.get(fd)
if not gamepad:
self.log.info("_gamepad_handler: Unknown gamepad? {}".format(fd))
return
try:
for event in gamepad.read():
command = self._getCommandFromEvent(gamepad, event)
if command:
self._processCommand(command)
except BlockingIOError:
pass
except OSError as error:
if error.errno == errno.ENODEV:
self._forget(gamepad.devicePath)
else:
gamepad.log(traceback.format_exc())
except Exception as error:
gamepad.log(traceback.format_exc())
def _getCommandFromEvent(self, gamepad: Gamepad,
event: evdev.InputEvent) -> Command:
if event.type not in [EV_ABS, EV_KEY]:
return
eventSignature = "{}:{}".format(evdev.ecodes.EV[event.type],
event.code)
commandId = gamepad.config.get(eventSignature)
if not commandId:
gamepad.logOnce("Unmapped event: {}:{}".format(
gamepad.hash, eventSignature))
return
gamepad.logDebug("Got event: {}".format(str(event)))
return Command(commandId, gamepad.scaleAndClampValue(event), gamepad)
def _processCommand(self, command: Command):
processor = self.commandProcessors.get(command.id)
if not processor:
command.gamepad.log("Unrecognized command: {}".format(command.id))
return
command.gamepad.logDebug("Processing command: {}".format(str(command)))
processor(self, command)
def _processSpeedCommand(self, command: Command):
match = re.match(r"^speed-(\d)$", command.id)
speed = int(match.group(1)) if match else 0
if speed not in [1, 2, 3, 4]:
command.gamepad.log("Unrecognized speed command: {}".format(
str(command)))
self.changed = self.changed or self.speed != speed
self.speed = speed
def _processAxisCommand(self, command: Command):
match = re.match(r"^axis-(.)$", command.id)
axis = match.group(1) if match else ""
if axis not in ["x", "y", "z"]:
command.gamepad.log("Unrecognized axis command: {}".format(
str(command)))
sign = command.gamepad.config.get("sign-{}".format(axis), 1)
oldValue = self.axes[axis]
locked = self.lock.get(axis, False)
self.axes[axis] = 0 if locked else command.value * sign
self.changed = self.changed or oldValue != self.axes[axis]
command.gamepad.logDebug("_processAxisCommand: {}".format(
json.dumps({
"command.value": command.value,
"axis": axis,
"oldValue": oldValue,
"value": self.axes[axis],
"sign": sign,
"locked": locked,
"changed": self.changed
})))
def _processLockCommand(self, command: Command):
match = re.match(r"^lock-(.)$", command.id)
axis = match.group(1) if match else ""
if axis not in ["x", "y"]:
command.gamepad.log("Unrecognized lock command: {}".format(
str(command)))
self.lock[axis] = bool(command.value)
def _processDisabled(self, command: Command):
pass
def _updateJogging(self):
try:
if not self.changed:
return
self.changed = False
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
axes = {axis: value * scale for (axis, value) in self.axes.items()}
try: try:
axes = {}
for i in range(len(self.v)): axes["xyzabc"[i]] = self.v[i]
self.ctrl.mach.jog(axes) self.ctrl.mach.jog(axes)
except:
self.log.info(traceback.format_exc())
finally:
# We only update 4 times a second, to keep from overwhelming the system
# EV_ABS events can happen hundreds of times a second.
self.ctrl.ioloop.call_later(0.25, self._updateJogging)
except Exception as e: commandProcessors = {
self.log.warning('Jog: %s', e) "speed-1": _processSpeedCommand,
"speed-2": _processSpeedCommand,
self.ctrl.ioloop.call_later(0.25, self.callback) "speed-3": _processSpeedCommand,
"speed-4": _processSpeedCommand,
"axis-x": _processAxisCommand,
def changed(self): "axis-y": _processAxisCommand,
scale = 1.0 "axis-z": _processAxisCommand,
if self.speed == 1: scale = 1.0 / 128.0 "lock-x": _processLockCommand,
if self.speed == 2: scale = 1.0 / 32.0 "lock-y": _processLockCommand,
if self.speed == 3: scale = 1.0 / 4.0 "disabled": _processDisabled
} # type: dict[str, typing.Callable[[Command], None]]
self.v = [x * scale for x in self.axes]

View File

@@ -128,7 +128,7 @@ class Log(object):
def remove_listener(self, listener): self.listeners.remove(listener) def remove_listener(self, listener): self.listeners.remove(listener)
def get(self, name, level = None): def get(self, name, level = None) -> Logger:
if not name in self.loggers: if not name in self.loggers:
self.loggers[name] = Logger(self, name, self.level) self.loggers[name] = Logger(self, name, self.level)
return self.loggers[name] return self.loggers[name]

View File

@@ -1,106 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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

View File

@@ -1,124 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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

View File

@@ -1,142 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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.Constants import *
_format = 'llHHi'
size = struct.calcsize(_format)
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.
"""
tsec = int(self.time)
tfrac = int((self.time - tsec) * 1000000)
return struct.pack(_format, 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, s)
self.time = tsec + tfrac / 1000000.0

View File

@@ -1,146 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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, name):
"""
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(event, state, name)
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

View File

@@ -1,145 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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()) +
"({:0.2f}, {:0.2f}) ".format(*self.get_mouse()) +
"({:0.2f}, {:0.2f})".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

View File

@@ -1,218 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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
import logging
from inevent.Constants import *
from inevent import ioctl
from inevent.AbsAxisScaling import AbsAxisScaling
from inevent import Event
from inevent.EventState import EventState
log = logging.getLogger('inevent')
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, devName):
"""
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.devName = devName
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):
"""s
Required to make this class an iterator
"""
return self
def next(self): return self.__next__()
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, Event.size)
if s:
event = Event.Event(self)
event.decode(s)
return event
except Exception as e:
log.info('Reading event: %s' % e)
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()

View File

@@ -1,245 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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
import logging
from inevent.Constants import *
log = logging.getLogger('inevent')
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:
log.warning("Do not understand device ID: %s", firstLine)
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()

View File

@@ -1,288 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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
import functools
import logging
from inevent.EventHandler import EventHandler
from inevent import Keys
from inevent.Constants import *
from inevent.EventStream import EventStream
log = logging.getLogger('inevent')
_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, '')
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, ioloop, cb, types = 'kbd mouse js'.split()):
self.ioloop = ioloop
self.cb = cb
self.streams = []
self.handler = EventHandler()
self.types = types
self.udevCtx = pyudev.Context()
self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
self.udevMon.filter_by(subsystem = 'input')
devs = list(self.find_devices(types))
for index, type, name in devs:
self.add_stream(index, type, name)
self.udevMon.start()
ioloop.add_handler(self.udevMon.fileno(), self.udev_handler, ioloop.READ)
def get_dev(self, index):
return pyudev.Device.from_name(self.udevCtx, 'input', 'event%s' % index)
def get_dev_name(self, index):
try:
dev = self.get_dev(index)
return dev.parent.attributes.asstring('name').decode('utf-8')
except: pass
def find_devices(self, 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, self.get_dev_name(index)
break
def process_udev_event(self):
action, device = self.udevMon.receive_device()
if device is None: return
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, devName in self.find_devices(self.types):
if index == devIndex:
self.add_stream(devIndex, devType, devName)
break
if action == 'remove': self.remove_stream(devIndex)
def stream_handler(self, fd, events):
for stream in self.streams:
if stream.filehandle == fd:
while True:
event = stream.next()
if event: self.handler.event(event, self.cb, stream.devName)
else: break
def udev_handler(self, fd, events):
self.process_udev_event()
def add_stream(self, devIndex, devType, devName):
try:
stream = EventStream(devIndex, devType, devName)
self.streams.append(stream)
self.ioloop.add_handler(stream.filehandle, self.stream_handler,
self.ioloop.READ)
log.info('Added %s[%d] %s', devType, devIndex, devName)
except OSError as e:
log.warning('Failed to add %s[%d]: %s', devType, devIndex, e)
def remove_stream(self, devIndex):
for stream in self.streams:
if stream.devIndex == devIndex:
self.streams.remove(stream)
self.ioloop.remove_handler(stream.filehandle)
stream.release()
self.cb.clear()
log.info('Removed %s[%d]', stream.devType, devIndex)
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()

View File

@@ -1,115 +0,0 @@
import logging
from inevent.Constants import *
log = logging.getLogger('inevent')
log.setLevel(logging.INFO)
def axes_to_string(axes):
s = ''
for axis in axes:
if s: s += ', '
else: s = '('
s += '{:6.3f}'.format(axis)
return s + ')'
def event_to_string(event, state):
s = '{} {}: '.format(event.get_source(), event.get_type_name())
if event.type == EV_ABS:
s += 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:
s += '({:d}, {:d}) '.format(*state.get_mouse()) + \
'({:d}, {:d})'.format(*state.get_wheel())
if event.type == EV_KEY:
state = 'pressed' if event.value else 'released'
s += '0x{:x} {}'.format(event.code, state)
return s
class JogHandler:
def __init__(self, config):
self.config = config
self.reset()
def changed(self):
log.info(axes_to_string(self.axes) + ' x {:d}'.format(self.speed))
def reset(self):
self.axes = [0.0, 0.0, 0.0, 0.0]
self.speed = 3
self.vertical_lock = 0
self.horizontal_lock = 0
def clear(self):
self.reset()
self.changed()
def get_config(self, name):
if name in self.config: return self.config[name]
return self.config['default']
def event(self, event, state, dev_name):
if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
config = self.get_config(dev_name)
changed = False
# Process event
if event.type == EV_ABS and event.code in config['axes']:
old_axes = list(self.axes)
deadband = config['deadband']
axis = config['axes'].index(event.code)
self.axes[axis] = event.stream.state.abs[event.code]
self.axes[axis] *= config['dir'][axis]
value = abs(self.axes[axis])
if value >= deadband:
sign = -1 if self.axes[axis] < 0 else 1
delta = value - deadband
range = 1 - deadband
# Scale the new value to the available range (full range, minus the deadband)
self.axes[axis] = (delta * sign) / range
else:
self.axes[axis] = 0
if self.horizontal_lock and axis not in [0, 3]:
self.axes[axis] = 0
if self.vertical_lock and axis not in [1, 2]:
self.axes[axis] = 0
if old_axes[axis] != self.axes[axis]: changed = True
elif event.type == EV_KEY and event.code in config['speed']:
old_speed = self.speed
self.speed = config['speed'].index(event.code) + 1
if self.speed != old_speed: changed = True
elif event.type == EV_KEY and event.code in config['lock']:
index = config['lock'].index(event.code)
self.horizontal_lock, self.vertical_lock = False, False
if event.value:
if index == 0: self.horizontal_lock = True
if index == 1: self.vertical_lock = True
log.debug(event_to_string(event, state))
if changed: self.changed()

View File

@@ -1,445 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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

View File

@@ -1,57 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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

View File

@@ -1,128 +0,0 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <http://www.gnu.org/licenses/>. #
# #
# The software 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 #
# Lesser General Public License for more details. #
# #
# You should have received a copy of the GNU Lesser General Public #
# License along with the software. If not, see #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
# 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