Cleanup. /JL

This commit is contained in:
2025-03-21 15:13:03 +01:00
parent 4e3e91580a
commit c5061846dc
18 changed files with 4122 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
pollInterval = 0.1
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
global running
running = True
speed = 0.0
steering = 0.0
# Create some callback functions for single events
def happyButtonPressed():
print(':)')
def happyButtonReleased():
print(':(')
def exitButtonPressed():
global running
print('EXIT')
running = False
# Start the background updating
gamepad.startBackgroundUpdates()
# Register the callback functions
gamepad.addButtonPressedHandler(buttonHappy, happyButtonPressed)
gamepad.addButtonReleasedHandler(buttonHappy, happyButtonReleased)
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
# Joystick events handled in the background
try:
while running and gamepad.isConnected():
# Check if the beep button is held
if gamepad.isPressed(buttonBeep):
print('BEEP')
# Update the joystick positions
# Speed control (inverted)
speed = -gamepad.axis(joystickSpeed)
# Steering control (not inverted)
steering = gamepad.axis(joystickSteering)
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
pollInterval = 0.1
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Start the background updating
gamepad.startBackgroundUpdates()
# Joystick events handled in the background
try:
while gamepad.isConnected():
# Check for the exit button
if gamepad.beenPressed(buttonExit):
print('EXIT')
break
# Check for happy button changes
if gamepad.beenPressed(buttonHappy):
print(':)')
if gamepad.beenReleased(buttonHappy):
print(':(')
# Check if the beep button is held
if gamepad.isPressed(buttonBeep):
print('BEEP')
# Update the joystick positions
# Speed control (inverted)
speed = -gamepad.axis(joystickSpeed)
# Steering control (not inverted)
steering = gamepad.axis(joystickSteering)
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

View File

