2018-11-26 22:36:32 -05:00
|
|
|
# Martin O'Hanlon
|
|
|
|
# www.stuffaboutcode.com
|
|
|
|
# A class for reading values from an xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
# uses xboxdrv and pygame
|
|
|
|
# xboxdrv should already be running
|
|
|
|
|
|
|
|
import pygame
|
|
|
|
from pygame.locals import *
|
2018-11-26 22:36:32 -05:00
|
|
|
import os
|
|
|
|
import sys
|
2014-09-28 18:37:13 +00:00
|
|
|
import threading
|
|
|
|
import time
|
|
|
|
|
|
|
|
"""
|
|
|
|
NOTES - pygame events and values
|
|
|
|
JOYAXISMOTION
|
|
|
|
event.axis event.value
|
|
|
|
0 - x axis left thumb (+1 is right, -1 is left)
|
|
|
|
1 - y axis left thumb (+1 is down, -1 is up)
|
|
|
|
2 - x axis right thumb (+1 is right, -1 is left)
|
|
|
|
3 - y axis right thumb (+1 is down, -1 is up)
|
|
|
|
4 - right trigger
|
|
|
|
5 - left trigger
|
|
|
|
JOYBUTTONDOWN | JOYBUTTONUP
|
|
|
|
event.button
|
|
|
|
A = 0
|
|
|
|
B = 1
|
|
|
|
X = 2
|
|
|
|
Y = 3
|
|
|
|
LB = 4
|
|
|
|
RB = 5
|
|
|
|
BACK = 6
|
|
|
|
START = 7
|
|
|
|
XBOX = 8
|
|
|
|
LEFTTHUMB = 9
|
|
|
|
RIGHTTHUMB = 10
|
|
|
|
JOYHATMOTION
|
|
|
|
event.value
|
|
|
|
[0] - horizontal
|
|
|
|
[1] - vertival
|
|
|
|
[0].0 - middle
|
|
|
|
[0].-1 - left
|
|
|
|
[0].+1 - right
|
|
|
|
[1].0 - middle
|
|
|
|
[1].-1 - bottom
|
|
|
|
[1].+1 - top
|
|
|
|
"""
|
2018-11-26 22:36:32 -05:00
|
|
|
# Main class for reading the xbox controller values
|
|
|
|
|
|
|
|
|
2014-09-28 18:37:13 +00:00
|
|
|
class XboxController(threading.Thread):
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# internal ids for the xbox controls
|
2014-09-28 18:37:13 +00:00
|
|
|
class XboxControls():
|
|
|
|
LTHUMBX = 0
|
|
|
|
LTHUMBY = 1
|
|
|
|
RTHUMBX = 2
|
|
|
|
RTHUMBY = 3
|
|
|
|
RTRIGGER = 4
|
|
|
|
LTRIGGER = 5
|
|
|
|
A = 6
|
|
|
|
B = 7
|
|
|
|
X = 8
|
|
|
|
Y = 9
|
|
|
|
LB = 10
|
|
|
|
RB = 11
|
|
|
|
BACK = 12
|
|
|
|
START = 13
|
|
|
|
XBOX = 14
|
|
|
|
LEFTTHUMB = 15
|
|
|
|
RIGHTTHUMB = 16
|
|
|
|
DPAD = 17
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# pygame axis constants for the analogue controls of the xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
class PyGameAxis():
|
|
|
|
LTHUMBX = 0
|
|
|
|
LTHUMBY = 1
|
|
|
|
RTHUMBX = 2
|
|
|
|
RTHUMBY = 3
|
|
|
|
RTRIGGER = 4
|
|
|
|
LTRIGGER = 5
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# pygame constants for the buttons of the xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
class PyGameButtons():
|
|
|
|
A = 0
|
|
|
|
B = 1
|
|
|
|
X = 2
|
|
|
|
Y = 3
|
|
|
|
LB = 4
|
|
|
|
RB = 5
|
|
|
|
BACK = 6
|
|
|
|
START = 7
|
|
|
|
XBOX = 8
|
|
|
|
LEFTTHUMB = 9
|
|
|
|
RIGHTTHUMB = 10
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# map between pygame axis (analogue stick) ids and xbox control ids
|
2014-09-28 18:37:13 +00:00
|
|
|
AXISCONTROLMAP = {PyGameAxis.LTHUMBX: XboxControls.LTHUMBX,
|
|
|
|
PyGameAxis.LTHUMBY: XboxControls.LTHUMBY,
|
|
|
|
PyGameAxis.RTHUMBX: XboxControls.RTHUMBX,
|
|
|
|
PyGameAxis.RTHUMBY: XboxControls.RTHUMBY}
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# map between pygame axis (trigger) ids and xbox control ids
|
2014-09-28 18:37:13 +00:00
|
|
|
TRIGGERCONTROLMAP = {PyGameAxis.RTRIGGER: XboxControls.RTRIGGER,
|
|
|
|
PyGameAxis.LTRIGGER: XboxControls.LTRIGGER}
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# map between pygame buttons ids and xbox contorl ids
|
2014-09-28 18:37:13 +00:00
|
|
|
BUTTONCONTROLMAP = {PyGameButtons.A: XboxControls.A,
|
|
|
|
PyGameButtons.B: XboxControls.B,
|
|
|
|
PyGameButtons.X: XboxControls.X,
|
|
|
|
PyGameButtons.Y: XboxControls.Y,
|
|
|
|
PyGameButtons.LB: XboxControls.LB,
|
|
|
|
PyGameButtons.RB: XboxControls.RB,
|
|
|
|
PyGameButtons.BACK: XboxControls.BACK,
|
|
|
|
PyGameButtons.START: XboxControls.START,
|
|
|
|
PyGameButtons.XBOX: XboxControls.XBOX,
|
|
|
|
PyGameButtons.LEFTTHUMB: XboxControls.LEFTTHUMB,
|
|
|
|
PyGameButtons.RIGHTTHUMB: XboxControls.RIGHTTHUMB}
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# setup xbox controller class
|
2014-09-28 18:37:13 +00:00
|
|
|
def __init__(self,
|
2018-11-26 22:36:32 -05:00
|
|
|
controllerCallBack=None,
|
|
|
|
joystickNo=0,
|
|
|
|
deadzone=0.1,
|
|
|
|
scale=1,
|
|
|
|
invertYAxis=False):
|
2014-09-28 18:37:13 +00:00
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# setup threading
|
2014-09-28 18:37:13 +00:00
|
|
|
threading.Thread.__init__(self)
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# persist values
|
2014-09-28 18:37:13 +00:00
|
|
|
self.running = False
|
|
|
|
self.controllerCallBack = controllerCallBack
|
|
|
|
self.joystickNo = joystickNo
|
|
|
|
self.lowerDeadzone = deadzone * -1
|
|
|
|
self.upperDeadzone = deadzone
|
|
|
|
self.scale = scale
|
|
|
|
self.invertYAxis = invertYAxis
|
|
|
|
self.controlCallbacks = {}
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# setup controller properties
|
|
|
|
self.controlValues = {self.XboxControls.LTHUMBX: 0,
|
|
|
|
self.XboxControls.LTHUMBY: 0,
|
|
|
|
self.XboxControls.RTHUMBX: 0,
|
|
|
|
self.XboxControls.RTHUMBY: 0,
|
|
|
|
self.XboxControls.RTRIGGER: 0,
|
|
|
|
self.XboxControls.LTRIGGER: 0,
|
|
|
|
self.XboxControls.A: 0,
|
|
|
|
self.XboxControls.B: 0,
|
|
|
|
self.XboxControls.X: 0,
|
|
|
|
self.XboxControls.Y: 0,
|
|
|
|
self.XboxControls.LB: 0,
|
|
|
|
self.XboxControls.RB: 0,
|
|
|
|
self.XboxControls.BACK: 0,
|
|
|
|
self.XboxControls.START: 0,
|
|
|
|
self.XboxControls.XBOX: 0,
|
|
|
|
self.XboxControls.LEFTTHUMB: 0,
|
|
|
|
self.XboxControls.RIGHTTHUMB: 0,
|
|
|
|
self.XboxControls.DPAD: (0, 0)}
|
|
|
|
|
|
|
|
# setup pygame
|
2014-09-28 18:37:13 +00:00
|
|
|
self._setupPygame(joystickNo)
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# Create controller properties
|
2014-09-28 18:37:13 +00:00
|
|
|
@property
|
|
|
|
def LTHUMBX(self):
|
|
|
|
return self.controlValues[self.XboxControls.LTHUMBX]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def LTHUMBY(self):
|
|
|
|
return self.controlValues[self.XboxControls.LTHUMBY]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def RTHUMBX(self):
|
|
|
|
return self.controlValues[self.XboxControls.RTHUMBX]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def RTHUMBY(self):
|
|
|
|
return self.controlValues[self.XboxControls.RTHUMBY]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def RTRIGGER(self):
|
|
|
|
return self.controlValues[self.XboxControls.RTRIGGER]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def LTRIGGER(self):
|
|
|
|
return self.controlValues[self.XboxControls.LTRIGGER]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def A(self):
|
|
|
|
return self.controlValues[self.XboxControls.A]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def B(self):
|
|
|
|
return self.controlValues[self.XboxControls.B]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def X(self):
|
|
|
|
return self.controlValues[self.XboxControls.X]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def Y(self):
|
|
|
|
return self.controlValues[self.XboxControls.Y]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def LB(self):
|
|
|
|
return self.controlValues[self.XboxControls.LB]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def RB(self):
|
|
|
|
return self.controlValues[self.XboxControls.RB]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def BACK(self):
|
|
|
|
return self.controlValues[self.XboxControls.BACK]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def START(self):
|
|
|
|
return self.controlValues[self.XboxControls.START]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def XBOX(self):
|
|
|
|
return self.controlValues[self.XboxControls.XBOX]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def LEFTTHUMB(self):
|
|
|
|
return self.controlValues[self.XboxControls.LEFTTHUMB]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def RIGHTTHUMB(self):
|
|
|
|
return self.controlValues[self.XboxControls.RIGHTTHUMB]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def DPAD(self):
|
|
|
|
return self.controlValues[self.XboxControls.DPAD]
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# setup pygame
|
2014-09-28 18:37:13 +00:00
|
|
|
def _setupPygame(self, joystickNo):
|
|
|
|
# set SDL to use the dummy NULL video driver, so it doesn't need a windowing system.
|
|
|
|
os.environ["SDL_VIDEODRIVER"] = "dummy"
|
|
|
|
# init pygame
|
|
|
|
pygame.init()
|
|
|
|
# create a 1x1 pixel screen, its not used so it doesnt matter
|
|
|
|
screen = pygame.display.set_mode((1, 1))
|
|
|
|
# init the joystick control
|
|
|
|
pygame.joystick.init()
|
|
|
|
# how many joysticks are there
|
2018-11-26 22:36:32 -05:00
|
|
|
# print pygame.joystick.get_count()
|
2014-09-28 18:37:13 +00:00
|
|
|
# get the first joystick
|
|
|
|
joy = pygame.joystick.Joystick(joystickNo)
|
|
|
|
# init that joystick
|
|
|
|
joy.init()
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# called by the thread
|
2014-09-28 18:37:13 +00:00
|
|
|
def run(self):
|
|
|
|
self._start()
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# start the controller
|
2014-09-28 18:37:13 +00:00
|
|
|
def _start(self):
|
|
|
|
self.running = True
|
2018-11-26 22:36:32 -05:00
|
|
|
# run until the controller is stopped
|
2014-09-28 18:37:13 +00:00
|
|
|
while(self.running):
|
2018-11-26 22:36:32 -05:00
|
|
|
# react to the pygame events that come from the xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
for event in pygame.event.get():
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
if event.type == pygame.QUIT:
|
|
|
|
print("Received event 'Quit', exiting.")
|
|
|
|
self.stop()
|
|
|
|
|
|
|
|
# thumb sticks, trigger buttons
|
2014-09-28 18:37:13 +00:00
|
|
|
if event.type == JOYAXISMOTION:
|
2018-11-26 22:36:32 -05:00
|
|
|
# is this axis on our xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
if event.axis in self.AXISCONTROLMAP:
|
2018-11-26 22:36:32 -05:00
|
|
|
# is this a y axis
|
|
|
|
yAxis = True if (
|
|
|
|
event.axis == self.PyGameAxis.LTHUMBY or event.axis == self.PyGameAxis.RTHUMBY) else False
|
|
|
|
# update the control value
|
2014-09-28 18:37:13 +00:00
|
|
|
self.updateControlValue(self.AXISCONTROLMAP[event.axis],
|
|
|
|
self._sortOutAxisValue(event.value, yAxis))
|
2018-11-26 22:36:32 -05:00
|
|
|
# is this axis a trigger
|
2014-09-28 18:37:13 +00:00
|
|
|
if event.axis in self.TRIGGERCONTROLMAP:
|
2018-11-26 22:36:32 -05:00
|
|
|
# update the control value
|
2014-09-28 18:37:13 +00:00
|
|
|
self.updateControlValue(self.TRIGGERCONTROLMAP[event.axis],
|
|
|
|
self._sortOutTriggerValue(event.value))
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# d pad
|
2014-09-28 18:37:13 +00:00
|
|
|
elif event.type == JOYHATMOTION:
|
2018-11-26 22:36:32 -05:00
|
|
|
# update control value
|
|
|
|
self.updateControlValue(
|
|
|
|
self.XboxControls.DPAD, event.value)
|
2014-09-28 18:37:13 +00:00
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# button pressed and unpressed
|
2014-09-28 18:37:13 +00:00
|
|
|
elif event.type == JOYBUTTONUP or event.type == JOYBUTTONDOWN:
|
2018-11-26 22:36:32 -05:00
|
|
|
# is this button on our xbox controller
|
2014-09-28 18:37:13 +00:00
|
|
|
if event.button in self.BUTTONCONTROLMAP:
|
2018-11-26 22:36:32 -05:00
|
|
|
# update control value
|
2014-09-28 18:37:13 +00:00
|
|
|
self.updateControlValue(self.BUTTONCONTROLMAP[event.button],
|
|
|
|
self._sortOutButtonValue(event.type))
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# stops the controller
|
2014-09-28 18:37:13 +00:00
|
|
|
def stop(self):
|
|
|
|
self.running = False
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# updates a specific value in the control dictionary
|
2014-09-28 18:37:13 +00:00
|
|
|
def updateControlValue(self, control, value):
|
2018-11-26 22:36:32 -05:00
|
|
|
# if the value has changed update it and call the callbacks
|
2014-09-28 18:37:13 +00:00
|
|
|
if self.controlValues[control] != value:
|
|
|
|
self.controlValues[control] = value
|
|
|
|
self.doCallBacks(control, value)
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# calls the call backs if necessary
|
2014-09-28 18:37:13 +00:00
|
|
|
def doCallBacks(self, control, value):
|
2018-11-26 22:36:32 -05:00
|
|
|
# call the general callback
|
|
|
|
if self.controllerCallBack != None:
|
|
|
|
self.controllerCallBack(control, value)
|
2014-09-28 18:37:13 +00:00
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# has a specific callback been setup?
|
2014-09-28 18:37:13 +00:00
|
|
|
if control in self.controlCallbacks:
|
|
|
|
self.controlCallbacks[control](value)
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# used to add a specific callback to a control
|
2014-09-28 18:37:13 +00:00
|
|
|
def setupControlCallback(self, control, callbackFunction):
|
|
|
|
# add callback to the dictionary
|
|
|
|
self.controlCallbacks[control] = callbackFunction
|
2018-11-26 22:36:32 -05:00
|
|
|
|
|
|
|
# scales the axis values, applies the deadzone
|
|
|
|
def _sortOutAxisValue(self, value, yAxis=False):
|
|
|
|
# invert yAxis
|
|
|
|
if yAxis and self.invertYAxis:
|
|
|
|
value = value * -1
|
|
|
|
# scale the value
|
2014-09-28 18:37:13 +00:00
|
|
|
value = value * self.scale
|
2018-11-26 22:36:32 -05:00
|
|
|
# apply the deadzone
|
|
|
|
if value < self.upperDeadzone and value > self.lowerDeadzone:
|
|
|
|
value = 0
|
2014-09-28 18:37:13 +00:00
|
|
|
return value
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# turns the trigger value into something sensible and scales it
|
2014-09-28 18:37:13 +00:00
|
|
|
def _sortOutTriggerValue(self, value):
|
2018-11-26 22:36:32 -05:00
|
|
|
# trigger goes -1 to 1 (-1 is off, 1 is full on, half is 0) - I want this to be 0 - 1
|
|
|
|
value = max(0, (value + 1) / 2)
|
|
|
|
# scale the value
|
2014-09-28 18:37:13 +00:00
|
|
|
value = value * self.scale
|
|
|
|
return value
|
|
|
|
|
2018-11-26 22:36:32 -05:00
|
|
|
# turns the event type (up/down) into a value
|
2014-09-28 18:37:13 +00:00
|
|
|
def _sortOutButtonValue(self, eventType):
|
2018-11-26 22:36:32 -05:00
|
|
|
# if the button is down its 1, if the button is up its 0
|
2014-09-28 18:37:13 +00:00
|
|
|
value = 1 if eventType == JOYBUTTONDOWN else 0
|
|
|
|
return value
|
|
|
|
|
|
|
|
#tests
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
|
|
|
#generic call back
|
|
|
|
def controlCallBack(xboxControlId, value):
|
2018-11-26 22:36:32 -05:00
|
|
|
print ("Control Id = {}, Value = {}".format(xboxControlId, value))
|
2014-09-28 18:37:13 +00:00
|
|
|
|
|
|
|
#specific callbacks for the left thumb (X & Y)
|
|
|
|
def leftThumbX(xValue):
|
2018-11-26 22:36:32 -05:00
|
|
|
print ("LX {}".format(xValue))
|
2014-09-28 18:37:13 +00:00
|
|
|
def leftThumbY(yValue):
|
2018-11-26 22:36:32 -05:00
|
|
|
print ("LY {}".format(yValue))
|
2014-09-28 18:37:13 +00:00
|
|
|
|
|
|
|
#setup xbox controller, set out the deadzone and scale, also invert the Y Axis (for some reason in Pygame negative is up - wierd!
|
|
|
|
xboxCont = XboxController(controlCallBack, deadzone = 30, scale = 100, invertYAxis = True)
|
|
|
|
|
|
|
|
#setup the left thumb (X & Y) callbacks
|
|
|
|
xboxCont.setupControlCallback(xboxCont.XboxControls.LTHUMBX, leftThumbX)
|
|
|
|
xboxCont.setupControlCallback(xboxCont.XboxControls.LTHUMBY, leftThumbY)
|
|
|
|
|
|
|
|
try:
|
|
|
|
#start the controller
|
|
|
|
xboxCont.start()
|
2018-11-26 22:36:32 -05:00
|
|
|
print ("xbox controller running")
|
|
|
|
|
2014-09-28 18:37:13 +00:00
|
|
|
#Ctrl C
|
2018-11-26 22:36:32 -05:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
print ("User cancelled")
|
2014-09-28 18:37:13 +00:00
|
|
|
|
|
|
|
#error
|
|
|
|
except:
|
2018-11-26 22:36:32 -05:00
|
|
|
print ("Unexpected error:", sys.exc_info()[0])
|
2014-09-28 18:37:13 +00:00
|
|
|
raise
|
|
|
|
|
|
|
|
finally:
|
|
|
|
#stop the controller
|
|
|
|
xboxCont.stop()
|