Last active
December 17, 2015 19:19
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With the Bluetooth device I have at least, disconnecting the Wiimote with cwiid.Wiimote().close() hangs the Pi.