@@ -0,0 +1,428 @@
# coding: utf-8
"""
Standard gamepad mappings.
Pulled in to Gamepad.py directly.
"""
class PS3(Gamepad):
fullName = 'PlayStation 3 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SELECT',
9: 'START',
10: 'PS',
11: 'L3',
12: 'R3',
13: 'DPAD-UP',
14: 'DPAD-DOWN',
15: 'DPAD-LEFT',
16: 'DPAD-RIGHT'
}
self._setupReverseMaps()
# PS3 controller settings for older Raspbian versions
#class PS3(Gamepad):
# fullName = 'PlayStation 3 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'RIGHT-Y',
# 4: 'roll-1',
# 5: 'pitch',
# 6: 'roll-2',
# 8: 'DPAD-UP',
# 9: 'DPAD-RIGHT',
# 10: 'DPAD-DOWN',
# 11: 'DPAD-LEFT',
# 12: 'L2',
# 13: 'R2',
# 14: 'L1',
# 15: 'R1',
# 16: 'TRIANGLE',
# 17: 'CIRCLE',
# 18: 'CROSS',
# 19: 'SQUARE'
# }
# self.buttonNames = {
# 0: 'SELECT',
# 1: 'L3',
# 2: 'R3',
# 3: 'START',
# 4: 'DPAD-UP',
# 5: 'DPAD-RIGHT',
# 6: 'DPAD-DOWN',
# 7: 'DPAD-LEFT',
# 8: 'L2',
# 9: 'R2',
# 10: 'L1',
# 11: 'R1',
# 12: 'TRIANGLE',
# 13: 'CIRCLE',
# 14: 'CROSS',
# 15: 'SQUARE',
# 16: 'PS'
# }
# self._setupReverseMaps()
class PS4(Gamepad):
fullName = 'PlayStation 4 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SHARE',
9: 'OPTIONS',
10: 'PS',
11: 'L3',
12: 'R3'
}
self._setupReverseMaps()
# PS4 controller settings for older Raspbian versions
#class PS4(Gamepad):
# fullName = 'PlayStation 4 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'L2',
# 4: 'R2',
# 5: 'RIGHT-Y',
# 6: 'DPAD-X',
# 7: 'DPAD-Y'
# }
# self.buttonNames = {
# 0: 'SQUARE',
# 1: 'CROSS',
# 2: 'CIRCLE',
# 3: 'TRIANGLE',
# 4: 'L1',
# 5: 'R1',
# 6: 'L2',
# 7: 'R2',
# 8: 'SHARE',
# 9: 'OPTIONS',
# 10: 'L3',
# 11: 'R3',
# 12: 'PS',
# 13: 'PAD'
# }
# self._setupReverseMaps()
class PS5(Gamepad):
fullName = 'PlayStation 5 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'CROSS',
1: 'CIRCLE',
2: 'TRIANGLE',
3: 'SQUARE',
4: 'L1',
5: 'R1',
6: 'L2',
7: 'R2',
8: 'SHARE',
9: 'OPTIONS',
10: 'PS',
11: 'L3',
12: 'R3'
}
self._setupReverseMaps()
# PS4 controller settings for older Raspbian versions
#class PS5(Gamepad):
# fullName = 'PlayStation 5 controller'
#
# def __init__(self, joystickNumber = 0):
# Gamepad.__init__(self, joystickNumber)
# self.axisNames = {
# 0: 'LEFT-X',
# 1: 'LEFT-Y',
# 2: 'RIGHT-X',
# 3: 'L2',
# 4: 'R2',
# 5: 'RIGHT-Y',
# 6: 'DPAD-X',
# 7: 'DPAD-Y'
# }
# self.buttonNames = {
# 0: 'SQUARE',
# 1: 'CROSS',
# 2: 'CIRCLE',
# 3: 'TRIANGLE',
# 4: 'L1',
# 5: 'R1',
# 6: 'L2',
# 7: 'R2',
# 8: 'SHARE',
# 9: 'OPTIONS',
# 10: 'L3',
# 11: 'R3',
# 12: 'PS',
# 13: 'PAD'
# }
# self._setupReverseMaps()
class Xbox360(Gamepad):
fullName = 'Xbox 360 controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'LT',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'RT'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'LB',
5: 'RB',
6: 'BACK',
7: 'START',
8: 'XBOX',
9: 'LA',
10: 'RA'
}
self._setupReverseMaps()
class XboxONE(Gamepad):
fullName = 'Xbox ONE controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LAS -X', #Left Analog Stick Left/Right
1: 'LAS -Y', #Left Analog Stick Up/Down
2: 'RAS -X', #Right Analog Stick Left/Right
3: 'RAS -Y', #Right Analog Stick Up/Down
4: 'RT', #Right Trigger
5: 'LT', #Left Trigger
6: 'DPAD -X', #D-Pad Left/Right
7: 'DPAD -Y' #D-Pad Up/Down
}
self.buttonNames = {
0: 'A', #A Button
1: 'B', #B Button
3: 'X', #X Button
4: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
11: 'START', #Hamburger Button
12: 'HOME', #XBOX Button
13: 'LASB', #Left Analog Stick button
14: 'RASB' #Right Analog Stick button
}
self._setupReverseMaps()
class Steam(Gamepad):
fullName = 'Steam controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'AS -X', #Analog Stick Left/Right
1: 'AS -Y', #Analog Stick Up/Down
2: 'RTP -X', #Right Track Pad Left/Right
3: 'RTP -Y', #Right Track Pad Up/Down
4: 'LTP -Y', #Left Track Pad Up/Down
5: 'LTP -X', #Left Track Pad Left/Right
6: 'RTA', #Right Trigger Axis
7: 'LTA' #Left Trigger Axis
}
self.buttonNames = {
0: 'LPTBUTTON', #Left TrackPad button
1: 'RTPBUTTON', #Right TrackPad button
2: 'A', #A Button
3: 'B', #B Button
4: 'X', #X Button
5: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
8: 'LT', #Left Trigger
9: 'RT', #Right Trigger
10: 'SELECT', #Select Button <
11: 'START', #Start button >
12: 'HOME', #Steam Button
13: 'STICKBUTTON', #Analog Stick button
15: 'LG', #Left Grip
16: 'RG', #Right Grip
17: 'LTP -DUP', #Left TrackPad D-PAD Up
18: 'LTP -DDOWN', #Left TrackPad D-PAD Down
19: 'LTP -DLEFT', #Left TrackPad D-PAD Left
20: 'LTP -DRIGHT', #Left TrackPad D-PAD Right
}
self._setupReverseMaps()
class MMP1251(Gamepad):
fullName = "ModMyPi Raspberry Pi Wireless USB Gamepad"
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'L2',
3: 'RIGHT-X',
4: 'RIGHT-Y',
5: 'R2',
6: 'DPAD-X',
7: 'DPAD-Y'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'L1',
5: 'R1',
6: 'SELECT',
7: 'START',
8: 'HOME',
9: 'L3',
10: 'R3'
}
self._setupReverseMaps()
class GameHat(Gamepad):
fullName = "WaveShare rpi GameHat "
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y'
}
self.buttonNames = {
0: 'A',
1: 'B',
2: 'X',
3: 'Y',
4: 'TR',
5: 'TL',
6: 'SELECT',
7: 'START'
}
self._setupReverseMaps()
class PG9099(Gamepad):
fullName = 'ipega PG-9099 Bluetooth Controller'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LAS -X', #Left Analog Stick Left/Right
1: 'LAS -Y', #Left Analog Stick Up/Down
2: 'RAS -X', #Right Analog Stick Left/Right
3: 'RAS -Y', #Right Analog Stick Up/Down
4: 'RT', #Right Trigger
5: 'LT', #Left Trigger
6: 'DPAD -X', #D-Pad Left/Right
7: 'DPAD -Y' #D-Pad Up/Down
}
self.buttonNames = {
0: 'A', #A Button
1: 'B', #B Button
3: 'X', #X Button
4: 'Y', #Y Button
6: 'LB', #Left Bumper
7: 'RB', #Right Bumper
10: 'SELECT', #Select Button
11: 'START', #Hamburger Button
13: 'LASB', #Left Analog Stick button
14: 'RASB' #Right Analog Stick button
}
self._setupReverseMaps()
class example(Gamepad):
# This class must have self.axisNames with a map
# of numbers to capitalised strings. Follow the
# conventions the other classes use for generic
# axes, make up your own names for axes unique
# to your device.
# self.buttonNames needs the same treatment.
# Use python Gamepad.py to get the event mappings.
fullName = 'Enter the human readable name of the device here'
def __init__(self, joystickNumber = 0):
Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'AXIS0',
1: 'AXIS1',
2: 'AXIS2'
}
self.buttonNames = {
0: 'BUTTON0',
1: 'BUTTON1',
2: 'BUTTON2'
}
self._setupReverseMaps()

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Make our own custom gamepad
# The numbers can be figured out by running the Gamepad script:
# ./Gamepad.py
# Press ENTER without typing a name to get raw numbers for each
# button press or axis movement, press CTRL+C when done
class CustomGamepad(Gamepad.Gamepad):
def __init__(self, joystickNumber = 0):
Gamepad.Gamepad.__init__(self, joystickNumber)
self.axisNames = {
0: 'LEFT-X',
1: 'LEFT-Y',
2: 'RIGHT-Y',
3: 'RIGHT-X',
4: 'DPAD-X',
5: 'DPAD-Y'
}
self.buttonNames = {
0: '1',
1: '2',
2: '3',
3: '4',
4: 'L1',
5: 'L2',
6: 'R1',
7: 'R2',
8: 'SELECT',
9: 'START',
10: 'L3',
11: 'R3'
}
self._setupReverseMaps()
# Gamepad settings
gamepadType = CustomGamepad
buttonHappy = '3'
buttonBeep = 'L3'
buttonExit = 'START'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Handle joystick updates one at a time
while gamepad.isConnected():
# Wait for the next event
eventType, control, value = gamepad.getNextEvent()
# Determine the type
if eventType == 'BUTTON':
# Button changed
if control == buttonHappy:
# Happy button (event on press and release)
if value:
print(':)')
else:
print(':(')
elif control == buttonBeep:
# Beep button (event on press)
if value:
print('BEEP')
elif control == buttonExit:
# Exit button (event on press)
if value:
print('EXIT')
break
elif eventType == 'AXIS':
# Joystick changed
if control == joystickSpeed:
# Speed control (inverted)
speed = -value
elif control == joystickSteering:
# Steering control (not inverted)
steering = value
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 260 KiB

