Over the past few weeks, I’ve been working on a little project that will allow me to remotely control an RC car via the internet. I know there are about a million other people who have done that already, but I want to make mine a little smarter - webcam, GPS, laser tracking via servo, etc - and I want to control it with a PS3 controller (with which I’m pretty handy). For a number of reasons, I’ve found that the easiest programming language to do this in is Python. I also need a GUI for this project (for video feeds and other telemetry), and fortunately there are a number of frameworks for GUI programming in Python. After some research, I decided to use wxPython for my little project. Everything has gone pretty smoothly so far, but one of the biggest stumbling blocks I’ve come across is using a joystick, specifically the wx.Joystick object.
Turns out that for all of the documentation and Python/wxPython tutorials and resources on the web, there is a dearth of tutorials specifically focusing on the joystick object. Yes, there is the joystick example that comes bundled with wxPython, but as a newbish user, I had a LOT of trouble following along with what was happening and why.
After a few nights of experimentation and trial-and-error, I finally got to a place where I understand pretty much everything about the wx.Joystick object. So I decided to make a detailed entry here wherein we build a fully-functional (albeit ugly) wxPython program that interfaces with a joystick, and I’ll explain everything along the way. Hopefully someone else finds this useful.
Background Info and Gotchas:
I’m using a Logitech F310 for my wxPython / wx.Joystick object, as it’s a pretty cheap and easy to find facsimile of a PS3 controller. I guess I could use an actual PS3 controller, but I thought there may be enough differences with drivers and such that I’d rather spend the $20 and save myself hours of frustrations and workarounds.
Also, any joystick should theoretically work for this example, but keep in mind that if your joystick is configured or installed in a way that the framework doesn’t expect it can throw an error. For my F310, wxPython gives a registry error when running the GetProductName method because the installer didn’t create a registry entry for the product name in a location wxPython could find. It doesn’t crash the program, but it’s still really irritating.
Prerequisites:
In order to understand these examples, you have to at least have a working understanding of Python. Note that my familiarity with Python is between beginner and intermediate, so that’s about where you need to be in order to understand the code. If there’s anything crazy, I’ll make sure to explain it in detail.
You also need a working knowledge of wxPython. I’m not going to go into any detail on Windows/Frames/Panels/etc or any of the methods and functions outside of the joystick-related ones. Again, my familiarity is between beginner and advanced, so you don’t need any more than that.
Example 1: Easiest wx.Joystick program
In this example, what I call the “Easiest Possible wx.Joystick Example,” we set up a frame, initialize a joystick object, attach some static text indicating the joystick is attached and ready, then render the window. Take a look:
# Simplest possible wx.Joystick example
# Import wx objects
import wx
# Taken from wxPython demos:
haveJoystick = True
if wx.Platform == "__WXMAC__":
haveJoystick = False
# The main frame
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
# Create Joystick object
self.stick = wx.Joystick()
self.stick.SetCapture(self)
# Now we'll add some basic info about our joystick to the frame:
# Add the joystick status:
joystick_status = {True: 'Good', False: 'Fail'}[self.stick.IsOk() == True] # See: http://en.wikipedia.org/wiki/%3F:#Python
wx.StaticText(self, -1, 'Joystick Status:', pos=(10,10))
wx.StaticText(self, -1, joystick_status, pos=(90,10))
# Show the info:
self.Show(True)
def main():
if haveJoystick == False:
print 'wx.Joystick is not available on this platform.'
return
else:
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
If you run this code, you’ll see a window like this:
The only major thing to note in this example is the block on lines 5-8 and its counterpart on lines 32-34. This came from the wxPython joystick demo, and it just tests to see if the wx.Joystick object is available on your system. I guess it doesn’t work with Macs? If you’re running a Mac, change line 32 to “if FALSE” (without quotes) and see what happens. Maybe it will work for you.
Example 2: Adding some more data
Example 1 was really easy, but also entirely pointless. Don’t worry, we were just establishing some base code to build on. Let’s expand on it by loading some more data about the joystick using the GetManufacturerId and GetProductId methods. We’ll also format the display window and set up some global variables to help keep all of the text aligned and consistent:
# wx.Joystick example number 2
# Import wx objects
import wx
# Taken from wxPython demos:
haveJoystick = True
if wx.Platform == "__WXMAC__":
haveJoystick = False
# Create a few global variables so we can tweak alignment, etc
X_LABEL = 10
X_QUANT = 120
Y = 10
LINE_HEIGHT = 15
# The main frame
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
global Y
# Create Joystick object
self.stick = wx.Joystick()
self.stick.SetCapture(self)
# Now we'll add some basic info about our joystick to the frame:
# Add the joystick status:
joystick_status = {True: 'Good', False: 'Fail'}[self.stick.IsOk() == True] # See: http://en.wikipedia.org/wiki/%3F:#Python
wx.StaticText(self, -1, 'Joystick Status', pos=(X_LABEL,Y))
wx.StaticText(self, -1, joystick_status, pos=(X_QUANT,Y))
Y = Y + LINE_HEIGHT
# Add the manufacturer ID:
wx.StaticText(self, -1, 'Manufacturer ID:', pos=(X_LABEL,Y))
wx.StaticText(self, -1, str(self.stick.GetManufacturerId()), pos=(X_QUANT,Y))
Y = Y + LINE_HEIGHT
# Add the product ID:
wx.StaticText(self, -1, 'Product ID:', pos=(X_LABEL,Y))
wx.StaticText(self, -1, str(self.stick.GetProductId()), pos=(X_QUANT,Y))
Y = Y + LINE_HEIGHT
# Set the frame size and title, center to the screen and load
self.SetSize((400, 200))
self.SetTitle('Joystick Info')
self.Centre()
self.Show(True)
def main():
if haveJoystick == False:
print 'wx.Joystick is not available on this platform.'
return
else:
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
After running the code, you should see this:
Again, we’re looking at a very simple and pointless program, but we added a few elements that will be important later on. By setting up the position variables X_LABEL, X_QUANT, Y, and LINE_HEIGHT, we can keep everything properly aligned in the display.
Example 3: Displaying (almost) all the joystick data
Notice in Example 2 that the process for adding text to the screen requires the same three steps:
Add the label Add the value Increase Y by LINE_HEIGHT so the next item will be properly aligned
That’s simple enough, but there are 40 methods for us to examine. That means 120 lines of code just to cover the data. In this example, we’ll set up a simple function, PrintJoystickMethod which will allow us to print methods to the screen with ONE line of code rather than three. This will save us 80 lines of code and simplify the process of managing changes to our algorithm down the road.
PrintJoystickMethod will also do something that we didn’t do in Examples 1 and 2. Notice in the first two examples that the text was added to the frame, but not made an attribute. This means that if we wanted to access or modify any of the text later on, we would be unable to do so. Our PrintJoystickMethod will, in addition to placing the text on the screen, create a StaticText attribute that we can access/modify later on. This will be important when we finally make the program interactive.
Let’s create PrintJoystickMethod and finally address all of the methods the wx.Joystick object offers:
# Import wx objects
import wx
# Taken from wxPython demos:
haveJoystick = True
if wx.Platform == "__WXMAC__":
haveJoystick = False
# Create a few global variables so we can tweak alignment, etc
X_LABEL = 10
X_QUANT = 120
Y = 10
LINE_HEIGHT = 15
def PrintJoystickMethod(self, label, val, attrns):
# Create a function to condense and clarify the code. This function takes a
# label and a value then positions them in the frame. The 'attrns' input
# makes the wx.StaticText object an attribute of the joystick object. For example,
# calling PrintJoystickMethod(self, 'Status: ', self.stick.IsOk(), 'status')
# is equivalent to running:
# 1. wx.StaticText(self, -1, 'Status: ', pos=(X_LABEL,Y))
# 2. self.stick.status=wx.StaticText(self, -1, str(self.stick.IsOk()), pos=(X_QUANT, Y))
# 3. Y = Y + LINE_HEIGHT
# This way we can condense three lines of code down to just one, and do it in a way that
# allows us to access and change the values later (like if the joystick is moved, button
# pressed, etc). Since this example covers around 40 methods, this function allows us to
# reduce the code from 120 lines to just 40, while maintaining a consistent visual layout.
global X_LABEL, X_QUANT, Y
wx.StaticText(self, -1, str(label), pos=(X_LABEL,Y))
setattr(self.stick, str(attrns), wx.StaticText(self, -1, str(val), pos=(X_QUANT,Y)))
Y = Y + LINE_HEIGHT
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
# Simple method to create a new column of data
def NewColumn():
global X_LABEL, X_QUANT, Y
Y = 10
X_LABEL = X_QUANT + 75
X_QUANT = X_LABEL + 110
# Simple method to create a new row of data:
def NewRow():
global Y
Y = Y + LINE_HEIGHT
# Create Joystick object
self.stick = wx.Joystick()
self.stick.SetCapture(self)
# Print the information to the screen:
joystick_status = {True: 'Good', False: 'Fail'}[self.stick.IsOk() == True] # See: http://en.wikipedia.org/wiki/%3F:#Python
PrintJoystickMethod(self, 'Joystick Status:', joystick_status, 'status')
PrintJoystickMethod(self, 'Manufacturer ID:', self.stick.GetManufacturerId(), 'mid')
PrintJoystickMethod(self, 'Product ID:', self.stick.GetProductId(), 'pid')
# Get the joystick name:
joystick_name = "<None>"
try:
# This is placed in a try...except block to catch errors that sometimes occur.
# Depends on the joystick, drivers, registry, etc. May or may not work as expected.
# If it throws errors during testing, just comment out this line and uncomment the line below it
joystick_name = self.stick.GetProductName()
#joystick_name = 'YOUR JOYSTICK NAME'
except:
pass
PrintJoystickMethod(self, 'Product Name:', joystick_name, 'name')
# Print some physical properties of the joystick:
NewRow()
PrintJoystickMethod(self, 'Movement Threshold:', self.stick.GetMovementThreshold(), 'movethresh')
PrintJoystickMethod(self, 'Number of Axes:', self.stick.GetNumberAxes(), 'numaxes')
PrintJoystickMethod(self, 'Number of Buttons:', self.stick.GetNumberButtons(), 'numbuttons')
PrintJoystickMethod(self, 'Number of Joysticks:', self.stick.GetNumberJoysticks(), 'numjoysticks')
# Translate boolean results to Yes/No answers for some joystick properties:
has_rudder = {True: 'Yes', False: 'No'}[self.stick.HasRudder() == True]
has_u = {True: 'Yes', False: 'No'}[self.stick.HasU() == True]
has_v = {True: 'Yes', False: 'No'}[self.stick.HasV() == True]
has_z = {True: 'Yes', False: 'No'}[self.stick.HasZ() == True]
has_pov = {True: 'Yes', False: 'No'}[self.stick.HasPOV() == True]
has_pov4dir = {True: 'Yes', False: 'No'}[self.stick.HasPOV4Dir() == True]
has_povcts = {True: 'Yes', False: 'No'}[self.stick.HasPOVCTS() == True]
# Print details about the rudder:
NewRow()
PrintJoystickMethod(self, 'Has Rudder?:', has_rudder, 'hasrudder')
if self.stick.HasRudder() == True:
PrintJoystickMethod(self, 'Rudder Max:', self.stick.GetRudderMax(), 'ruddermax')
PrintJoystickMethod(self, 'Rudder Min:', self.stick.GetRudderMin(), 'ruddermin')
PrintJoystickMethod(self, 'Rudder Position:', self.stick.GetRudderPosition(), 'rudderpos')
# Print details about the joystick's U-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has U?:', has_u, 'hasu')
if self.stick.HasU() == True:
PrintJoystickMethod(self, 'U Max:', self.stick.GetUMax(), 'umax')
PrintJoystickMethod(self, 'U Min:', self.stick.GetUMin(), 'umin')
PrintJoystickMethod(self, 'U Position:', self.stick.GetUPosition(), 'upos')
# Print details about the joystick's V-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has V?:', has_v, 'hasv')
if self.stick.HasV() == True:
PrintJoystickMethod(self, 'V Max:', self.stick.GetVMax(), 'vmax')
PrintJoystickMethod(self, 'V Min:', self.stick.GetVMin(), 'vmin')
PrintJoystickMethod(self, 'V Position:', self.stick.GetVPosition(), 'vpos')
# Print details about the joystick's Z-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has Z?:', has_z, 'hasz')
if self.stick.HasZ() == True:
PrintJoystickMethod(self, 'Z Max:', self.stick.GetZMax(), 'zmax')
PrintJoystickMethod(self, 'Z Min:', self.stick.GetZMin(), 'zmin')
PrintJoystickMethod(self, 'Z Position:', self.stick.GetZPosition(), 'zpos')
# Print details about the joystick's POV switch (if applicable):
NewColumn()
PrintJoystickMethod(self, 'Has POV?:', has_pov, 'haspov')
if self.stick.HasPOV() == True:
PrintJoystickMethod(self, 'POV Position:', self.stick.GetPOVPosition(), 'povpos')
# Does the joystick offer 4-directional POV switch?
# (forward, backward, left, and right):
NewRow()
PrintJoystickMethod(self, 'Has POV4Dir?:', has_pov4dir, 'haspov4dir')
# Print details about the joystick's POV switch's
# continuous degree bearings (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has POVCTS?:', has_povcts, 'haspovcts')
if self.stick.HasPOVCTS() == True:
PrintJoystickMethod(self, 'POVCTS Position:', self.stick.GetPOVCTSPosition(), 'povctspos')
# Print details about the joystick's polling:
NewRow()
PrintJoystickMethod(self, 'Polling (max):', self.stick.GetPollingMax(), 'pollingmin')
PrintJoystickMethod(self, 'Polling (min):', self.stick.GetPollingMin(), 'pollingmax')
# Print details about the joystick's X-Y postion:
NewRow()
PrintJoystickMethod(self, '(X,Y) Position:', self.stick.GetPosition(), 'xyposition')
# Print details about the joystick's X-axis:
NewRow();
PrintJoystickMethod(self, 'X (max):', self.stick.GetXMax(), 'xmax')
PrintJoystickMethod(self, 'X (min):', self.stick.GetXMin(), 'xmin')
# Print details about the joystick's Y-axis:
NewRow();
PrintJoystickMethod(self, 'Y (max):', self.stick.GetYMax(), 'ymax')
PrintJoystickMethod(self, 'Y (min):', self.stick.GetYMin(), 'ymin')
# Set the frame size and title, center to the screen and load
self.SetSize((500, 500))
self.SetTitle('Joystick Info')
self.Centre()
self.Show(True)
def main():
if haveJoystick == False:
print 'wx.Joystick is not available on this platform.'
return
else:
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
This should be the output of the program:
This should show you all of the available specs for your joystick (your values may not match those in the screenshot). By now you should be starting to get a feel for how much information is available to you from the wx.Joystick object. You may even be thinking about ways you could use this information in your own projects. Still, a static display is not really helpful for much. We’ll fix that in Example 4.
Example 4: Making it interactive
In this example, we’re going to do two things to bring our code to life. We’re going to bind the wx.EVT_JOYSTICK_EVENTS action to a function we’ll call OnJoystick. What this will do is basically set up a monitor; ANYTHING that happens on the joystick (movement, button press, etc) will register and be sent to the OnJoystick function that we’ll create. We’ll use our OnJoystick function to poll the state of the joystick for changes in position, then update the frame text with the new values.
We’re also going to add some widgets that indicate the positions of the joystick. There’s no real point to this other than to translate the numerical values into something visual. In theory, you could modify this code to use some advanced or custom widgets in place of the simple gauges I used.
Let’s do it:
# Import wx objects
import wx
# Taken from wxPython demos:
haveJoystick = True
if wx.Platform == "__WXMAC__":
haveJoystick = False
# Create a few global variables so we can tweak alignment in the GUI window
X_LABEL = 10
X_QUANT = 120
Y = 10
LINE_HEIGHT = 15
def PrintJoystickMethod(self, label, val, attrns):
# Create a function to condense and clarify the code. This function takes a
# label and a value then positions them in the frame. The 'attrns' input
# makes the wx.StaticText object an attribute of the joystick object. For example,
# calling PrintJoystickMethod(self, 'Status: ', self.stick.IsOk(), 'status')
# is equivalent to running:
# 1. wx.StaticText(self, -1, 'Status: ', pos=(X_LABEL,Y))
# 2. self.stick.status=wx.StaticText(self, -1, str(self.stick.IsOk()), pos=(X_QUANT, Y))
# 3. Y = Y + LINE_HEIGHT
# This way we can condense three lines of code down to just one, and do it in a way that
# allows us to access and change the values later (like if the joystick is moved, button
# pressed, etc). Since this example covers around 40 methods, this function allows us to
# reduce the code from 120 lines to just 40, while maintaining a consistent visual layout.
global X_LABEL, X_QUANT, Y
wx.StaticText(self, -1, str(label), pos=(X_LABEL,Y))
setattr(self.stick, str(attrns), wx.StaticText(self, -1, str(val), pos=(X_QUANT,Y)))
Y = Y + LINE_HEIGHT
# The main GUI window:
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
global Y
# Simple function to create a new column of data
def NewColumn():
global X_LABEL, X_QUANT, Y
Y = 10
X_LABEL = X_QUANT + 75
X_QUANT = X_LABEL + 110
# Simple function to create a new row of data:
def NewRow():
global Y
Y = Y + LINE_HEIGHT
# Create Joystick object
self.stick = wx.Joystick()
self.stick.SetCapture(self)
# Print the product information to the screen:
joystick_status = {True: 'Good', False: 'Fail'}[self.stick.IsOk() == True] # Translate Boolean value to text. For more info, see: http://en.wikipedia.org/wiki/%3F:#Python
PrintJoystickMethod(self, 'Joystick Status:', joystick_status, 'status')
PrintJoystickMethod(self, 'Manufacturer ID:', self.stick.GetManufacturerId(), 'mid')
PrintJoystickMethod(self, 'Product ID:', self.stick.GetProductId(), 'pid')
# Get the joystick name:
joystick_name = "<None>"
try:
# This is placed in a try...except block to catch errors that sometimes occur.
# Depends on the joystick, drivers, registry, etc. May or may not work as expected.
# If it throws errors during testing, just comment out this line and uncomment the line below it
joystick_name = self.stick.GetProductName()
#joystick_name = 'YOUR JOYSTICK NAME'
except:
pass
PrintJoystickMethod(self, 'Product Name:', joystick_name, 'name')
# Print some physical properties of the joystick:
NewRow()
PrintJoystickMethod(self, 'Movement Threshold:', self.stick.GetMovementThreshold(), 'movethresh')
PrintJoystickMethod(self, 'Number of Axes:', self.stick.GetNumberAxes(), 'numaxes')
PrintJoystickMethod(self, 'Number of Buttons:', self.stick.GetNumberButtons(), 'numbuttons')
PrintJoystickMethod(self, 'Number of Joysticks:', self.stick.GetNumberJoysticks(), 'numjoysticks')
# Translate boolean results to Yes/No answers for some joystick properties:
has_rudder = {True: 'Yes', False: 'No'}[self.stick.HasRudder() == True]
has_u = {True: 'Yes', False: 'No'}[self.stick.HasU() == True]
has_v = {True: 'Yes', False: 'No'}[self.stick.HasV() == True]
has_z = {True: 'Yes', False: 'No'}[self.stick.HasZ() == True]
has_pov = {True: 'Yes', False: 'No'}[self.stick.HasPOV() == True]
has_pov4dir = {True: 'Yes', False: 'No'}[self.stick.HasPOV4Dir() == True]
has_povcts = {True: 'Yes', False: 'No'}[self.stick.HasPOVCTS() == True]
# Print details about the rudder (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has Rudder?:', has_rudder, 'hasrudder')
if self.stick.HasRudder() == True:
PrintJoystickMethod(self, 'Rudder Max:', self.stick.GetRudderMax(), 'ruddermax')
PrintJoystickMethod(self, 'Rudder Min:', self.stick.GetRudderMin(), 'ruddermin')
PrintJoystickMethod(self, 'Rudder Position:', self.stick.GetRudderPosition(), 'rudderpos')
# Print details about the joystick's U-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has U?:', has_u, 'hasu')
if self.stick.HasU() == True:
PrintJoystickMethod(self, 'U Max:', self.stick.GetUMax(), 'umax')
PrintJoystickMethod(self, 'U Min:', self.stick.GetUMin(), 'umin')
PrintJoystickMethod(self, 'U Position:', self.stick.GetUPosition(), 'upos')
# Print details about the joystick's V-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has V?:', has_v, 'hasv')
if self.stick.HasV() == True:
PrintJoystickMethod(self, 'V Max:', self.stick.GetVMax(), 'vmax')
PrintJoystickMethod(self, 'V Min:', self.stick.GetVMin(), 'vmin')
PrintJoystickMethod(self, 'V Position:', self.stick.GetVPosition(), 'vpos')
# Print details about the joystick's Z-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has Z?:', has_z, 'hasz')
if self.stick.HasZ() == True:
PrintJoystickMethod(self, 'Z Max:', self.stick.GetZMax(), 'zmax')
PrintJoystickMethod(self, 'Z Min:', self.stick.GetZMin(), 'zmin')
PrintJoystickMethod(self, 'Z Position:', self.stick.GetZPosition(), 'zpos')
# Print details about the joystick's POV switch (if applicable):
NewColumn()
PrintJoystickMethod(self, 'Has POV?:', has_pov, 'haspov')
if self.stick.HasPOV() == True:
PrintJoystickMethod(self, 'POV Position:', self.stick.GetPOVPosition(), 'povpos')
# Does the joystick offer 4-directional POV switch?
# (forward, backward, left, and right):
NewRow()
PrintJoystickMethod(self, 'Has POV4Dir?:', has_pov4dir, 'haspov4dir')
# Print details about the joystick's POV switch's
# continuous degree bearings (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has POVCTS?:', has_povcts, 'haspovcts')
if self.stick.HasPOVCTS() == True:
PrintJoystickMethod(self, 'POVCTS Position:', self.stick.GetPOVCTSPosition(), 'povctspos')
# Print details about the joystick's polling:
NewRow()
PrintJoystickMethod(self, 'Polling (max):', self.stick.GetPollingMax(), 'pollingmin')
PrintJoystickMethod(self, 'Polling (min):', self.stick.GetPollingMin(), 'pollingmax')
# Print details about the joystick's X-Y postion:
NewRow()
PrintJoystickMethod(self, '(X,Y) Position:', self.stick.GetPosition(), 'xyposition')
# Print details about the joystick's X-axis:
NewRow()
PrintJoystickMethod(self, 'X (max):', self.stick.GetXMax(), 'xmax')
PrintJoystickMethod(self, 'X (min):', self.stick.GetXMin(), 'xmin')
# Print details about the joystick's Y-axis:
NewRow()
PrintJoystickMethod(self, 'Y (max):', self.stick.GetYMax(), 'ymax')
PrintJoystickMethod(self, 'Y (min):', self.stick.GetYMin(), 'ymin')
# Bind joystick actions to an updater function:
self.Bind(wx.EVT_JOYSTICK_EVENTS, self.OnJoystick) # If anything happens w/joystick, call OnJoystick
self.stick.SetMovementThreshold(1) # Sets the movement threshold, the number of steps outside which the joystick is deemed to have moved.
# Create some simple gauge widgets for the joystick positions
NewColumn()
wx.StaticText(self, -1, 'X-axis Gauge: ', pos=(X_LABEL,Y))
self.xpos = wx.Gauge(self, range=self.stick.GetXMax(), pos=(X_QUANT, Y), size=(150, 5))
wx.StaticText(self, -1, 'Y-axis Gauge: ', pos=(X_LABEL,Y+35))
self.ypos = wx.Gauge(self, range=self.stick.GetYMax(), pos=(X_QUANT, Y+35), size=(150, 5))
wx.StaticText(self, -1, 'Z-axis Gauge: ', pos=(X_LABEL,Y+70))
self.zpos = wx.Gauge(self, range=self.stick.GetZMax(), pos=(X_QUANT, Y+70), size=(150, 5))
wx.StaticText(self, -1, 'Rudder Gauge: ', pos=(X_LABEL,Y+105))
self.rpos = wx.Gauge(self, range=self.stick.GetRudderMax(), pos=(X_QUANT, Y+105), size=(150, 5))
self.SetSize((750, 575))
self.SetTitle('Joystick Info')
self.Centre()
self.Show(True)
def OnJoystick(self, e):
# Update the values for xy position, rudder position, and z-axis position.
# These are all values that can change. The number of buttons, axes, etc - values that are
# fixed - are not updated for that reason. You can add more values to update by simply adding
# a new line of the format self.stick.ATTRNS.SetLabel(str(self.stick.METHOD())) where ATTRNS
# is the same one defined for the value during initialization, and METHOD is the wxPython
# method for collecting the value.
self.stick.xyposition.SetLabel(str(self.stick.GetPosition()))
self.stick.rudderpos.SetLabel( str(self.stick.GetRudderPosition()))
self.stick.zpos.SetLabel( str(self.stick.GetZPosition()))
# Update the widgets:
newx, newy = self.stick.GetPosition()
self.xpos.SetValue(newx)
self.ypos.SetValue(newy)
self.zpos.SetValue(self.stick.GetZPosition())
self.rpos.SetValue(self.stick.GetRudderPosition())
def main():
if haveJoystick == False:
print 'wx.Joystick is not available on this platform.'
return
else:
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
If the code runs successfully, you should see this:
You probably noticed a major element is missing in this example: buttons. WTF is happening to my buttons?? The reason I didn’t include button polling here is that it is a bit complicated. In fact, it’s complex enough that I thought it deserved its own example. Read on…
Example 5: wx.Joystick and Button Checking
Checking the joystick button states is one of the trickier things to do in this example. The GetButtonState method does NOT return an array or list of buttons pressed as one might expect (supposedly it used to - the docs are unclear about this). The current version of wxPython returns an integer representing the binary state of all the buttons. The state of each button is read as 1 (pressed) or 0 (not pressed).
So, let’s say that your joystick has ten buttons. When none are pressed, wxPython treats this as a 10-bit binary value of 0000000000 (ten values of ‘0,’ representing ten unpressed buttons). The GetButtonState method returns the decimal representation of 0000000000, which is simply 0.
Now, let’s say you press a button that the joystick treats as ‘Button 4’; then wxPython treats this as 0000001000, which is 9 values of ‘0’ and one value of ‘1’ in the position 4th from the right (because binary is read from right to left), representing 9 unpressed buttons and the fourth button is pressed. In this case, GetButtonState returns the decimal representation of 0000001000, which is 8.
Going further, let’s say you simultaneously press buttons 2, 5, and 8 on your ten button joystick. wxPython reads this as 0010010010 and GetButtonState returns the decimal value of 0010010010, which is 146. Make sense? If not, re-read these paragraphs until it does!
To read the button states, we first create an array attribute on the joystick object during the initialization stage, and use a FOR loop to iterate over the range of the number of joystick buttons (i=0..BUTTONS-1). We create a StaticText object for each element in the array that displays the corresponding button’s state (on/off):
In coding terms, this goes in the init function:
self.stick.butns = {}
for i in range(self.stick.GetNumberButtons()):
wx.StaticText(self, -1, 'Button '+str(i+1)+': ', pos=(X_LABEL,Y))
self.stick.butns[i] = wx.StaticText(self, -1, 'Off', pos=(X_QUANT,Y))
Y = Y + LINE_HEIGHT
Note that this approach forgoes the PrintJoystickMethod function for adding the text to the frame. Doing things this way makes the button states easier to access/modify later on.
After the object has been intialized with the button array, the OnJoystick function is called when the user presses a button on the joystick. Again, we use a FOR loop to iterate over the range of the number of joystick buttons (i=0..BUTTONS-1), only now we use some binary logic to figure out which buttons have been pressed:
t = self.stick.GetButtonState()
for i in range(self.stick.GetNumberButtons()):
if (t & (1<<i)):
self.stick.butns[i].SetLabel('On')
else:
self.stick.butns[i].SetLabel('Off')
This block, adapted from the wxPython joystick demo, does some pretty important things. It’s extremely concise, yet expresses a TON of logic necessary to make this all work. The key line is the conditional statement “if (t & (1<<i)):”. Let’s look at that in detail.
The single ampersand is a binary AND operator. This is different from a logical AND operator in that it combines the binary representations of two values by copying a bit only if the corresponding bits of the two operands is 1. What does that mean? If we have two binary values, 0101 and 1110, then (0101 & 1110) evaluates to 0100 (binary) or 4 (dec). Note that the only place in which both operands share a 1 is the 3rd place from the right, hence the result of 0100.
Further, the << is a bitwise left shift operator, which takes the binary representation of the left operand and shifts it to the left by a number of places determined by the operand on the right. So “3<<2” means “take the binary value of 3 (0011) and shift everything two places to the left.” This results in a binary value of 1100, or 12 in decimal. In the FOR loop above, this simple conditional statement handles all of the logic necessary for determining which button was pressed.
If we return to the example of a 10-button joystick where buttons 2, 5, and 8 are simultaneously pressed, we can build a truth table for all of the possibilities generated by the FOR loop:
i t 1<<i (t & (1<<i)) Evaluates as Button Number
0 0010010010 0000000001 0000000000 = 0 FALSE 1
1 0010010010 0000000010 0000000010 = 2 TRUE 2
2 0010010010 0000000100 0000000000 = 0 FALSE 3
3 0010010010 0000001000 0000000000 = 0 FALSE 4
4 0010010010 0000010000 0000010000 = 16 TRUE 5
5 0010010010 0000100000 0000000000 = 0 FALSE 6
6 0010010010 0001000000 0000000000 = 0 FALSE 7
7 0010010010 0010000000 0010000000 = 128 TRUE 8
8 0010010010 0100000000 0000000000 = 0 FALSE 9
9 0010010010 1000000000 0000000000 = 0 FALSE 10
So we can see that this little conditional statement within the FOR loop correctly returns TRUE for any and all buttons that are being pressed. Using this, and the button array we initialized on the joystick object, we can now update each value to indicate whether it is being pressed (“On”) or not (“Off”).
With that in mind, let’s press on to the final example of this post:
# Import wx objects
import wx
# Taken from wxPython demos:
MAX_BUTTONS = 16 # This comes from a limit in Python dealing with binary numbers I guess. More info on line 209
haveJoystick = True
if wx.Platform == "__WXMAC__":
haveJoystick = False
# Create a few global variables so we can tweak alignment in the GUI window
X_LABEL = 10
X_QUANT = 120
Y = 10
LINE_HEIGHT = 15
def PrintJoystickMethod(self, label, val, attrns):
# Create a function to condense and clarify the code. This function takes a
# label and a value then positions them in the frame. The 'attrns' input
# makes the wx.StaticText object an attribute of the joystick object. For example,
# calling PrintJoystickMethod(self, 'Status: ', self.stick.IsOk(), 'status')
# is equivalent to running:
# 1. wx.StaticText(self, -1, 'Status: ', pos=(X_LABEL,Y))
# 2. self.stick.status=wx.StaticText(self, -1, str(self.stick.IsOk()), pos=(X_QUANT, Y))
# 3. Y = Y + LINE_HEIGHT
# This way we can condense three lines of code down to just one, and do it in a way that
# allows us to access and change the values later (like if the joystick is moved, button
# pressed, etc). Since this example covers around 40 methods, this function allows us to
# reduce the code from 120 lines to just 40, while maintaining a consistent visual layout.
global X_LABEL, X_QUANT, Y
wx.StaticText(self, -1, str(label), pos=(X_LABEL,Y))
setattr(self.stick, str(attrns), wx.StaticText(self, -1, str(val), pos=(X_QUANT,Y)))
Y = Y + LINE_HEIGHT
# The main GUI window:
class Example(wx.Frame):
def __init__(self, *args, **kw):
super(Example, self).__init__(*args, **kw)
global Y
# Simple function to create a new column of data
def NewColumn():
global X_LABEL, X_QUANT, Y
Y = 10
X_LABEL = X_QUANT + 75
X_QUANT = X_LABEL + 110
# Simple function to create a new row of data:
def NewRow():
global Y
Y = Y + LINE_HEIGHT
# Create Joystick object
self.stick = wx.Joystick()
self.stick.SetCapture(self)
# Print the product information to the screen:
joystick_status = {True: 'Good', False: 'Fail'}[self.stick.IsOk() == True] # Translate Boolean value to text. For more info, see: http://en.wikipedia.org/wiki/%3F:#Python
PrintJoystickMethod(self, 'Joystick Status:', joystick_status, 'status')
PrintJoystickMethod(self, 'Manufacturer ID:', self.stick.GetManufacturerId(), 'mid')
PrintJoystickMethod(self, 'Product ID:', self.stick.GetProductId(), 'pid')
# Get the joystick name:
joystick_name = "<None>"
try:
# This is placed in a try...except block to catch errors that sometimes occur.
# Depends on the joystick, drivers, registry, etc. May or may not work as expected.
# If it throws errors during testing, just comment out this line and uncomment the line below it
joystick_name = self.stick.GetProductName()
#joystick_name = 'YOUR JOYSTICK NAME'
except:
pass
PrintJoystickMethod(self, 'Product Name:', joystick_name, 'name')
# Print some physical properties of the joystick:
NewRow()
PrintJoystickMethod(self, 'Movement Threshold:', self.stick.GetMovementThreshold(), 'movethresh')
PrintJoystickMethod(self, 'Number of Axes:', self.stick.GetNumberAxes(), 'numaxes')
PrintJoystickMethod(self, 'Number of Buttons:', self.stick.GetNumberButtons(), 'numbuttons')
PrintJoystickMethod(self, 'Number of Joysticks:', self.stick.GetNumberJoysticks(), 'numjoysticks')
# Translate boolean results to Yes/No answers for some joystick properties:
has_rudder = {True: 'Yes', False: 'No'}[self.stick.HasRudder() == True]
has_u = {True: 'Yes', False: 'No'}[self.stick.HasU() == True]
has_v = {True: 'Yes', False: 'No'}[self.stick.HasV() == True]
has_z = {True: 'Yes', False: 'No'}[self.stick.HasZ() == True]
has_pov = {True: 'Yes', False: 'No'}[self.stick.HasPOV() == True]
has_pov4dir = {True: 'Yes', False: 'No'}[self.stick.HasPOV4Dir() == True]
has_povcts = {True: 'Yes', False: 'No'}[self.stick.HasPOVCTS() == True]
# Print details about the rudder (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has Rudder?:', has_rudder, 'hasrudder')
if self.stick.HasRudder() == True:
PrintJoystickMethod(self, 'Rudder Max:', self.stick.GetRudderMax(), 'ruddermax')
PrintJoystickMethod(self, 'Rudder Min:', self.stick.GetRudderMin(), 'ruddermin')
PrintJoystickMethod(self, 'Rudder Position:', self.stick.GetRudderPosition(), 'rudderpos')
# Print details about the joystick's U-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has U?:', has_u, 'hasu')
if self.stick.HasU() == True:
PrintJoystickMethod(self, 'U Max:', self.stick.GetUMax(), 'umax')
PrintJoystickMethod(self, 'U Min:', self.stick.GetUMin(), 'umin')
PrintJoystickMethod(self, 'U Position:', self.stick.GetUPosition(), 'upos')
# Print details about the joystick's V-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has V?:', has_v, 'hasv')
if self.stick.HasV() == True:
PrintJoystickMethod(self, 'V Max:', self.stick.GetVMax(), 'vmax')
PrintJoystickMethod(self, 'V Min:', self.stick.GetVMin(), 'vmin')
PrintJoystickMethod(self, 'V Position:', self.stick.GetVPosition(), 'vpos')
# Print details about the joystick's Z-axis (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has Z?:', has_z, 'hasz')
if self.stick.HasZ() == True:
PrintJoystickMethod(self, 'Z Max:', self.stick.GetZMax(), 'zmax')
PrintJoystickMethod(self, 'Z Min:', self.stick.GetZMin(), 'zmin')
PrintJoystickMethod(self, 'Z Position:', self.stick.GetZPosition(), 'zpos')
# Print details about the joystick's POV switch (if applicable):
NewColumn()
PrintJoystickMethod(self, 'Has POV?:', has_pov, 'haspov')
if self.stick.HasPOV() == True:
PrintJoystickMethod(self, 'POV Position:', self.stick.GetPOVPosition(), 'povpos')
# Does the joystick offer 4-directional POV switch?
# (forward, backward, left, and right):
NewRow()
PrintJoystickMethod(self, 'Has POV4Dir?:', has_pov4dir, 'haspov4dir')
# Print details about the joystick's POV switch's
# continuous degree bearings (if applicable):
NewRow()
PrintJoystickMethod(self, 'Has POVCTS?:', has_povcts, 'haspovcts')
if self.stick.HasPOVCTS() == True:
PrintJoystickMethod(self, 'POVCTS Position:', self.stick.GetPOVCTSPosition(), 'povctspos')
# Print details about the joystick's polling:
NewRow()
PrintJoystickMethod(self, 'Polling (max):', self.stick.GetPollingMax(), 'pollingmin')
PrintJoystickMethod(self, 'Polling (min):', self.stick.GetPollingMin(), 'pollingmax')
# Print details about the joystick's X-Y postion:
NewRow()
PrintJoystickMethod(self, '(X,Y) Position:', self.stick.GetPosition(), 'xyposition')
# Print details about the joystick's X-axis:
NewRow()
PrintJoystickMethod(self, 'X (max):', self.stick.GetXMax(), 'xmax')
PrintJoystickMethod(self, 'X (min):', self.stick.GetXMin(), 'xmin')
# Print details about the joystick's Y-axis:
NewRow()
PrintJoystickMethod(self, 'Y (max):', self.stick.GetYMax(), 'ymax')
PrintJoystickMethod(self, 'Y (min):', self.stick.GetYMin(), 'ymin')
# Print joystick button press bitlist. More info on this in line 209
NewRow()
PrintJoystickMethod(self, 'Bitlist:', self.stick.GetNumberButtons(), 'bitlist')
# Create an array attribute on the joystick object to hold a StaticText
# object for each button state. This approach forgoes the PrintJoystickMethod
# function to make the button states easier to access/modify later
self.stick.butns = {}
for i in range(self.stick.GetNumberButtons()):
wx.StaticText(self, -1, 'Button '+str(i+1)+': ', pos=(X_LABEL,Y))
self.stick.butns[i] = wx.StaticText(self, -1, 'Off', pos=(X_QUANT,Y))
Y = Y + LINE_HEIGHT
# Bind joystick actions to an updater function:
self.Bind(wx.EVT_JOYSTICK_EVENTS, self.OnJoystick) # If anything happens w/joystick, call OnJoystick
self.stick.SetMovementThreshold(1) # Sets the movement threshold, the number of steps outside which the joystick is deemed to have moved.
# Create some simple gauge widgets for the joystick positions
NewColumn()
wx.StaticText(self, -1, 'X-axis Gauge: ', pos=(X_LABEL,Y))
self.xpos = wx.Gauge(self, range=self.stick.GetXMax(), pos=(X_QUANT, Y), size=(150, 5))
wx.StaticText(self, -1, 'Y-axis Gauge: ', pos=(X_LABEL,Y+35))
self.ypos = wx.Gauge(self, range=self.stick.GetYMax(), pos=(X_QUANT, Y+35), size=(150, 5))
wx.StaticText(self, -1, 'Z-axis Gauge: ', pos=(X_LABEL,Y+70))
self.zpos = wx.Gauge(self, range=self.stick.GetZMax(), pos=(X_QUANT, Y+70), size=(150, 5))
wx.StaticText(self, -1, 'Rudder Gauge: ', pos=(X_LABEL,Y+105))
self.rpos = wx.Gauge(self, range=self.stick.GetRudderMax(), pos=(X_QUANT, Y+105), size=(150, 5))
self.SetSize((750, 575))
self.SetTitle('Joystick Info')
self.Centre()
self.Show(True)
def OnJoystick(self, e):
# Update the values for xy position, rudder position, z-axis position and the button bitlist.
# These are all values that can change. The number of buttons, axes, etc - values that are
# fixed - are not updated for that reason. You can add more values to update by simply adding
# a new line of the format self.stick.ATTRNS.SetLabel(str(self.stick.METHOD())) where ATTRNS
# is the same one defined for the value during initialization, and METHOD is the wxPython
# method for collecting the value.
self.stick.xyposition.SetLabel(str(self.stick.GetPosition()))
self.stick.rudderpos.SetLabel( str(self.stick.GetRudderPosition()))
self.stick.zpos.SetLabel( str(self.stick.GetZPosition()))
self.stick.bitlist.SetLabel( str(self.stick.GetButtonState()))
# Checking the joystick button states is one of the trickier things to do in this example.
# The state of each button is read as 1 (pressed) or 0 (not pressed). The GetButtonState()
# method does NOT return an array or list as one might expect (supposedly it used to - the
# docs are unclear about this). The current version of wxPython returns an integer representing
# the binary state of all the buttons. So, let's say that your joystick has ten buttons. When
# none are pressed, wxPython treats this as a 10-bit value of 0000000000, and the GetButtonState
# method returns an integer of 0. Now, let's say you press a button that the joystick treats
# as 'button 4'; then wxPython treats this as 0000001000 (remember that binary is read from
# right to left!!), and GetButtonState returns a value of 8 (which is the decimal representation
# of 0000001000). Let's say you press buttons 2, 5, and 8 on your ten button joystick, wxPython
# reads this as 0010010010 and GetButtonState returns a value of 146 (146 in decimal is 0010010010
# in binary). Make sense?
# In the for loop below, we iterate the variable i over the range of the number of joystick buttons
# (i=0..BUTTONS-1). The line that reads "if (t & (1<<i)):" (taken from the wxPython demo) does some
# pretty important things. It's extremely concise, yet expresses a TON of logic necessary to make
# this all work. We'll go through it now:
# The single ampersand is a binary AND operator. This is different from a logical AND operator in
# that it combines the binary representations of two values by copying a bit only if the
# corresponding bits of the two operands is 1. What does that mean? If we have two binary values,
# 0101 and 1110, then (0101 & 1110) evaluates to 0100 (binary) or 4 (dec). Note that the only digit in which
# both operands share a 1 is the 3rd place from the right, hence the result of 0100. Further,
# the << is a bitwise left shift operator, which takes the binary representation of the left operand
# and shifts it to the left by a number of places determined by the operand on the right. So "3<<2"
# means "take the binary value of 3 (0011) and shift everything two places to the left," and results
# in a value of 1100, or 12. In the for loop below, this simple conditional statement handles all
# of the logic necessary for determining which button was pressed.
# If we return to the example of a 10-button joystick where buttons 2, 5, and 8 are simultaneously
# pressed, we can build a truth table for all of the possibilities generated by the for loop:
# i t 1<<i (t & (1<<i)) Evaluates as Button Number
# 0 0010010010 0000000001 0000000000 = 0 FALSE 1
# 1 0010010010 0000000010 0000000010 = 2 TRUE 2
# 2 0010010010 0000000100 0000000000 = 0 FALSE 3
# 3 0010010010 0000001000 0000000000 = 0 FALSE 4
# 4 0010010010 0000010000 0000010000 = 16 TRUE 5
# 5 0010010010 0000100000 0000000000 = 0 FALSE 6
# 6 0010010010 0001000000 0000000000 = 0 FALSE 7
# 7 0010010010 0010000000 0010000000 = 128 TRUE 8
# 8 0010010010 0100000000 0000000000 = 0 FALSE 9
# 9 0010010010 1000000000 0000000000 = 0 FALSE 10
# So we can see that this little conditional statement within the for loop correctly returns TRUE
# for any and all buttons that are being pressed. Using this, and the button array we initialized
# on the joystick object, we can now update each value to indicate whether it is being pressed ("On")
# or not ("Off")
t = self.stick.GetButtonState()
for i in range(self.stick.GetNumberButtons()):
if (t & (1<<i)):
self.stick.butns[i].SetLabel('On')
else:
self.stick.butns[i].SetLabel('Off')
# Update the widgets:
newx, newy = self.stick.GetPosition()
self.xpos.SetValue(newx)
self.ypos.SetValue(newy)
self.zpos.SetValue(self.stick.GetZPosition())
self.rpos.SetValue(self.stick.GetRudderPosition())
def main():
if haveJoystick == False:
print 'wx.Joystick is not available on this platform.'
return
else:
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
It should look something like this:
We now have a working example that covers every method of the wx.Joystick object, built from the ground up starting with basic building blocks. It’s not too complicated, which is good. As I stated up top, there are more complex examples involving the wx.Joystick object and lots of pretty pictures and graphics and widgets, but these seem to be more advanced, with a steep learning curve. I may post some more about this later on, but for now I hope you enjoyed my simple wx.Joystick tutorial!