File shanwan-joystick.py of Package shanwan-gamepad
# shanwan-joystick.py - Userspace script to split controller input for
# the Shanwan Wireless Twin controllers
#
# Copyright (C) 2016 Joost van den Brandt
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import uinput
import time
from collections import defaultdict
#import struct
# Shanwan Twin USB hidraw driver.
# Read 32 bytes from hidraw. They remain static if you don't press
# anything on the joystick, but occasionally it flips from having
# joystick id 1 to 2 at the start and vice-versa. We'll just test the
# first byte then we'll know which set of 8 bytes to look at for each
# joystick.
class TwinUSB:
def __init__(self, devfile):
try:
self.file = open( devfile, "rb" );
except:
print ("Could not open", devfile)
return
self.buf = None
self.oldBuf = None
self.hatDir = defaultdict(list)
self.oldHatDir = defaultdict(list)
# Will hold the state of all of the buttons and axes.
# Each gamepad has 12 buttons, 1 dpad and 2 analog sticks.
# The buttons are either on or off, the dpad is either in
# some direction or none.
self.eventState = defaultdict(list)
self.events = (uinput.BTN_TRIGGER, uinput.BTN_THUMB, uinput.BTN_THUMB2, \
uinput.BTN_TOP, uinput.BTN_TOP2, uinput.BTN_PINKIE, \
uinput.BTN_BASE, uinput.BTN_BASE2, uinput.BTN_BASE3, \
uinput.BTN_BASE4, uinput.BTN_BASE5, uinput.BTN_BASE6,
uinput.ABS_HAT0X + (-1, 1, 0, 0), \
uinput.ABS_HAT0Y + (-1, 1, 0, 0), \
uinput.ABS_X + (0, 255, 0, 0), uinput.ABS_Y + (0, 255, 0, 0),\
uinput.ABS_Z + (0, 255, 0, 0), uinput.ABS_RZ + (0, 255, 0, 0))
self.axisEvents = [uinput.ABS_HAT0X, uinput.ABS_HAT0Y, uinput.ABS_X, \
uinput.ABS_Y, uinput.ABS_Z, uinput.ABS_RZ]
# Hat directions from top clockwise
self.hatDirs = [[0,-1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1]]
self.gamepadIds = []
self.devices = defaultdict(lambda : uinput.device([]))
time.sleep(1)
def getEventChanges(self):
"""Read the raw controller input data into a buffer and interpret
the data if it's different from the last read"""
# Note: for buttons being released, the event state for the button
# will be set to -1. (t.e. if it was previously pressed and now is
# not pressed, it will be set to -1) The button release event will
# be emitted once then the event state for the button will be reset
# to 0.
self.buf = self.file.read(8)
if self.buf != self.oldBuf:
self.oldBuf = self.buf
# Interpret the events
self.interpretEvents(self.buf[0])
def interpretEvents(self, dev):
"""Interpret the raw controller input data and update the event state"""
# FirstID is the joystick id that is in the first byte of the
# buffer, either 1 or 2. If it's anything else then something
# went wrong.
if dev in self.gamepadIds:
btn = self.buf[5]
# Buttons 1 to 4:
self.eventState[dev][0] = -1 if self.eventState[dev][0] and not btn & 16 else btn & 16
self.eventState[dev][1] = -1 if self.eventState[dev][1] and not btn & 32 else btn & 32
self.eventState[dev][2] = -1 if self.eventState[dev][2] and not btn & 64 else btn & 64
self.eventState[dev][3] = -1 if self.eventState[dev][3] and not btn & 128 else btn & 128
# Buttons 5 to 12:
btn = self.buf[6]
self.eventState[dev][4] = -1 if self.eventState[dev][4] and not btn & 1 else btn & 1
self.eventState[dev][5] = -1 if self.eventState[dev][5] and not btn & 2 else btn & 2
self.eventState[dev][6] = -1 if self.eventState[dev][6] and not btn & 4 else btn & 4
self.eventState[dev][7] = -1 if self.eventState[dev][7] and not btn & 8 else btn & 8
self.eventState[dev][8] = -1 if self.eventState[dev][8] and not btn & 16 else btn & 16
self.eventState[dev][9] = -1 if self.eventState[dev][9] and not btn & 32 else btn & 32
self.eventState[dev][10] = -1 if self.eventState[dev][10] and not btn & 64 else btn & 64
self.eventState[dev][11] = -1 if self.eventState[dev][11] and not btn & 128 else btn & 128
# Left stick (eventstate 14-15) (byte 3 = x 4 = y))
axisX = self.buf[3]
axisY = self.buf[4]
self.eventState[dev][12] = axisX
self.eventState[dev][13] = axisY
# Right stick (eventstate 12-13) (byte 1 = x 2 = y))
axisX = self.buf[1]
axisY = self.buf[2]
self.eventState[dev][14] = axisX
self.eventState[dev][15] = axisY
# D-pad (first 4 bits of byte 5 is the hat direction.
# value is 0 to 7 for the 8 directions starting from
# the top and going clockwise. Neutral position = 15.)
# these must be converted into -1, 0, 1 event values for
# the x and y hat axes.
# Mask off the last 4 bits
self.hatDir[dev] = self.buf[5] & 0xf
if self.hatDir[dev] == 15:
self.eventState[dev][16] = 0
self.eventState[dev][17] = 0
else:
self.eventState[dev][16] = self.hatDirs[self.hatDir[dev]][0]
self.eventState[dev][17] = self.hatDirs[self.hatDir[dev]][1]
else:
self.gamepadIds.append(dev)
# BusType=3 --> USB
self.devices[dev] =uinput.Device(self.events, name="usb gamepad ", vendor=0x0810, product=0xe501, bustype=3, version=0x0110)
self.eventState[dev] = ([0, 0, 0, 0, 0, 0, 0 ,0 ,0 ,0 ,0 ,0, 127, 127, 127, 127, 0, 0])
self.hatDir[dev] = 15
print ("ShanWan controller with ID "+str(dev)+" added!")
def emitEvents(self):
"""Emit input events based on the current event state"""
for i in self.gamepadIds:
# Button events:
for j in range(0, 12):
# If this button was just released then emit a release event
# and reset to 0.
if self.eventState[i][j] == -1:
self.devices[i].emit(self.events[j], 0)
self.eventState[i][j] = 0
elif self.eventState[i][j] > 0:
self.devices[i].emit(self.events[j], 1)
# Axis events
self.devices[i].emit(self.axisEvents[2], self.eventState[i][12])
self.devices[i].emit(self.axisEvents[3], self.eventState[i][13])
self.devices[i].emit(self.axisEvents[4], self.eventState[i][14])
self.devices[i].emit(self.axisEvents[5], self.eventState[i][15])
# D=pad
# if the current hat direction is different to oldHat
# then send an event on both axes
if self.oldHatDir[i] != self.hatDir[i]:
self.devices[i].emit(self.axisEvents[0], self.eventState[i][16])
self.devices[i].emit(self.axisEvents[1], self.eventState[i][17])
self.oldHatDir[i] = self.hatDir[i]
"""Main proecess"""
cJoysticks = None
try:
if len(sys.argv) != 2:
print ("Usage: joystick.py [hidraw-file]")
sys.exit()
if not sys.argv[1].startswith("/dev/"):
print("You must enter a hidraw file path, e.g. /dev/hidraw0")
sys.exit()
cJoysticks = TwinUSB(sys.argv[1])
# see if the file was opened
if hasattr(cJoysticks, 'file'):
running = 1
while (running):
cJoysticks.getEventChanges()
cJoysticks.emitEvents()
except KeyboardInterrupt:
running = 0
print ("Bye")
finally:
if cJoysticks:
if hasattr(cJoysticks, 'file'):
cJoysticks.file.close()