120
controls/Gamepad/EventExample.py Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS5
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
dpadX = 'DPAD-X'
dpadY = 'DPAD-Y'
pollInterval = 0.2
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
global running
global beepOn
global speed
global steering
global direction
global dirChange
running = True
beepOn = False
speed = 0.0
steering = 0.0
direction = ""
dirChange = False
# Create some callback functions
def happyButtonPressed():
print(':)')
def happyButtonReleased():
print(':(')
def beepButtonChanged(isPressed):
global beepOn
beepOn = isPressed
def exitButtonPressed():
global running
print('EXIT')
running = False
def speedAxisMoved(position):
global speed
speed = -position # Inverted
def steeringAxisMoved(position):
global steering
steering = position # Non-inverted
def dpadXMoved(dir):
global direction
global dirChange
print("X direction: ", dir)
match dir:
case -1.0:
direction = 'LEFT'
dirChange = True
case 1.0:
direction = 'RIGHT'
dirChange = True
def dpadYMoved(dir):
global direction
global dirChange
print("Y direction: ", dir)
match dir:
case -1.0:
direction = 'UP'
dirChange = True
case 1.0:
direction = 'DOWN'
dirChange = True
# Start the background updating
gamepad.startBackgroundUpdates()
# Register the callback functions
gamepad.addButtonPressedHandler(buttonHappy, happyButtonPressed)
gamepad.addButtonReleasedHandler(buttonHappy, happyButtonReleased)
gamepad.addButtonChangedHandler(buttonBeep, beepButtonChanged)
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
gamepad.addAxisMovedHandler(joystickSpeed, speedAxisMoved)
gamepad.addAxisMovedHandler(joystickSteering, steeringAxisMoved)
gamepad.addAxisMovedHandler(dpadX, dpadXMoved)
gamepad.addAxisMovedHandler(dpadY, dpadYMoved)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Show the current speed and steering
# print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))
# Display the beep if held
if beepOn:
print('BEEP')
if dirChange:
print(direction)
dirChange = False
# Sleep for our polling interval
time.sleep(pollInterval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()

617
controls/Gamepad/Gamepad.py Executable file
View File

@@ -0,0 +1,617 @@
#!/usr/bin/env python
# coding: utf-8
"""
This module is designed to read inputs from a gamepad or joystick.
See Controllers.py the names which can be used with specific gamepad types.
For basic use see the following examples:
AsyncExample.py - Updates read in the background.
EventExample.py - Updates passed to callback functions.
PollingExample.py - Reading updates one at a time.
AsyncAndEventExample.py - Mixing callbacks and background updates.
"""
import os
import sys
import struct
import time
import threading
import inspect
def available(joystickNumber = 0):
"""Check if a joystick is connected and ready to use."""
joystickPath = '/dev/input/js' + str(joystickNumber)
return os.path.exists(joystickPath)
class Gamepad:
EVENT_CODE_BUTTON = 0x01
EVENT_CODE_AXIS = 0x02
EVENT_CODE_INIT_BUTTON = 0x80 | EVENT_CODE_BUTTON
EVENT_CODE_INIT_AXIS = 0x80 | EVENT_CODE_AXIS
MIN_AXIS = -32767.0
MAX_AXIS = +32767.0
EVENT_BUTTON = 'BUTTON'
EVENT_AXIS = 'AXIS'
fullName = 'Generic (numbers only)'
class UpdateThread(threading.Thread):
"""Thread used to continually run the updateState function on a Gamepad in the background
One of these is created by the Gamepad startBackgroundUpdates function and closed by stopBackgroundUpdates"""
def __init__(self, gamepad):
threading.Thread.__init__(self)
if isinstance(gamepad, Gamepad):
self.gamepad = gamepad
else:
raise ValueError('Gamepad update thread was not created with a valid Gamepad object')
self.running = True
def run(self):
try:
while self.running:
self.gamepad.updateState()
self.gamepad = None
except:
self.running = False
self.gamepad = None
raise
def __init__(self, joystickNumber = 0):
self.joystickNumber = str(joystickNumber)
self.joystickPath = '/dev/input/js' + self.joystickNumber
retryCount = 5
while True:
try:
self.joystickFile = open(self.joystickPath, 'rb')
break
except IOError as e:
retryCount -= 1
if retryCount > 0:
time.sleep(0.5)
else:
raise IOError('Could not open gamepad %s: %s' % (self.joystickNumber, str(e)))
self.eventSize = struct.calcsize('IhBB')
self.pressedMap = {}
self.wasPressedMap = {}
self.wasReleasedMap = {}
self.axisMap = {}
self.buttonNames = {}
self.buttonIndex = {}
self.axisNames = {}
self.axisIndex = {}
self.lastTimestamp = 0
self.updateThread = None
self.connected = True
self.pressedEventMap = {}
self.releasedEventMap = {}
self.changedEventMap = {}
self.movedEventMap = {}
def __del__(self):
try:
self.joystickFile.close()
except AttributeError:
pass
def _setupReverseMaps(self):
for index in self.buttonNames:
self.buttonIndex[self.buttonNames[index]] = index
for index in self.axisNames:
self.axisIndex[self.axisNames[index]] = index
def _getNextEventRaw(self):
"""Returns the next raw event from the gamepad.
The return format is:
timestamp (ms), value, event type code, axis / button number
Throws an IOError if the gamepad is disconnected"""
if self.connected:
try:
rawEvent = self.joystickFile.read(self.eventSize)
except IOError as e:
self.connected = False
raise IOError('Gamepad %s disconnected: %s' % (self.joystickNumber, str(e)))
if rawEvent is None:
self.connected = False
raise IOError('Gamepad %s disconnected' % self.joystickNumber)
else:
return struct.unpack('IhBB', rawEvent)
else:
raise IOError('Gamepad has been disconnected')
def _rawEventToDescription(self, event):
"""Decodes the raw event from getNextEventRaw into a formatted string."""
timestamp, value, eventType, index = event
if eventType == Gamepad.EVENT_CODE_BUTTON:
if index in self.buttonNames:
button = self.buttonNames[index]
else:
button = str(index)
if value == 0:
return '%010u: Button %s released' % (timestamp, button)
elif value == 1:
return '%010u: button %s pressed' % (timestamp, button)
else:
return '%010u: button %s state %i' % (timestamp, button, value)
elif eventType == Gamepad.EVENT_CODE_AXIS:
if index in self.axisNames:
axis = self.axisNames[index]
else:
axis = str(index)
position = value / Gamepad.MAX_AXIS
return '%010u: Axis %s at %+06.1f %%' % (timestamp, axis, position * 100)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
if index in self.buttonNames:
button = self.buttonNames[index]
else:
button = str(index)
if value == 0:
return '%010u: Button %s initially released' % (timestamp, button)
elif value == 1:
return '%010u: button %s initially pressed' % (timestamp, button)
else:
return '%010u: button %s initially state %i' % (timestamp, button, value)
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
if index in self.axisNames:
axis = self.axisNames[index]
else:
axis = str(index)
position = value / Gamepad.MAX_AXIS
return '%010u: Axis %s initially at %+06.1f %%' % (timestamp, axis, position * 100)
else:
return '%010u: Unknown event %u, Index %u, Value %i' % (timestamp, eventType, index, value)
def getNextEvent(self, skipInit = True):
"""Returns the next event from the gamepad.
The return format is:
event name, entity name, value
For button events the event name is BUTTON and value is either True or False.
For axis events the event name is AXIS and value is between -1.0 and +1.0.
Names are string based when found in the button / axis decode map.
When not available the raw index is returned as an integer instead.
After each call the internal state used by getPressed and getAxis is updated.
Throws an IOError if the gamepad is disconnected"""
self.lastTimestamp, value, eventType, index = self._getNextEventRaw()
skip = False
eventName = None
entityName = None
finalValue = None
if eventType == Gamepad.EVENT_CODE_BUTTON:
eventName = Gamepad.EVENT_BUTTON
if index in self.buttonNames:
entityName = self.buttonNames[index]
else:
entityName = index
if value == 0:
finalValue = False
self.wasReleasedMap[index] = True
for callback in self.releasedEventMap[index]:
callback()
else:
finalValue = True
self.wasPressedMap[index] = True
for callback in self.pressedEventMap[index]:
callback()
self.pressedMap[index] = finalValue
for callback in self.changedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_AXIS:
eventName = Gamepad.EVENT_AXIS
if index in self.axisNames:
entityName = self.axisNames[index]
else:
entityName = index
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
for callback in self.movedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
eventName = Gamepad.EVENT_BUTTON
if index in self.buttonNames:
entityName = self.buttonNames[index]
else:
entityName = index
if value == 0:
finalValue = False
else:
finalValue = True
self.pressedMap[index] = finalValue
self.wasPressedMap[index] = False
self.wasReleasedMap[index] = False
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
skip = skipInit
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
eventName = Gamepad.EVENT_AXIS
if index in self.axisNames:
entityName = self.axisNames[index]
else:
entityName = index
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
self.movedEventMap[index] = []
skip = skipInit
else:
skip = True
if skip:
return self.getNextEvent()
else:
return eventName, entityName, finalValue
def updateState(self):
"""Updates the internal button and axis states with the next pending event.
This call waits for a new event if there are not any waiting to be processed."""
self.lastTimestamp, value, eventType, index = self._getNextEventRaw()
if eventType == Gamepad.EVENT_CODE_BUTTON:
if value == 0:
finalValue = False
self.wasReleasedMap[index] = True
for callback in self.releasedEventMap[index]:
callback()
else:
finalValue = True
self.wasPressedMap[index] = True
for callback in self.pressedEventMap[index]:
callback()
self.pressedMap[index] = finalValue
for callback in self.changedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_AXIS:
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
for callback in self.movedEventMap[index]:
callback(finalValue)
elif eventType == Gamepad.EVENT_CODE_INIT_BUTTON:
if value == 0:
finalValue = False
else:
finalValue = True
self.pressedMap[index] = finalValue
self.wasPressedMap[index] = False
self.wasReleasedMap[index] = False
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
elif eventType == Gamepad.EVENT_CODE_INIT_AXIS:
finalValue = value / Gamepad.MAX_AXIS
self.axisMap[index] = finalValue
self.movedEventMap[index] = []
def startBackgroundUpdates(self, waitForReady = True):
"""Starts a background thread which keeps the gamepad state updated automatically.
This allows for asynchronous gamepad updates and event callback code.
Do not use with getNextEvent"""
if self.updateThread is not None:
if self.updateThread.running:
raise RuntimeError('Called startBackgroundUpdates when the update thread is already running')
self.updateThread = Gamepad.UpdateThread(self)
self.updateThread.start()
if waitForReady:
while not self.isReady() and self.connected:
time.sleep(1.0)
def stopBackgroundUpdates(self):
"""Stops the background thread which keeps the gamepad state updated automatically.
This may be called even if the background thread was never started.
The thread will stop on the next event after this call was made."""
if self.updateThread is not None:
self.updateThread.running = False
def isReady(self):
"""Used with updateState to indicate that the gamepad is now ready for use.
This is usually after the first button press or stick movement."""
return len(self.axisMap) + len(self.pressedMap) > 1
def waitReady(self):
"""Convenience function which waits until the isReady call is True."""
self.updateState()
while not self.isReady() and self.connected:
time.sleep(1.0)
self.updateState()
def isPressed(self, buttonName):
"""Returns the last observed state of a gamepad button specified by name or index.
True if pressed, False if not pressed.
Status is updated by getNextEvent calls.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
return self.pressedMap[buttonIndex]
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def beenPressed(self, buttonName):
"""Returns True if the button specified by name or index has been pressed since the last beenPressed call.
Used in conjunction with updateState.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if self.wasPressedMap[buttonIndex]:
self.wasPressedMap[buttonIndex] = False
return True
else:
return False
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def beenReleased(self, buttonName):
"""Returns True if the button specified by name or index has been released since the last beenReleased call.
Used in conjunction with updateState.
Throws ValueError if the button name or index cannot be found."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if self.wasReleasedMap[buttonIndex]:
self.wasReleasedMap[buttonIndex] = False
return True
else:
return False
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def axis(self, axisName):
"""Returns the last observed state of a gamepad axis specified by name or index.
Throws a ValueError if the axis index is unavailable.
Status is updated by getNextEvent calls.
Throws ValueError if the button name or index cannot be found."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
return self.axisMap[axisIndex]
except KeyError:
raise ValueError('Axis %i was not found' % axisIndex)
except ValueError:
raise ValueError('Axis name %s was not found' % axisName)
def availableButtonNames(self):
"""Returns a list of available button names for this gamepad.
An empty list means that no button mapping has been provided."""
return self.buttonIndex.keys()
def availableAxisNames(self):
"""Returns a list of available axis names for this gamepad.
An empty list means that no axis mapping has been provided."""
return self.axisIndex.keys()
def isConnected(self):
"""Returns True until reading from the device fails."""
return self.connected
def addButtonPressedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index is pressed.
This callback gets no parameters passed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.pressedEventMap[buttonIndex]:
self.pressedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonPressedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index is pressed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.pressedEventMap[buttonIndex]:
self.pressedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addButtonReleasedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index is released.
This callback gets no parameters passed."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.releasedEventMap[buttonIndex]:
self.releasedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonReleasedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index is released."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.releasedEventMap[buttonIndex]:
self.releasedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addButtonChangedHandler(self, buttonName, callback):
"""Adds a callback for when a specific button specified by name or index changes.
This callback gets a boolean for the button pressed state."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback not in self.changedEventMap[buttonIndex]:
self.changedEventMap[buttonIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def removeButtonChangedHandler(self, buttonName, callback):
"""Removes a callback for when a specific button specified by name or index changes."""
try:
if buttonName in self.buttonIndex:
buttonIndex = self.buttonIndex[buttonName]
else:
buttonIndex = int(buttonName)
if callback in self.changedEventMap[buttonIndex]:
self.changedEventMap[buttonIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % buttonIndex)
except ValueError:
raise ValueError('Button name %s was not found' % buttonName)
def addAxisMovedHandler(self, axisName, callback):
"""Adds a callback for when a specific axis specified by name or index changes.
This callback gets the updated position of the axis."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
if callback not in self.movedEventMap[axisIndex]:
self.movedEventMap[axisIndex].append(callback)
except KeyError:
raise ValueError('Button %i was not found' % axisIndex)
except ValueError:
raise ValueError('Button name %s was not found' % axisName)
def removeAxisMovedHandler(self, axisName, callback):
"""Removes a callback for when a specific axis specified by name or index changes."""
try:
if axisName in self.axisIndex:
axisIndex = self.axisIndex[axisName]
else:
axisIndex = int(axisName)
if callback in self.movedEventMap[axisIndex]:
self.movedEventMap[axisIndex].remove(callback)
except KeyError:
raise ValueError('Button %i was not found' % axisIndex)
except ValueError:
raise ValueError('Button name %s was not found' % axisName)
def removeAllEventHandlers(self):
"""Removes all event handlers from all axes and buttons."""
for index in self.pressedEventMap.keys():
self.pressedEventMap[index] = []
self.releasedEventMap[index] = []
self.changedEventMap[index] = []
self.movedEventMap[index] = []
def disconnect(self):
"""Cleanly disconnect and remove any threads and event handlers."""
self.connected = False
self.removeAllEventHandlers()
self.stopBackgroundUpdates()
del self.joystickFile
###########################
# Import gamepad mappings #
###########################
scriptDir = os.path.dirname(os.path.realpath(__file__))
controllerScript = os.path.join(scriptDir, "Controllers.py")
exec(open(controllerScript).read())
# Generate a list of available gamepad types
moduleDict = globals()
classList = [moduleDict[a] for a in moduleDict.keys() if inspect.isclass(moduleDict[a])]
controllerDict = {}
deviceNames = []
for gamepad in classList:
controllerDict[gamepad.__name__.upper()] = gamepad
deviceNames.append(gamepad.__name__)
deviceNames.sort()
##################################################################
# When this script is run it provides testing code for a gamepad #
##################################################################
if __name__ == "__main__":
# Python 2/3 compatibility
try:
input = raw_input
except NameError:
pass
# ANSI colour code sequences
GREEN = '\033[0;32m'
CYAN = '\033[0;36m'
BLUE = '\033[1;34m'
RESET = '\033[0m'
# Ask for the gamepad to use
print('Gamepad axis and button events...')
print('Press CTRL+C to exit')
print('')
print('Available device names:')
formatString = ' ' + GREEN + '%s' + RESET + ' - ' + CYAN + '%s' + RESET
for device in deviceNames:
print(formatString % (device, controllerDict[device.upper()].fullName))
print('')
print('What device name are you using (leave blank if not in the list)')
device = input('? ' + GREEN).strip().upper()
print(RESET)
# Wait for a connection
if not available():
print('Please connect your gamepad...')
while not available():
time.sleep(1.0)
print('Gamepad connected')
# Pick the correct class
if device in controllerDict:
print(controllerDict[device].fullName)
gamepad = controllerDict[device]()
elif device == '':
print('Unspecified gamepad')
print('')
gamepad = Gamepad()
else:
print('Unknown gamepad')
print('')
sys.exit()
# Display the event messages as they arrive
while True:
eventType, index, value = gamepad.getNextEvent()
print(BLUE + eventType + RESET + ',\t ' +
GREEN + str(index) + RESET + ',\t' +
CYAN + str(value) + RESET)
time.sleep(1.0)

21
controls/Gamepad/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Arron Churchill
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.

40
controls/Gamepad/ListNames.py Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
print('Gamepad connected')
# Pick the gamepad type
#gamepad = Gamepad.Gamepad() #Generic unnamed controls
#gamepad = Gamepad.PS3()
gamepad = Gamepad.PS4()
# Show the selected gamepad type
print('Gamepad type: ' + gamepad.__class__.__name__)
# Display axis names
axisNames = gamepad.availableAxisNames()
if axisNames:
print('Available axis names:')
for axis in axisNames:
print(' ' + str(axis))
else:
print('No axis name mappings configured')
print('')
# Display button names
buttonNames = gamepad.availableButtonNames()
if buttonNames:
print('Available button names:')
for button in buttonNames:
print(' ' + str(button))
else:
print('No button name mappings configured')

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python
# coding: utf-8
# Load the gamepad and time libraries
import Gamepad
import time
# Gamepad settings
gamepadType = Gamepad.PS4
buttonHappy = 'CROSS'
buttonBeep = 'CIRCLE'
buttonExit = 'PS'
joystickSpeed = 'LEFT-Y'
joystickSteering = 'RIGHT-X'
# Wait for a connection
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(1.0)
gamepad = gamepadType()
print('Gamepad connected')
# Set some initial state
speed = 0.0
steering = 0.0
# Handle joystick updates one at a time
while gamepad.isConnected():
# Wait for the next event
eventType, control, value = gamepad.getNextEvent()
# Determine the type
if eventType == 'BUTTON':
# Button changed
if control == buttonHappy:
# Happy button (event on press and release)
if value:
print(':)')
else:
print(':(')
elif control == buttonBeep:
# Beep button (event on press)
if value:
print('BEEP')
elif control == buttonExit:
# Exit button (event on press)
if value:
print('EXIT')
break
elif eventType == 'AXIS':
# Joystick changed
if control == joystickSpeed:
# Speed control (inverted)
speed = -value
elif control == joystickSteering:
# Steering control (not inverted)
steering = value
print('%+.1f %% speed, %+.1f %% steering' % (speed * 100, steering * 100))

168
controls/Gamepad/README.md Normal file
View File

@@ -0,0 +1,168 @@
# PiBorg Gamepad Library
![Polling flowchart](Diagrams/gamepad-logo.svg)
The Gamepad library provides a simple way of getting inputs from joysticks, gamepads, and other game controllers.
Both buttons and axes / joysticks can be referenced by names as well as their raw index numbers. These can be easily customised and new controllers can easily be added as well.
It is designed and tested for the Raspberry Pi, but it should work with an Linux install and any device which shows up as a ```/dev/input/js?``` device.
Gamepad only uses imports which are part of the standard Python install and works with both Python 2 and 3.
Multiple controllers are also supported, just create more than one ```Gamepad``` instance with different joystick IDs.
See our other projects on GitHub [here](https://github.com/piborg) :)
# Installing the Library
```
cd ~
git clone https://github.com/piborg/Gamepad
```
That's it. The library does not need installing, it just needs to be downloaded.
# The Gamepad Library
The library provides three basic modes for getting updates from your controller:
1. Polling - we ask the library for the next controller update one at a time.
2. Asynchronous - the controller state is updated in the background.
3. Event - callbacks are made when the controller state changes.
See the examples further down for an explanation of how each mode is used.
The library itself is contained in just two scripts:
## ```Gamepad.py```
The main library, this script contains all of the code which reads input from the controller. It contains the ```Gamepad``` class which has all of the logic used to decode the joystick events and store the current state.
It works with both the raw axis / joystick and button numbers and easy to read names. It also contains the threading code needed to handle the background updating used in the asynchronous and event modes.
This script can be run directly using ```./Gamepad.py``` to check your controller mappings are correct or work out the mapping numbers for your own controller.
## ```Controllers.py```
This script contains the layouts for the different controllers. Each controller is represented by its own class inheriting from the main ```Gamepad``` class.
If the mapping is not right for you the layout for both the axis / joystick names and the button names by editing these classes. Adding your own controller is also simple, just make your own class based on the example at the bottom :)
Any button or axis without a name can still be used by the raw number if needed. This also means the ```Gamepad``` class can be used directly if you are only using the raw numbers.
This script is not run directly, instead it is read by ```Gamepad.py``` so that all of the devices are available when Gamepad is imported.
# Examples
## Polling mode - ```PollingExample.py```
The polling mode is probably the simplest and uses the Gamepad library to decode controller events one at a time.
![Polling flowchart](Diagrams/Polling.svg)
It works by repeatedly calling the ```getNextEvent()``` function to get the next update from the controller in the order they occurred. Each call returns three things:
1. The event type. This is either ```'BUTTON'``` for button presses or ```'AXIS'``` for axis / joystick movements.
2. The control which changed. This is a string with the name if available, or the index number if no name was available.
3. The new value for the control. For buttons this is ```True``` or ```False```, for axis / joystick movement it is a position between -1.0 and +1.0.
For example if the circle button on your controller was just pressed you would get ```'BUTTON', 'CIRCLE', True``` as the result.
Polling mode cannot be used at the same time as the asynchronous or event modes as they read the controller events for you.
## Asynchronous mode - ```AsyncExample.py```
Asynchronous mode works by reading the controller events in a background thread and updating the objects state to match the controller.
![Asynchronous flowchart](Diagrams/Asynchronous.svg)
It is started by calling the ```startBackgroundUpdates()``` function and should be stopped at the end of your script by calling the ```disconnect()``` function.
The current controller state can be queried using the following functions:
* ```isConnected()``` - check if the controller is still connected.
* ```isPressed(X)``` - check if a button is currently pressed.
* ```beenPressed(X)``` - see if a button was pressed since the last check. Only returns ```True``` once per press.
* ```beenReleased(X)``` - see if a button was released since the last check. Only returns ```True``` once per release.
* ```axis(X)``` - read the latest axis / joystick position. This is a float number between -1.0 and +1.0.
In all cases ```X``` can either be the string based name or the raw index number (e.g. ```'CIRCLE'``` or ```1```).
Asynchronous mode cannot be used at the same time as the polling mode as it reads the controller events for you, but it can be used with event mode.
## Event mode - ```EventExample.py```
Event mode works by reading the controller events in a background thread and calling functions in your script when changes occur.
![Event flowchart](Diagrams/Event.svg)
It is started by calling the ```startBackgroundUpdates()``` function and should be stopped at the end of your script by calling the ```disconnect()``` function. The ```isConnected()``` function will return ```False``` if the controller is disconnected while running.
Once started you can register your functions to controller events using these calls:
* ```addButtonPressedHandler(X, F)``` - called when a button is pressed, no values passed.
* ```addButtonReleasedHandler(X, F)``` - called when a button is released, no values passed.
* ```addButtonChangedHandler(X, F)``` - called when a button changes state, boolean passed with ```True``` for pressed or ```False``` for released.
* ```addAxisMovedHandler(X, F)``` - called when an axis / joystick is moved, a float number between -1.0 and +1.0 is passed.
In all cases ```X``` can either be the string based name or the raw index number (e.g. ```'CIRCLE'``` or ```1```). ```F``` is the function which gets called when the event occurs.
The same function can be registered with multiple events. You can also register multiple functions with the same event.
You can also remove an already registered event using these calls if needed:
* ```removeButtonPressedHandler(X, F)``` - removes a callback added by ```addButtonPressedHandler```.
* ```removeButtonReleasedHandler(X, F)``` - removes a callback added by ```addButtonReleasedHandler```.
* ```removeButtonChangedHandler(X, F)``` - removes a callback added by ```addButtonChangedHandler```.
* ```removeAxisMovedHandler(X, F)``` - removes a callback added by ```addAxisMovedHandler```.
* ```removeAllEventHandlers()``` - removes all added callbacks for this controller.
Event mode cannot be used at the same time as the polling mode as it reads the controller events for you, but it can be used with asynchronous mode.
## Asynchronous and event mode - ```AsyncAndEventExample.py```
This is not really a mode, but an example of how the asynchronous and event modes can be used at the same time. This is generally my preferred option as event mode is often easier to understand for button presses and asynchronous mode works well with axis / joystick movements.
![Asynchronous and event flowchart](Diagrams/Asynchronous-and-event.svg)
The example script here is really a hybrid between the ```AsyncExample.py``` and ```EventExample.py``` examples. Button callbacks are registered in the event style, then the loop only checks one button and the joystick positions.
In this style you are free to mix and match what you see as events and what you read the state of directly.
## Getting the available names - ```ListNames.py```
This example is just a helpful utility to print out all of the axis and button names for a controller type. You can change the controller type by looking for this line:
```
gamepad = Gamepad.PS4()
```
and changing ```PS4``` to any available device name.
The list of device names is shown when you run ```./Gamepad.py``` directly.
## Custom controller in your own script - ```CustomGamepadExample.py```
This example shows how you can create a controller mapping in your own script without changing ```Controllers.py```. This can be useful if you need to use different names in just one script, or if you want to keep all of your changes in your own code.
In this case you make your own class inheriting from ```Gamepad.Gamepad``` in the same way as they are written in ```Controllers.py```. You do not have to set the ```fullName``` value.
## RockyBorg example - ```rockyJoy.py```
Here we have an actual use of the Gamepad library, controlling a [RockyBorg](https://www.piborg.org/rockyborg-white) robot :)
It works exactly like the old Pygame version of the ```rbJoystick.py``` example script, but with the addition of a button to end the script.
The controller and button layout is all specified towards the top of the script and the standard [RockyBorg library](https://github.com/piborg/rockyborg) is used to control the motors and servo.
## MonsterBorg example - ```monsterJoy.py```
Here we have another real-world use of the Gamepad library, controlling a [MonsterBorg](https://www.piborg.org/monsterborg) robot :)
This provides the same control scheme we usually use for our robots, plus buttons to toggle the LEDs and end the script.
The controller and button layout is all specified towards the top of the script and the standard [ThunderBorg](https://www.piborg.org/thunderborg) library is used to control the motors.
# Using Gamepad in your own project
If you are using ```Gamepad``` in your own script it will need access to both the ```Gamepad.py``` and ```Controllers.py``` scripts. This can be done in a few ways:
1. Write your script in the Gamepad directory.
2. Copy the two scripts into your project's directory.
3. Add the Gamepad directory to your PYTHONPATH environment variable.
4. Add these lines to your script before importing the library: ```import sys``` and ```sys.path.insert(0, "/home/pi/Gamepad")```, where ```/home/pi/Gamepad``` is the Gamepad directory.
# Troubleshooting
For troubleshooting and further help please post questions on our [forum](http://forum.piborg.org/forum).
# Reporting Issues
If you find a bug with the RockyBorg code, please feel free to report it as an issue. When reporting issues with this library please fill in the issue report with as much information as you can to help us help you.
Please supply as much information as you can so we can replicate the bug locally. :)

166
controls/Gamepad/monsterJoy.py Executable file
View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python
# coding: utf-8
# Load the libraries
import sys
import Gamepad
import time
sys.path.insert(0, "/home/pi/thunderborg")
import ThunderBorg
# Settings for the gamepad
gamepadType = Gamepad.PS4 # Class for the gamepad (e.g. Gamepad.PS3)
joystickSpeed = 'LEFT-Y' # Joystick axis to read for up / down position
joystickSpeedInverted = True # Set this to True if up and down appear to be swapped
joystickSteering = 'RIGHT-X' # Joystick axis to read for left / right position
joystickSteeringInverted = False # Set this to True if left and right appear to be swapped
buttonSlow = 'L2' # Joystick button for driving slowly whilst held
slowFactor = 0.5 # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed
buttonFastTurn = 'R2' # Joystick button for turning fast
buttonExit = 'PS' # Joystick button to end the program
buttonFullBeamToggle = 'CROSS' # Joystick button to toggle the LEDs between battery mode and fully white
interval = 0.05 # Time between motor updates in seconds, smaller responds faster but uses more processor time
# Power settings
voltageIn = 1.2 * 10 # Total battery voltage to the ThunderBorg
voltageOut = 12.0 * 0.95 # Maximum motor voltage, we limit it to 95% to allow the RPi to get uninterrupted power
# Setup the power limits
if voltageOut > voltageIn:
maxPower = 1.0
else:
maxPower = voltageOut / float(voltageIn)
# Setup the ThunderBorg
TB = ThunderBorg.ThunderBorg()
#TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address
TB.Init()
if not TB.foundChip:
boards = ThunderBorg.ScanForThunderBorg()
if len(boards) == 0:
print('No ThunderBorg found, check you are attached :)')
else:
print('No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress))
for board in boards:
print(' %02X (%d)' % (board, board))
print('If you need to change the I²C address change the setup line so it is correct, e.g.')
print('TB.i2cAddress = 0x%02X' % (boards[0]))
sys.exit()
# Ensure the communications failsafe has been enabled!
failsafe = False
for i in range(5):
TB.SetCommsFailsafe(True)
failsafe = TB.GetCommsFailsafe()
if failsafe:
break
if not failsafe:
print('Board %02X failed to report in failsafe mode!' % (TB.i2cAddress))
sys.exit()
# Show battery monitoring settings
battMin, battMax = TB.GetBatteryMonitoringLimits()
battCurrent = TB.GetBatteryReading()
print('Battery monitoring settings:')
print(' Minimum (red) %02.2f V' % (battMin))
print(' Half-way (yellow) %02.2f V' % ((battMin + battMax) / 2))
print(' Maximum (green) %02.2f V' % (battMax))
print('')
print(' Current voltage %02.2f V' % (battCurrent))
print('')
# Setup the state shared with callbacks
global running
global fullBeam
running = True
fullBeam = False
# Create the callback functions
def exitButtonPressed():
global running
print('EXIT')
running = False
def fullBeamButtonPressed():
global fullBeam
fullBeam = not fullBeam
if fullBeam:
TB.SetLedShowBattery(False)
TB.SetLeds(1, 1, 1)
else:
TB.SetLedShowBattery(True)
# Wait for a connection
TB.MotorsOff()
TB.SetLedShowBattery(False)
TB.SetLeds(0, 0, 1)
waitingToggle = True
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(interval * 4)
waitingToggle = not waitingToggle
if waitingToggle:
TB.SetLeds(0, 0, 1)
else:
TB.SetLeds(0, 0, 0)
print('Gamepad connected')
gamepad = gamepadType()
# Start the background updating
gamepad.startBackgroundUpdates()
TB.SetLedShowBattery(True)
# Register the callback functions
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
gamepad.addButtonPressedHandler(buttonFullBeamToggle, fullBeamButtonPressed)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Read the latest speed and steering
if joystickSpeedInverted:
speed = -gamepad.axis(joystickSpeed)
else:
speed = +gamepad.axis(joystickSpeed)
if joystickSteeringInverted:
steering = -gamepad.axis(joystickSteering)
else:
steering = +gamepad.axis(joystickSteering)
# Work out the adjusted speed and steering
if gamepad.isPressed(buttonSlow):
finalSpeed = speed * slowFactor
else:
finalSpeed = speed
if gamepad.isPressed(buttonFastTurn):
finalSteering = steering * 2.0
else:
finalSteering = steering
# Determine the drive power levels
driveLeft = finalSpeed
driveRight = finalSpeed
if finalSteering < -0.05:
# Turning left
driveLeft *= 1.0 + finalSteering
elif finalSteering > +0.05:
# Turning right
driveRight *= 1.0 - finalSteering
# Set the motors to the new speeds
TB.SetMotor1(driveRight * maxPower)
TB.SetMotor2(driveLeft * maxPower)
# Sleep for our motor change interval
time.sleep(interval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()
# Turn the motors off
TB.MotorsOff()
# Set the LED to a dim red to indicate we have finished
TB.SetCommsFailsafe(False)
TB.SetLedShowBattery(False)
TB.SetLeds(0.2, 0, 0)

131
controls/Gamepad/rockyJoy.py Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python
# coding: utf-8
# Load the libraries
import sys
import Gamepad
import time
sys.path.insert(0, "/home/pi/rockyborg")
import RockyBorg
# Settings for the gamepad
gamepadType = Gamepad.PS4 # Class for the gamepad (e.g. Gamepad.PS3)
joystickSpeed = 'LEFT-Y' # Joystick axis to read for up / down position
joystickSpeedInverted = True # Set this to True if up and down appear to be swapped
joystickSteering = 'RIGHT-X' # Joystick axis to read for left / right position
joystickSteeringInverted = False # Set this to True if left and right appear to be swapped
buttonSlow = 'L2' # Joystick button for driving slowly whilst held
slowFactor = 0.5 # Speed to slow to when the drive slowly button is held, e.g. 0.5 would be half speed
buttonExit = 'PS' # Joystick button to end the program
interval = 0.05 # Time between motor updates in seconds, smaller responds faster but uses more processor time
# Power settings
voltageIn = 1.2 * 8 # Total battery voltage to the RockyBorg
voltageOut = 6.0 # Maximum motor voltage
# Setup the power limits
if voltageOut > voltageIn:
maxPower = 1.0
else:
maxPower = voltageOut / float(voltageIn)
# Setup the RockyBorg
RB = RockyBorg.RockyBorg()
#RB.i2cAddress = 0x21 # Uncomment and change the value if you have changed the board address
RB.Init()
if not RB.foundChip:
boards = RockyBorg.ScanForRockyBorg()
if len(boards) == 0:
print('No RockyBorg found, check you are attached :)')
else:
print('No RockyBorg at address %02X, but we did find boards:' % (RB.i2cAddress))
for board in boards:
print(' %02X (%d)' % (board, board))
print('If you need to change the I²C address change the setup line so it is correct, e.g.')
print('RB.i2cAddress = 0x%02X' % (boards[0]))
sys.exit()
# Enable the motors and disable the failsafe
RB.SetCommsFailsafe(False)
RB.MotorsOff()
RB.SetMotorsEnabled(True)
# Setup the state shared with callbacks
global running
running = True
# Create the callback functions
def exitButtonPressed():
global running
print('EXIT')
running = False
# Wait for a connection
RB.MotorsOff()
RB.SetLed(True)
waitingToggle = True
if not Gamepad.available():
print('Please connect your gamepad...')
while not Gamepad.available():
time.sleep(interval * 4)
waitingToggle = not waitingToggle
if waitingToggle:
RB.SetLed(False)
else:
RB.SetLed(True)
print('Gamepad connected')
gamepad = gamepadType()
# Start the background updating
gamepad.startBackgroundUpdates()
RB.SetLed(True)
# Register the callback functions
gamepad.addButtonPressedHandler(buttonExit, exitButtonPressed)
# Keep running while joystick updates are handled by the callbacks
try:
while running and gamepad.isConnected():
# Read the latest speed and steering
if joystickSpeedInverted:
speed = -gamepad.axis(joystickSpeed)
else:
speed = +gamepad.axis(joystickSpeed)
if joystickSteeringInverted:
steering = -gamepad.axis(joystickSteering)
else:
steering = +gamepad.axis(joystickSteering)
# Work out the adjusted speed
if gamepad.isPressed(buttonSlow):
finalSpeed = speed * slowFactor
else:
finalSpeed = speed
# Determine the drive power levels based on steering angle
servoPosition = steering
driveLeft = finalSpeed
driveRight = finalSpeed
if steering < -0.05:
# Turning left
driveLeft *= 1.0 + (0.5 * steering)
elif steering > +0.05:
# Turning right
driveRight *= 1.0 - (0.5 * steering)
# Set the motors to the new speeds and tilt the servo to steer
RB.SetMotor1(-driveLeft * maxPower)
RB.SetMotor2(driveRight * maxPower)
RB.SetServoPosition(servoPosition)
# Sleep for our motor change interval
time.sleep(interval)
finally:
# Ensure the background thread is always terminated when we are done
gamepad.disconnect()
# Turn the motors off
RB.MotorsOff()
# Turn the LED off indicate we have finished
RB.SetLed(False)