Skip to content
This repository was archived by the owner on Aug 16, 2020. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 109 additions & 105 deletions XboxController.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#Martin O'Hanlon
#www.stuffaboutcode.com
#A class for reading values from an xbox controller
# Martin O'Hanlon
# www.stuffaboutcode.com
# A class for reading values from an xbox controller
# uses xboxdrv and pygame
# xboxdrv should already be running

import pygame
from pygame.locals import *
import os, sys
import os
import sys
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)
Expand All @@ -21,7 +21,6 @@
3 - y axis right thumb (+1 is down, -1 is up)
4 - right trigger
5 - left trigger

JOYBUTTONDOWN | JOYBUTTONUP
event.button
A = 0
Expand All @@ -35,7 +34,6 @@
XBOX = 8
LEFTTHUMB = 9
RIGHTTHUMB = 10

JOYHATMOTION
event.value
[0] - horizontal
Expand All @@ -46,12 +44,13 @@
[1].0 - middle
[1].-1 - bottom
[1].+1 - top

"""
#Main class for reading the xbox controller values
# Main class for reading the xbox controller values


class XboxController(threading.Thread):

#internal ids for the xbox controls
# internal ids for the xbox controls
class XboxControls():
LTHUMBX = 0
LTHUMBY = 1
Expand All @@ -72,7 +71,7 @@ class XboxControls():
RIGHTTHUMB = 16
DPAD = 17

#pygame axis constants for the analogue controls of the xbox controller
# pygame axis constants for the analogue controls of the xbox controller
class PyGameAxis():
LTHUMBX = 0
LTHUMBY = 1
Expand All @@ -81,7 +80,7 @@ class PyGameAxis():
RTRIGGER = 4
LTRIGGER = 5

#pygame constants for the buttons of the xbox controller
# pygame constants for the buttons of the xbox controller
class PyGameButtons():
A = 0
B = 1
Expand All @@ -95,17 +94,17 @@ class PyGameButtons():
LEFTTHUMB = 9
RIGHTTHUMB = 10

#map between pygame axis (analogue stick) ids and xbox control ids
# map between pygame axis (analogue stick) ids and xbox control ids
AXISCONTROLMAP = {PyGameAxis.LTHUMBX: XboxControls.LTHUMBX,
PyGameAxis.LTHUMBY: XboxControls.LTHUMBY,
PyGameAxis.RTHUMBX: XboxControls.RTHUMBX,
PyGameAxis.RTHUMBY: XboxControls.RTHUMBY}
#map between pygame axis (trigger) ids and xbox control ids

# map between pygame axis (trigger) ids and xbox control ids
TRIGGERCONTROLMAP = {PyGameAxis.RTRIGGER: XboxControls.RTRIGGER,
PyGameAxis.LTRIGGER: XboxControls.LTRIGGER}

#map between pygame buttons ids and xbox contorl ids
# map between pygame buttons ids and xbox contorl ids
BUTTONCONTROLMAP = {PyGameButtons.A: XboxControls.A,
PyGameButtons.B: XboxControls.B,
PyGameButtons.X: XboxControls.X,
Expand All @@ -117,19 +116,19 @@ class PyGameButtons():
PyGameButtons.XBOX: XboxControls.XBOX,
PyGameButtons.LEFTTHUMB: XboxControls.LEFTTHUMB,
PyGameButtons.RIGHTTHUMB: XboxControls.RIGHTTHUMB}
#setup xbox controller class

# setup xbox controller class
def __init__(self,
controllerCallBack = None,
joystickNo = 0,
deadzone = 0.1,
scale = 1,
invertYAxis = False):
controllerCallBack=None,
joystickNo=0,
deadzone=0.1,
scale=1,
invertYAxis=False):

#setup threading
# setup threading
threading.Thread.__init__(self)
#persist values

# persist values
self.running = False
self.controllerCallBack = controllerCallBack
self.joystickNo = joystickNo
Expand All @@ -139,30 +138,30 @@ def __init__(self,
self.invertYAxis = invertYAxis
self.controlCallbacks = {}

#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
# 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
self._setupPygame(joystickNo)

#Create controller properties
# Create controller properties
@property
def LTHUMBX(self):
return self.controlValues[self.XboxControls.LTHUMBX]
Expand Down Expand Up @@ -235,7 +234,7 @@ def RIGHTTHUMB(self):
def DPAD(self):
return self.controlValues[self.XboxControls.DPAD]

#setup pygame
# setup pygame
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"
Expand All @@ -246,100 +245,107 @@ def _setupPygame(self, joystickNo):
# init the joystick control
pygame.joystick.init()
# how many joysticks are there
#print pygame.joystick.get_count()
# print pygame.joystick.get_count()
# get the first joystick
joy = pygame.joystick.Joystick(joystickNo)
# init that joystick
joy.init()

#called by the thread
# called by the thread
def run(self):
self._start()

#start the controller
# start the controller
def _start(self):

self.running = True

#run until the controller is stopped
# run until the controller is stopped
while(self.running):
#react to the pygame events that come from the xbox controller
# react to the pygame events that come from the xbox controller
for event in pygame.event.get():

#thumb sticks, trigger buttons
if event.type == pygame.QUIT:
print("Received event 'Quit', exiting.")
self.stop()

# thumb sticks, trigger buttons
if event.type == JOYAXISMOTION:
#is this axis on our xbox controller
# is this axis on our xbox controller
if event.axis in self.AXISCONTROLMAP:
#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
# 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
self.updateControlValue(self.AXISCONTROLMAP[event.axis],
self._sortOutAxisValue(event.value, yAxis))
#is this axis a trigger
# is this axis a trigger
if event.axis in self.TRIGGERCONTROLMAP:
#update the control value
# update the control value
self.updateControlValue(self.TRIGGERCONTROLMAP[event.axis],
self._sortOutTriggerValue(event.value))
#d pad

# d pad
elif event.type == JOYHATMOTION:
#update control value
self.updateControlValue(self.XboxControls.DPAD, event.value)
# update control value
self.updateControlValue(
self.XboxControls.DPAD, event.value)

#button pressed and unpressed
# button pressed and unpressed
elif event.type == JOYBUTTONUP or event.type == JOYBUTTONDOWN:
#is this button on our xbox controller
# is this button on our xbox controller
if event.button in self.BUTTONCONTROLMAP:
#update control value
# update control value
self.updateControlValue(self.BUTTONCONTROLMAP[event.button],
self._sortOutButtonValue(event.type))
#stops the controller

# stops the controller
def stop(self):
self.running = False

#updates a specific value in the control dictionary
# updates a specific value in the control dictionary
def updateControlValue(self, control, value):
#if the value has changed update it and call the callbacks
# if the value has changed update it and call the callbacks
if self.controlValues[control] != value:
self.controlValues[control] = value
self.doCallBacks(control, value)
#calls the call backs if necessary

# calls the call backs if necessary
def doCallBacks(self, control, value):
#call the general callback
if self.controllerCallBack != None: self.controllerCallBack(control, value)
# call the general callback
if self.controllerCallBack != None:
self.controllerCallBack(control, value)

#has a specific callback been setup?
# has a specific callback been setup?
if control in self.controlCallbacks:
self.controlCallbacks[control](value)
#used to add a specific callback to a control

# used to add a specific callback to a control
def setupControlCallback(self, control, callbackFunction):
# add callback to the dictionary
self.controlCallbacks[control] = callbackFunction

#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

# 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
value = value * self.scale
#apply the deadzone
if value < self.upperDeadzone and value > self.lowerDeadzone: value = 0
# apply the deadzone
if value < self.upperDeadzone and value > self.lowerDeadzone:
value = 0
return value

#turns the trigger value into something sensible and scales it
# turns the trigger value into something sensible and scales it
def _sortOutTriggerValue(self, value):
#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
# 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
value = value * self.scale
return value

#turns the event type (up/down) into a value
# turns the event type (up/down) into a value
def _sortOutButtonValue(self, eventType):
#if the button is down its 1, if the button is up its 0
# if the button is down its 1, if the button is up its 0
value = 1 if eventType == JOYBUTTONDOWN else 0
return value

Expand All @@ -348,13 +354,13 @@ def _sortOutButtonValue(self, eventType):

#generic call back
def controlCallBack(xboxControlId, value):
print "Control Id = {}, Value = {}".format(xboxControlId, value)
print ("Control Id = {}, Value = {}".format(xboxControlId, value))

#specific callbacks for the left thumb (X & Y)
def leftThumbX(xValue):
print "LX {}".format(xValue)
print ("LX {}".format(xValue))
def leftThumbY(yValue):
print "LY {}".format(yValue)
print ("LY {}".format(yValue))

#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)
Expand All @@ -366,17 +372,15 @@ def leftThumbY(yValue):
try:
#start the controller
xboxCont.start()
print "xbox controller running"
while True:
time.sleep(1)

print ("xbox controller running")

#Ctrl C
except KeyboardInterrupt:
print "User cancelled"
except KeyboardInterrupt:
print ("User cancelled")

#error
except:
print "Unexpected error:", sys.exc_info()[0]
print ("Unexpected error:", sys.exc_info()[0])
raise

finally:
Expand Down