Skip to content

Instantly share code, notes, and snippets.

@nealtodd
Last active December 17, 2015 19:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nealtodd/5659321 to your computer and use it in GitHub Desktop.
Save nealtodd/5659321 to your computer and use it in GitHub Desktop.
Base class for detecting Wiimote button presses and triggering actions via methods. Written so that a subclass can be used to control a Raspberry Pi based robot.
import time
import cwiid
import threading
class WiimoteBase(object):
"""
Base class for sending Wiimote button presses to class
methods. Subclass to implement actions in the methods.
Also allows control of leds and rumble on the Wiimote.
"""
# Constants for the Wiimote buttons corresponding to
# their bitwise values (BTN_2=1, BTN_1=2, BTN_B=4, etc).
BTN_2 = cwiid.BTN_2
BTN_1 = cwiid.BTN_1
BTN_B = cwiid.BTN_B
BTN_A = cwiid.BTN_A
BTN_MINUS = cwiid.BTN_MINUS
BTN_HOME = cwiid.BTN_HOME
BTN_LEFT = cwiid.BTN_LEFT
BTN_RIGHT = cwiid.BTN_RIGHT
BTN_DOWN = cwiid.BTN_DOWN
BTN_UP = cwiid.BTN_UP
BTN_PLUS = cwiid.BTN_PLUS
buttons = (
BTN_2,
BTN_1,
BTN_B,
BTN_A,
BTN_MINUS,
BTN_HOME,
BTN_LEFT,
BTN_RIGHT,
BTN_DOWN,
BTN_UP,
BTN_PLUS
)
def __init__(self, wiimote, freq=2):
"""
Instanciate class by providing a wiimote instance
(created by cwiid.Wiimote()).
The frequency that the wiimote state is sampled can
be supplied, but also set at any time after as an
attribute.
"""
if not isinstance(wiimote, cwiid.Wiimote):
raise("cwiid.Wiimote object not supplied")
self._wiimote = wiimote
# switch wiimote to report button presses
self._wiimote.rpt_mode = cwiid.RPT_BTN
self.freq = freq
# initialise attributes
self._running = False
self._thread = None
self.led_state = 0 # integer state of the leds
self.rumble_state = False # Boolean state of the rumble
self._buttons_pressed_last = 0 # previous sample state of the
# buttons for change detection
self._buttons_sent = 0 # state of the last button changes sent
# to the action methods
def _run(self):
"""
Loop at frequency=freq reading the button presses and sending
state changes to the action methods.
Runs as a thread so that method calls on the class are not blocked
when the loop runs.
"""
while self._running:
# Sample the current state of the buttons
self._buttons_pressed = self._wiimote.state['buttons']
# Detect which buttons have changed since the last sample
# (by bitwise XORing with the previous sample state)
self.buttons_changed = self._buttons_pressed ^ self._buttons_pressed_last
# Loop over all the buttons
for button in self.buttons:
# Test if button was changed (via a bitwise AND)
if self.buttons_changed & button:
# Flip the bit corresponding to the button in _buttons_sent
# (i.e. if it was 0 (button up) then flip to 1 (button down)
# and vise-versa).
self._buttons_sent = self._buttons_sent ^ button
# Extract that bit as a Boolean indicating if the button
# was pressed (down) or released (up) in this sample).
is_down = bool(self._buttons_sent & button)
# Send the button state to the appropriate action method.
button == self.BTN_2 and self._btn_2(is_down)
button == self.BTN_1 and self._btn_1(is_down)
button == self.BTN_B and self._btn_b(is_down)
button == self.BTN_A and self._btn_a(is_down)
button == self.BTN_MINUS and self._btn_minus(is_down)
button == self.BTN_HOME and self._btn_home(is_down)
button == self.BTN_LEFT and self._btn_left(is_down)
button == self.BTN_RIGHT and self._btn_right(is_down)
button == self.BTN_DOWN and self._btn_down(is_down)
button == self.BTN_UP and self._btn_up(is_down)
button == self.BTN_PLUS and self._btn_plus(is_down)
# Update the last buttons pressed state ready for the next sample.
self._buttons_pressed_last = self._buttons_pressed
# Pause for the number of seconds = 1/freq.
time.sleep(1. / self.freq)
# Do-nothing actions corresponding to the buttons. Override these methods
# in Subclasses. All take a boolean indicating whether the button was pressed
# or released.
def _btn_2(self, is_down):
pass
def _btn_1(self, is_down):
pass
def _btn_b(self, is_down):
pass
def _btn_a(self, is_down):
pass
def _btn_minus(self, is_down):
pass
def _btn_home(self, is_down):
pass
def _btn_left(self, is_down):
pass
def _btn_right(self, is_down):
pass
def _btn_down(self, is_down):
pass
def _btn_up(self, is_down):
pass
def _btn_plus(self, is_down):
pass
def is_pressed(self, button):
"""
Utility method for determining whether a button
is currently pressed.
Can be used externally or for logic control within
action methods (e.g. to determine whether another
button was pressed at the same time).
"""
if button in self.buttons:
return bool(self._buttons_sent & button)
def start(self):
"""
Start the sampling loop to detect button presses
and dispatch to the action methods.
"""
if not (self._thread and self._thread.is_alive()):
self._start_seq()
self._running = True
# Sampling loop runs as a thread.
self._thread = threading.Thread(target=self._run)
self._thread.start()
def _start_seq(self):
"""
Signal to the wiimote that it is being responded to.
Blip the leds and rumble. Override to change behavior.
"""
for led in xrange(0, 4):
self._wiimote.led = 2**led
time.sleep(0.1)
self._wiimote.led = 0
self._pulse_rumble(0.25)
def stop(self):
"""
Stop the sampling loop.
"""
self._running = False
self._thread and self._thread.join()
self._thread = None
self._stop_seq()
def _stop_seq(self):
"""
Signal to the wiimote that it is not being responded to.
Double rumble. Override to change behavior.
"""
for _ in xrange(0, 2):
self._pulse_rumble(0.25)
def rumble(self, state=False):
"""
Toggle the wiimote rumble on (state=True) and off (state=False)
The rumble_state attribute can be used to determine the current
rumble state of the wiimote.
"""
self.rumble_state = state
self._wiimote.rumble = state
def leds(self, led1=None, led2=None, led3=None, led4=None):
"""
Toggle the wiimote leds on (ledN=True) and off (ledN=False).
The led_state attribute can be used to determine the current
(bitwise) led state of the wiimote (an integer between 0 and 15).
"""
self.led_state = (
(1 * led1 if led1 is not None else self.led_state & 1)
+ (2 * led2 if led2 is not None else self.led_state & 2)
+ (4 * led3 if led3 is not None else self.led_state & 4)
+ (8 * led4 if led4 is not None else self.led_state & 8)
)
self._wiimote.led = self.led_state
def _pulse_rumble(self, duration):
"""
Utility method to rumble the wiimote for a duration in seconds.
"""
self.rumble(True)
time.sleep(duration)
self.rumble(False)
time.sleep(duration)
def __repr__(self):
if self._thread:
if self._thread.is_alive():
_buttons_pressed = []
for button in self.buttons:
if self._buttons_pressed & button:
_buttons_pressed.append(button)
return "State: %s" % _buttons_pressed
else:
return "Thread dead"
else:
return "Not running"
class WiimoteConnect(WiimoteBase):
def __init__(self):
# initialise attributes
self._wiimote = None
self._running = False
self._thread = None
self.freq = 2
self.led_state = 0 # integer state of the leds
self.rumble_state = False # Boolean state of the rumble
self._buttons_pressed_last = 0 # previous sample state of the
# buttons for change detection
self._buttons_sent = 0 # state of the last button changes sent
# to the action methods
def connect(self):
if not self.is_connected():
print "Press 1 and 2 on the wiimote to connect"
try:
self._wiimote = cwiid.Wiimote()
except:
print "Connection failed"
else:
# switch wiimote to report button presses
self._wiimote.rpt_mode = cwiid.RPT_BTN
print "Connected"
else:
print "Already connected"
def disconnect(self):
if self.is_connected():
self.stop()
self._wiimote.close()
print "Disconnected"
def is_connected(self):
if self._wiimote is None:
return False
else:
try:
self._wiimote.state
except ValueError:
return False
else:
return True
def start(self):
if not self.is_connected():
print "Not yet connected"
else:
super(WiimoteConnect, self).start()
def __del__(self):
self.disconnect()
@nealtodd
Copy link
Author

nealtodd commented Jun 1, 2013

With the Bluetooth device I have at least, disconnecting the Wiimote with cwiid.Wiimote().close() hangs the Pi.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment