From 2b0327615944587405b1da3e29412943063698a0 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 1 Dec 2018 17:56:31 +0100 Subject: [PATCH] #46: Added Wiimote support --- platypush/backend/wiimote.py | 116 +++++++++++++++++++++++++++++ platypush/message/event/wiimote.py | 14 ++++ platypush/plugins/wiimote.py | 77 +++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 platypush/backend/wiimote.py create mode 100644 platypush/message/event/wiimote.py create mode 100644 platypush/plugins/wiimote.py diff --git a/platypush/backend/wiimote.py b/platypush/backend/wiimote.py new file mode 100644 index 00000000..4a20ad18 --- /dev/null +++ b/platypush/backend/wiimote.py @@ -0,0 +1,116 @@ +""" +.. moduleauthor:: Fabio Manganiello +""" + +import cwiid +import re +import time + +from platypush.backend import Backend +from platypush.message import Message +from platypush.message.event.wiimote import WiimoteEvent +from platypush.message.request import Request + + +class WiimoteBackend(Backend): + """ + Backend to communicate with a Nintendo WiiMote controller + + Triggers: + + * :class:`platypush.message.event.Wiimote.WiimoteEvent` \ + when the state of the Wiimote (battery, buttons, acceleration etc.) changes + + Requires: + + * **python3-wiimote** (follow instructions at https://github.com/azzra/python3-wiimote) + """ + + _wiimote = None + + + def msg_callback(self): + def _callback(msg_list, timestamp): + print(msg_list) + + return _callback + + def get_wiimote(self): + if not self._wiimote: + self._wiimote = cwiid.Wiimote() + self._wiimote.mesg_callback = self.msg_callback() + # self._wiimote.enable(cwiid.FLAG_MESG_IFC | cwiid.FLAG_MOTIONPLUS) + self._wiimote.enable(cwiid.FLAG_MOTIONPLUS) + self._wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS + + self.logger.info('WiiMote connected') + self._wiimote.led = 1 + self._wiimote.rumble = True + time.sleep(0.5) + self._wiimote.rumble = False + + return self._wiimote + + def get_state(self): + wm = self.get_wiimote() + state = wm.state + parsed_state = {} + + # Get buttons + all_btns = [attr for attr in dir(cwiid) if attr.startswith('BTN_')] + parsed_state['buttons'] = { btn: True for btn in all_btns + if state.get('buttons', 0) & getattr(cwiid, btn) != 0 } + + # Get LEDs + all_leds = [attr for attr in dir(cwiid) if re.match('LED\d_ON', attr)] + parsed_state['led'] = { led[:4]: True for led in all_leds + if state.get('leds', 0) & getattr(cwiid, led) != 0 } + + # Get errors + all_errs = [attr for attr in dir(cwiid) if attr.startswith('ERROR_')] + parsed_state['error'] = { err: True for err in all_errs + if state.get('errs', 0) & getattr(cwiid, err) != 0 } + + parsed_state['battery'] = round(state.get('battery', 0)/cwiid.BATTERY_MAX, 3) + parsed_state['rumble'] = bool(state.get('rumble', 0)) + + if 'acc' in state: + parsed_state['acc'] = tuple(int(acc/5)*5 for acc in state['acc']) + + if 'motionplus' in state: + parsed_state['motionplus'] = { + 'angle_rate': tuple(int(angle/100) for angle + in state['motionplus']['angle_rate']), + 'low_speed': state['motionplus']['low_speed'], + } + + return parsed_state + + + def run(self): + super().run() + connection_attempts = 0 + last_state = {} + + while not self.should_stop(): + try: + state = self.get_state() + changed_state = { k: state[k] for k in state.keys() + if state[k] != last_state.get(k) } + + if changed_state: + self.bus.post(WiimoteEvent(**changed_state)) + connection_attempts = 0 + last_state = state + time.sleep(0.1) + except RuntimeError as e: + if type(e) == RuntimeError and str(e) == 'Error opening wiimote connection': + if connection_attempts == 0: + self.logger.info('Press 1+2 to pair your WiiMote controller') + else: + self.logger.exception(e) + self._wiimote = None + connection_attempts += 1 + +# vim:sw=4:ts=4:et: + diff --git a/platypush/message/event/wiimote.py b/platypush/message/event/wiimote.py new file mode 100644 index 00000000..2010422c --- /dev/null +++ b/platypush/message/event/wiimote.py @@ -0,0 +1,14 @@ +from platypush.message.event import Event + + +class WiimoteEvent(Event): + """ + Event triggered upon Wiimote event + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +# vim:sw=4:ts=4:et: + diff --git a/platypush/plugins/wiimote.py b/platypush/plugins/wiimote.py new file mode 100644 index 00000000..e6af6d62 --- /dev/null +++ b/platypush/plugins/wiimote.py @@ -0,0 +1,77 @@ +""" +.. moduleauthor:: Fabio Manganiello +""" + +import time + +from platypush.context import get_backend +from platypush.plugins import Plugin, action + +class WiimotePlugin(Plugin): + """ + WiiMote plugin. + A wrapper around the :mod:`platypush.backend.wiimote` backend to + programmatically control a Nintendo WiiMote. + + It requires the WiiMote backend to be enabled. + """ + + @classmethod + def _get_wiimote(cls): + return get_backend('wiimote').get_wiimote() + + + @action + def connect(self): + """ + Connects to the WiiMote + """ + self._get_wiimote() + + + @action + def close(self): + """ + Closes the connection with the WiiMote + """ + self._get_wiimote().close() + + + @action + def rumble(self, secs): + """ + Rumbles the controller for the specified number of seconds + """ + wm = self._get_wiimote() + wm.rumble = True + time.sleep(secs) + wm.rumble = False + + + @action + def state(self): + """ + Return the state of the controller + """ + return get_backend('wiimote').get_state() + + + @action + def set_leds(self, leds): + """ + Set the LEDs state on the controller + + :param leds: Iterable with the new states to be applied to the LEDs. Example: [1, 0, 0, 0] or (False, True, False, False) + :type leds: list + """ + + new_led = 0 + for i, led in enumerate(leds): + if led: + new_led |= (1 << i) + + self._get_wiimote().led = new_led + + +# vim:sw=4:ts=4:et: +