Using USB and Bluetooth Controllers with Python

Sam just shared a new tutorial: "Using USB and Bluetooth Controllers with Python"



The Raspberry Pi is an amazing piece of technology, and it’s the platform of choice for all kinds of projects. Something that makes it great is the integration of complex hardware that is taken care of for you. For example, if you wanted to use…

Read more

awesome tutorial! thanks!

I used this to get an xbox one s controller reading into python on an RPI3 perfectly. Now Im just wondering if there is a way to handle the controller going to sleep (event 40) from within that for loop…as is, this causes the program to crash…

any suggestions?

thanks!

Hi Aaron,

By the sounds of it, I’d say that’s an issue on the controller side as it is part of the device firmware to save power.

That said, you can certainly address the issue of it crashing. What you want to do is nest your controller events in a ‘try’ loop so that it will attempt to use the controller config if possible, and then you can add a condition if that device isn’t found and avoid the crash
I actually navigated a similar error when creating my Pixel Panel project with 8Bitdo Bluetooth controllers. I’ll dig up the code I used so you can use that as a framework for avoiding the crashes.

Hey thanks for the reply,

I did come up with this as the first if statement in the for loop:

if event.code==40:
print(‘Your controller went to sleep…’)
while os.path.exists(’/dev/input/event0’) != True:
time.sleep(1)

which does prevent the crash when the controller falls to sleep, but when you turn the controller back on and it exits the while loop, it will still say the device does not exist.

It seems like some file pointer created inside of evdev is no longer valid since the device shutdown.

I dont know, maybe try to reinitialize the object on the restart of the controller…

happy i made it this far already though! thanks for your help!

oh yea and I did find plenty of people upset about the firmware on the controller:

https://xbox.uservoice.com/forums/251650-console-hardware-accessories/suggestions/6031387-customize-when-and-how-controller-turns-off-due-to

Hmm, indeed. Here is the solution that I implemented for my project. I used a handler script which decided whether the controller connection was active, and then in-game handling was taken care of in the specific game script using the same technique:

#!usr/bin/python
import subprocess, time, os, sys
from dotstar import Adafruit_DotStar
from evdev import InputDevice, categorize, ecodes

sizeX = 30
sizeY = 15
NUMPIXELS = 2*sizeX*sizeY
strip = Adafruit_DotStar(NUMPIXELS, 8000000)
strip.begin()
strip.show()

counter = 1
select_button = 49

devID = '/dev/input/event1'
cmd1 = "sudo python /home/pi/SnakeMatrix/sa-matrix-mic.py"
cmd2 = "sudo python /home/pi/SnakeMatrix/snake-matrix.py"

p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE, shell=True)

while not os.path.exists(devID):
	time.sleep(0.01)

#loop and filter by event code and print the mapped label
def process_events():
	global counter
	global cmd1
	global cmd2
	toggleDir = '/home/pi/SnakeMatrix/toggleYes'
	try:
		gamepad = InputDevice(devID)
		for event in gamepad.read_loop():
			if event.type == ecodes.EV_KEY and event.value == 1 and event.code == select_button:
				if counter == 0:
					if os.path.exists(toggleDir):
						os.rmdir(toggleDir)
					time.sleep(0.5)
					print("Launching Spectrum Analyzer")
					p1 = subprocess.Popen(cmd1, stdout=subprocess.PIPE, shell=True)
					counter += 1
					#return False
		
				elif counter == 1:
					if not os.path.exists(toggleDir):
						os.makedirs(toggleDir)
					print("Launching Snake")
					time.sleep(0.5)
					p2 = subprocess.Popen(cmd2, stdout=subprocess.PIPE, shell=True)
					counter = 0
	except:			
		return False
while True:
	process_events()

If you get rid of the project specific things, the main things I looked for was the devID variable as it always enumerates sequentially, so providing you don’t hotplug a bunch of different things in there, you’ll be fine. You could always implement some extra filtering to find the Bluetooth ID tag and match that specifically if you needed to work around the devID enumeration.

But hopefully, that gives you an idea of how to get around that crash. I spent quite a few hours working out the best way to ensure that a disconnected controller wouldn’t cause issues and it seemed to work well.

The trick was implementing your code as a separate function which you can run depending on the return of the controller connecttion check.

Hi, I was following your guide but when I go to run the first or second script I get the following error.

Traceback (most recent call last):
File “./test.py”, line 6, in
gamepad = InputDevice(’/dev/input/js0’)
File “/usr/local/lib/python2.7/dist-packages/evdev/device.py”, line 133, in init
info_res = _input.ioctl_devinfo(self.fd)
IOError: [Errno 22] Invalid argument

I’m running the latest version of raspbian. Also tried on a fresh install to rule that out.

Any idea as to what I’m doing wrong?

Thanks.

How do I configure it to detect if I hold a button?

When I print the input from the controller it doesnt show any different value for a hold.

Hello and thank you I use servoblaster for servo management. I have a replica ps3 joypad when I use a servo button that moves. while remaining with the button pressed it executes only one instruction. Is it possible to send the temp command that the button is pressed?

Thank you

Thanks for the information sharing
vmware

[EDIT]: I improved and fixed my code and uploaded to GitHub - MarioMey/py-gamepads: Connect several bluetooth gamepads.. There would be updates.

Here is my contribution. This code is for connecting two gamepads (mine are like this). It creates one or two InputDevices at the beginning and registers them to selector … or it waits you to turn them on. If one or both are turned off (intentionally or automatic by no using them for some minutes), it closes device and unregisters it from selector (preventing errors). You can turn them on and off whenever you want.

Default event devices are 16 and 20 but, if any of that devices goes to the next event number (17 or 21), code also check that path. If you have different numbers, you can set it in command line.

Remember that evdev uses /dev/input/event* . Don’t use /dev/input/js0 because, even if it receives data, it does’t work with evdev , it raises the IOError: [Errno 22] Invalid argument error.

Also check buttons codes. Mine are like they are in the code. Maybe they differ from yours.

I run this code inside OBS and use it to send OSC messages to another software (PureData) but it is prepared to run as standalone (no OBS and no OSC messages). It only prints the number of the /dev/input/event* device, followed by a letter:

Hat

  • h: horizontal analog (-127 to 127)
  • v: vertical analog (-127 to 127)
  • t: top (1 for press, 0 for release)
  • b: bottom (1/0)
  • r: right (1/0)
  • l: left (1/0)
  • c: center (0/1) (optional)

Buttons

  • 0
  • 1
  • 2
  • 3
  • 4 (which is also on/off button)

Code: GitHub - MarioMey/py-gamepads: Connect several bluetooth gamepads.

3 Likes

I am using a bluetooth shutter button to connect to a script on a raspberry pi via your tutorial here. Works great, except after a bit, my button loses connection. If I turn it off and back on, it starts working again. What do you see as a possible cause of this? Is there any coding that would refresh the connection periodically or something, or is this related to the native bluetooth in the pi?

Hi Jade,

Welcome! I’m unsure what is causing that, but you may be able to get around it by running the disconnect and reconnect commands from within Python:

Let me know if this sparks any followup Qs :slight_smile: