#!/usr/bin/env python # coding: utf-8 # # xbmc-wiimote - XBMC event client for Wii remotes # # Copyright © 2011 Paul van Tilburg # # XBMC Wiimote Gateway is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as published # by the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General # Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program. If not, see . import cwiid import os import sys import getopt from time import * from xbmc.xbmcclient import XBMCClient, PacketBUTTON from threading import Thread, Event, Timer ### Resettable timer code, from http://code.activestate.com/recipes/577407/, ### with comments and debug statements removed. class TimerReset(Thread): def __init__(self, interval, function, args=[], kwargs={}): Thread.__init__(self) self.interval = interval self.function = function self.args = args self.kwargs = kwargs self.finished = Event() self.resetted = True def cancel(self): self.finished.set() def run(self): while self.resetted: self.resetted = False self.finished.wait(self.interval) if not self.finished.isSet(): self.function(*self.args, **self.kwargs) self.finished.set() def reset(self, interval=None): if interval: self.interval = interval self.resetted = True self.finished.set() self.finished.clear() ### end of included code class Pinger(Thread): def run(self): global xbmc while(True): sleep(55) xbmc.ping() # Default settings PROGRAM = "XBMC Wiimote Gateway" ICON = "/usr/share/pixmaps/xbmc/bluetooth.png" WIIMOTE_MAP = "CC:WiiRemote" WIIMOTE_CONVERT = { 1: cwiid.BTN_UP, 2: cwiid.BTN_DOWN, 3: cwiid.BTN_LEFT, 4: cwiid.BTN_RIGHT, 5: cwiid.BTN_A, 6: cwiid.BTN_B, 7: cwiid.BTN_MINUS, 8: cwiid.BTN_HOME, 9: cwiid.BTN_PLUS, 10: cwiid.BTN_1, 11: cwiid.BTN_2} # Extend XBMCClient with a method to directly send WiiMote button codes. def send_wiimote_button(self, code=0, map_name=WIIMOTE_MAP, queue=0, repeat=1): packet = PacketBUTTON(code=int(code), map_name=str(map_name), queue=queue, repeat=repeat) packet.send(self.sock, self.addr, self.uid) XBMCClient.send_wiimote_button = send_wiimote_button def rumble_signal(wm): wm.rumble = 1 for i in range(0, 3): sleep(0.1) wm.rumble = (i % 2) def convert_msg_to_buttons(mesg): buttons = [] for button in WIIMOTE_CONVERT.keys(): if mesg & WIIMOTE_CONVERT[button]: buttons.append(button) return buttons def handle_msg(mesg_list, time): global xbmc, wm, tim for mesg in mesg_list: if mesg[0] == cwiid.MESG_BTN: print("Handling keycode %d" % mesg[1]) tim.reset() if (mesg[1] == 0): xbmc.send_wiimote_button(0, repeat=0) else: # FIXME: How to send combined key presses? for button in convert_msg_to_buttons(mesg[1]): xbmc.send_wiimote_button(button) elif mesg[0] == cwiid.MESG_ERROR: wm.close() tim.cancel() if (mesg[1] == cwiid.ERROR_DISCONNECT): print("Wiimote disconnect to to power-off") xbmc.send_notification(PROGRAM, "Wiimote disconnected") elif (mesg[1] == cwiid.ERROR_COMM): print("Wiimote communication error!") xbmc.send_notification(PROGRAM, "Wiimote communication error!") else: print("Unhandled and ignored message type: %d" % mesg[0]) def timeout_msg(): print("Timeout due to inactivity!") def usage(): print("Usage: xbmc-wiimote [OPTION]...\n" "XBMC event client that receives input from a Wii remote via bluetooth and\n" "feeds them to XBMC.\n\n" "Available options:\n" " -b, --bt-address=ADDRESS\tBluetooth address Wii remote\n" " -l, --led-pattern=PATTERN\tLed patteren when connected (e.g. 1001)\n" " -t, --timeout=TIME\t\tAuto-disconnect timeout\n" " -h, --help\t\t\tDisplay this help and exit\n" "Setting the bluetooth address is mandatory.") def main(): # Define and get the commandline options. progname = os.path.basename(sys.argv[0]) try: opts, args = getopt.getopt(sys.argv[1:], "hb:l:t:", ["help", "bt-address=", "led-pattern=", "timeout="]) except getopt.GetoptError, err: print "%s: %s." % (progname, err) print "Try `%s --help` for more information." % progname sys.exit(2) # Option parsing and defaults. led_pattern = 0b1001 timeout = 120 bt_address = None for opt, arg in opts: if opt in ("-h", "--help"): usage() sys.exit() elif opt in ("-b", "--bt-address"): bt_address = arg elif opt in ("-l", "--led-pattern"): led_pattern = int(arg[::-1], 2) elif opt in ("-t", "--timeout"): timeout = int(arg) else: assert False, "unhandled option: %s" % opt if not bt_address: print "%s: the bluetooth address is not set!" % progname print "Try `%s --help` for more information." % progname sys.exit(3) # Create an XBMCClient object and connect. global xbmc, wm, tim xbmc = XBMCClient(PROGRAM, icon_file=ICON) xbmc.connect() # Start the pinger thread to frequently let XBMC know we are still here. ping = Pinger() ping.start() while(True): # The main loop. try: print("Press 1+2 on the Wiimote with address %s..." % bt_address) wm = cwiid.Wiimote(bt_address, cwiid.FLAG_REPEAT_BTN | cwiid.FLAG_MESG_IFC) except RuntimeError as error: print(error) continue # If a Wiimote is found, set the leds, rumble, register the # callback and start the timer. tim = TimerReset(timeout, timeout_msg) tim.start() wm.led = led_pattern wm.rpt_mode = cwiid.RPT_BTN wm.mesg_callback = handle_msg rumble_signal(wm) battperc = 100.0 * wm.state['battery'] / cwiid.BATTERY_MAX print("Wiimote connected (battery: %d%%)" % battperc) xbmc.send_notification(PROGRAM, "Wiimote connected (battery: %d%%)" % battperc) tim.join() # A timeout has occured (due to inactivity), close the connection to # the Wiimote. try: wm.close() except ValueError: # Connection was already closed. pass print("Wiimote disconnected due to timeout") #xbmc.send_notification(PROGRAM, "Wiimote disconnected") sleep(3) # Ok we're done, close the connection. xbmc.close() if __name__=="__main__": main()