Added joystick.linux backend
This commit is contained in:
parent
7ee869ce42
commit
fc1d9ad3e6
8 changed files with 225 additions and 8 deletions
|
@ -22,6 +22,9 @@ Given the high speed of development in the first phase, changes are being report
|
||||||
|
|
||||||
- Added PWM PCA9685 plugin.
|
- Added PWM PCA9685 plugin.
|
||||||
|
|
||||||
|
- Added Linux native joystick plugin, ``backend.joystick.linux``, for the cases where
|
||||||
|
``python-inputs`` doesn't work and ``jstest`` is too slow.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- `switch.switchbot` plugin renamed to `switchbot.bluetooth` plugin, while the new plugin
|
- `switch.switchbot` plugin renamed to `switchbot.bluetooth` plugin, while the new plugin
|
||||||
|
|
|
@ -33,6 +33,7 @@ Backends
|
||||||
platypush/backend/inotify.rst
|
platypush/backend/inotify.rst
|
||||||
platypush/backend/joystick.rst
|
platypush/backend/joystick.rst
|
||||||
platypush/backend/joystick.jstest.rst
|
platypush/backend/joystick.jstest.rst
|
||||||
|
platypush/backend/joystick.linux.rst
|
||||||
platypush/backend/kafka.rst
|
platypush/backend/kafka.rst
|
||||||
platypush/backend/light.hue.rst
|
platypush/backend/light.hue.rst
|
||||||
platypush/backend/linode.rst
|
platypush/backend/linode.rst
|
||||||
|
|
5
docs/source/platypush/backend/joystick.linux.rst
Normal file
5
docs/source/platypush/backend/joystick.linux.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.backend.joystick.linux``
|
||||||
|
====================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.backend.joystick.linux
|
||||||
|
:members:
|
5
docs/source/platypush/plugins/pwm.pca9685.rst
Normal file
5
docs/source/platypush/plugins/pwm.pca9685.rst
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
``platypush.plugins.pwm.pca9685``
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. automodule:: platypush.plugins.pwm.pca9685
|
||||||
|
:members:
|
|
@ -104,6 +104,7 @@ Plugins
|
||||||
platypush/plugins/ping.rst
|
platypush/plugins/ping.rst
|
||||||
platypush/plugins/printer.cups.rst
|
platypush/plugins/printer.cups.rst
|
||||||
platypush/plugins/pushbullet.rst
|
platypush/plugins/pushbullet.rst
|
||||||
|
platypush/plugins/pwm.pca9685.rst
|
||||||
platypush/plugins/qrcode.rst
|
platypush/plugins/qrcode.rst
|
||||||
platypush/plugins/redis.rst
|
platypush/plugins/redis.rst
|
||||||
platypush/plugins/rtorrent.rst
|
platypush/plugins/rtorrent.rst
|
||||||
|
|
|
@ -51,6 +51,10 @@ class JoystickJstestBackend(Backend):
|
||||||
|
|
||||||
This backend only works on Linux and it requires the ``joystick`` package to be installed.
|
This backend only works on Linux and it requires the ``joystick`` package to be installed.
|
||||||
|
|
||||||
|
**NOTE**: This backend can be quite slow, since it has to run another program (``jstest``) and parse its output.
|
||||||
|
Consider it as a last resort if your joystick works with neither :class:`platypush.backend.joystick.JoystickBackend`
|
||||||
|
nor :class:`platypush.backend.joystick.JoystickLinuxBackend`.
|
||||||
|
|
||||||
Instructions on Debian-based distros::
|
Instructions on Debian-based distros::
|
||||||
|
|
||||||
# apt-get install joystick
|
# apt-get install joystick
|
||||||
|
|
186
platypush/backend/joystick/linux.py
Normal file
186
platypush/backend/joystick/linux.py
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import array
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
from fcntl import ioctl
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
|
from platypush.backend import Backend
|
||||||
|
from platypush.message.event.joystick import JoystickConnectedEvent, JoystickDisconnectedEvent, \
|
||||||
|
JoystickButtonPressedEvent, JoystickButtonReleasedEvent, JoystickAxisEvent
|
||||||
|
|
||||||
|
|
||||||
|
class JoystickLinuxBackend(Backend):
|
||||||
|
"""
|
||||||
|
This backend intercepts events from joystick devices through the native Linux API implementation.
|
||||||
|
|
||||||
|
It is loosely based on https://gist.github.com/rdb/8864666, which itself uses the
|
||||||
|
`Linux kernel joystick API <https://www.kernel.org/doc/Documentation/input/joystick-api.txt>`_ to interact with
|
||||||
|
the devices.
|
||||||
|
|
||||||
|
Triggers:
|
||||||
|
|
||||||
|
* :class:`platypush.message.event.joystick.JoystickConnectedEvent` when the joystick is connected.
|
||||||
|
* :class:`platypush.message.event.joystick.JoystickDisconnectedEvent` when the joystick is disconnected.
|
||||||
|
* :class:`platypush.message.event.joystick.JoystickButtonPressedEvent` when a joystick button is pressed.
|
||||||
|
* :class:`platypush.message.event.joystick.JoystickButtonReleasedEvent` when a joystick button is released.
|
||||||
|
* :class:`platypush.message.event.joystick.JoystickAxisEvent` when an axis value of the joystick changes.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# These constants were borrowed from linux/input.h
|
||||||
|
axis_names = {
|
||||||
|
0x00: 'x',
|
||||||
|
0x01: 'y',
|
||||||
|
0x02: 'z',
|
||||||
|
0x03: 'rx',
|
||||||
|
0x04: 'ry',
|
||||||
|
0x05: 'rz',
|
||||||
|
0x06: 'throttle',
|
||||||
|
0x07: 'rudder',
|
||||||
|
0x08: 'wheel',
|
||||||
|
0x09: 'gas',
|
||||||
|
0x0a: 'brake',
|
||||||
|
0x10: 'hat0x',
|
||||||
|
0x11: 'hat0y',
|
||||||
|
0x12: 'hat1x',
|
||||||
|
0x13: 'hat1y',
|
||||||
|
0x14: 'hat2x',
|
||||||
|
0x15: 'hat2y',
|
||||||
|
0x16: 'hat3x',
|
||||||
|
0x17: 'hat3y',
|
||||||
|
0x18: 'pressure',
|
||||||
|
0x19: 'distance',
|
||||||
|
0x1a: 'tilt_x',
|
||||||
|
0x1b: 'tilt_y',
|
||||||
|
0x1c: 'tool_width',
|
||||||
|
0x20: 'volume',
|
||||||
|
0x28: 'misc',
|
||||||
|
}
|
||||||
|
|
||||||
|
button_names = {
|
||||||
|
0x120: 'trigger',
|
||||||
|
0x121: 'thumb',
|
||||||
|
0x122: 'thumb2',
|
||||||
|
0x123: 'top',
|
||||||
|
0x124: 'top2',
|
||||||
|
0x125: 'pinkie',
|
||||||
|
0x126: 'base',
|
||||||
|
0x127: 'base2',
|
||||||
|
0x128: 'base3',
|
||||||
|
0x129: 'base4',
|
||||||
|
0x12a: 'base5',
|
||||||
|
0x12b: 'base6',
|
||||||
|
0x12f: 'dead',
|
||||||
|
0x130: 'a',
|
||||||
|
0x131: 'b',
|
||||||
|
0x132: 'c',
|
||||||
|
0x133: 'x',
|
||||||
|
0x134: 'y',
|
||||||
|
0x135: 'z',
|
||||||
|
0x136: 'tl',
|
||||||
|
0x137: 'tr',
|
||||||
|
0x138: 'tl2',
|
||||||
|
0x139: 'tr2',
|
||||||
|
0x13a: 'select',
|
||||||
|
0x13b: 'start',
|
||||||
|
0x13c: 'mode',
|
||||||
|
0x13d: 'thumbl',
|
||||||
|
0x13e: 'thumbr',
|
||||||
|
0x220: 'dpad_up',
|
||||||
|
0x221: 'dpad_down',
|
||||||
|
0x222: 'dpad_left',
|
||||||
|
0x223: 'dpad_right',
|
||||||
|
# XBox 360 controller uses these codes.
|
||||||
|
0x2c0: 'dpad_left',
|
||||||
|
0x2c1: 'dpad_right',
|
||||||
|
0x2c2: 'dpad_up',
|
||||||
|
0x2c3: 'dpad_down',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, device: str = '/dev/input/js0', *args, **kwargs):
|
||||||
|
"""
|
||||||
|
:param device: Joystick device to monitor (default: ``/dev/input/js0``).
|
||||||
|
"""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.device = device
|
||||||
|
self._axis_states = {}
|
||||||
|
self._button_states = {}
|
||||||
|
self._axis_map = []
|
||||||
|
self._button_map = []
|
||||||
|
|
||||||
|
def _init_joystick(self, dev: IO):
|
||||||
|
# Get the device name.
|
||||||
|
buf = array.array('B', [0] * 64)
|
||||||
|
ioctl(dev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len)
|
||||||
|
js_name = buf.tobytes().rstrip(b'\x00').decode('utf-8')
|
||||||
|
|
||||||
|
# Get number of axes and buttons.
|
||||||
|
buf = array.array('B', [0])
|
||||||
|
ioctl(dev, 0x80016a11, buf) # JSIOCGAXES
|
||||||
|
num_axes = buf[0]
|
||||||
|
|
||||||
|
buf = array.array('B', [0])
|
||||||
|
ioctl(dev, 0x80016a12, buf) # JSIOCGBUTTONS
|
||||||
|
num_buttons = buf[0]
|
||||||
|
|
||||||
|
# Get the axis map.
|
||||||
|
buf = array.array('B', [0] * 0x40)
|
||||||
|
ioctl(dev, 0x80406a32, buf) # JSIOCGAXMAP
|
||||||
|
|
||||||
|
for axis in buf[:num_axes]:
|
||||||
|
axis_name = self.axis_names.get(axis, 'unknown(0x%02x)' % axis)
|
||||||
|
self._axis_map.append(axis_name)
|
||||||
|
self._axis_states[axis_name] = 0.0
|
||||||
|
|
||||||
|
# Get the button map.
|
||||||
|
buf = array.array('H', [0] * 200)
|
||||||
|
ioctl(dev, 0x80406a34, buf) # JSIOCGBTNMAP
|
||||||
|
|
||||||
|
for btn in buf[:num_buttons]:
|
||||||
|
btn_name = self.button_names.get(btn, 'unknown(0x%03x)' % btn)
|
||||||
|
self._button_map.append(btn_name)
|
||||||
|
self._button_states[btn_name] = 0
|
||||||
|
|
||||||
|
self.bus.post(JoystickConnectedEvent(device=self.device, name=js_name, axes=self._axis_map,
|
||||||
|
buttons=self._button_map))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
while not self.should_stop():
|
||||||
|
# Open the joystick device.
|
||||||
|
self.logger.info(f'Opening {self.device}...')
|
||||||
|
try:
|
||||||
|
jsdev = open(self.device, 'rb')
|
||||||
|
self._init_joystick(jsdev)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.debug(f'Joystick device on {self.device} not available: {e}')
|
||||||
|
time.sleep(5)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Joystick event loop
|
||||||
|
while not self.should_stop():
|
||||||
|
try:
|
||||||
|
evbuf = jsdev.read(8)
|
||||||
|
if evbuf:
|
||||||
|
_, value, evt_type, number = struct.unpack('IhBB', evbuf)
|
||||||
|
|
||||||
|
if evt_type & 0x01:
|
||||||
|
button = self._button_map[number]
|
||||||
|
if button:
|
||||||
|
self._button_states[button] = value
|
||||||
|
evt_class = JoystickButtonPressedEvent if value else JoystickButtonReleasedEvent
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
self.bus.post(evt_class(button=button))
|
||||||
|
|
||||||
|
if evt_type & 0x02:
|
||||||
|
axis = self._axis_map[number]
|
||||||
|
if axis:
|
||||||
|
fvalue = value / 32767.0
|
||||||
|
self._axis_states[axis] = fvalue
|
||||||
|
# noinspection PyTypeChecker
|
||||||
|
self.bus.post(JoystickAxisEvent(axis=axis, value=fvalue))
|
||||||
|
except OSError as e:
|
||||||
|
self.logger.warning(f'Connection to {self.device} lost: {e}')
|
||||||
|
self.bus.post(JoystickDisconnectedEvent(device=self.device))
|
||||||
|
break
|
|
@ -1,5 +1,5 @@
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import List
|
from typing import Optional, Iterable, Union
|
||||||
|
|
||||||
from platypush.message.event import Event
|
from platypush.message.event import Event
|
||||||
|
|
||||||
|
@ -35,6 +35,18 @@ class JoystickConnectedEvent(_JoystickEvent):
|
||||||
"""
|
"""
|
||||||
Event triggered upon joystick connection.
|
Event triggered upon joystick connection.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
*args,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
axes: Optional[Iterable[Union[int, str]]] = None,
|
||||||
|
buttons: Optional[Iterable[Union[int, str]]] = None,
|
||||||
|
**kwargs):
|
||||||
|
"""
|
||||||
|
:param name: Device name.
|
||||||
|
:param axes: List of the device axes, as indices or names.
|
||||||
|
:param buttons: List of the device buttons, as indices or names.
|
||||||
|
"""
|
||||||
|
super().__init__(*args, name=name, axes=axes, buttons=buttons, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class JoystickDisconnectedEvent(_JoystickEvent):
|
class JoystickDisconnectedEvent(_JoystickEvent):
|
||||||
|
@ -48,7 +60,7 @@ class JoystickStateEvent(_JoystickEvent):
|
||||||
Event triggered when the state of the joystick changes.
|
Event triggered when the state of the joystick changes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, axes: List[int], buttons: List[bool], **kwargs):
|
def __init__(self, *args, axes: Iterable[int], buttons: Iterable[bool], **kwargs):
|
||||||
"""
|
"""
|
||||||
:param axes: Joystick axes values, as a list of integer values.
|
:param axes: Joystick axes values, as a list of integer values.
|
||||||
:param buttons: Joystick buttons values, as a list of boolean values (True for pressed, False for released).
|
:param buttons: Joystick buttons values, as a list of boolean values (True for pressed, False for released).
|
||||||
|
@ -61,9 +73,9 @@ class JoystickButtonPressedEvent(_JoystickEvent):
|
||||||
Event triggered when a joystick button is pressed.
|
Event triggered when a joystick button is pressed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, button: int, **kwargs):
|
def __init__(self, *args, button: Union[int, str], **kwargs):
|
||||||
"""
|
"""
|
||||||
:param button: Button index.
|
:param button: Button index or name.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, button=button, **kwargs)
|
super().__init__(*args, button=button, **kwargs)
|
||||||
|
|
||||||
|
@ -73,9 +85,9 @@ class JoystickButtonReleasedEvent(_JoystickEvent):
|
||||||
Event triggered when a joystick button is released.
|
Event triggered when a joystick button is released.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, button: int, **kwargs):
|
def __init__(self, *args, button: Union[int, str], **kwargs):
|
||||||
"""
|
"""
|
||||||
:param button: Button index.
|
:param button: Button index or name.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, button=button, **kwargs)
|
super().__init__(*args, button=button, **kwargs)
|
||||||
|
|
||||||
|
@ -85,9 +97,9 @@ class JoystickAxisEvent(_JoystickEvent):
|
||||||
Event triggered when an axis value of the joystick changes.
|
Event triggered when an axis value of the joystick changes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, axis: int, value: int, **kwargs):
|
def __init__(self, *args, axis: Union[int, str], value: int, **kwargs):
|
||||||
"""
|
"""
|
||||||
:param axis: Axis index.
|
:param axis: Axis index or name.
|
||||||
:param value: Axis value.
|
:param value: Axis value.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, axis=axis, value=value, **kwargs)
|
super().__init__(*args, axis=axis, value=value, **kwargs)
|
||||||
|
|
Loading…
Reference in a new issue