#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2008-2013 Team XBMC # # This program 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 2 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, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import time import sys import struct import math import binascii from bluetooth import set_l2cap_mtu SX_SELECT = 1 << 0 SX_L3 = 1 << 1 SX_R3 = 1 << 2 SX_START = 1 << 3 SX_DUP = 1 << 4 SX_DRIGHT = 1 << 5 SX_DDOWN = 1 << 6 SX_DLEFT = 1 << 7 SX_L2 = 1 << 8 SX_R2 = 1 << 9 SX_L1 = 1 << 10 SX_R1 = 1 << 11 SX_TRIANGLE = 1 << 12 SX_CIRCLE = 1 << 13 SX_X = 1 << 14 SX_SQUARE = 1 << 15 SX_POWER = 1 << 16 SX_LSTICK_X = 0 SX_LSTICK_Y = 1 SX_RSTICK_X = 2 SX_RSTICK_Y = 3 # (map, key, amount index, axis) keymap_sixaxis = { SX_X : ('XG', 'A', 0, 0), SX_CIRCLE : ('XG', 'B', 0, 0), SX_SQUARE : ('XG', 'X', 0, 0), SX_TRIANGLE : ('XG', 'Y', 0, 0), SX_DUP : ('XG', 'dpadup', 0, 0), SX_DDOWN : ('XG', 'dpaddown', 0, 0), SX_DLEFT : ('XG', 'dpadleft', 0, 0), SX_DRIGHT : ('XG', 'dpadright', 0, 0), SX_START : ('XG', 'start', 0, 0), SX_SELECT : ('XG', 'back', 0, 0), SX_R1 : ('XG', 'white', 0, 0), SX_R2 : ('XG', 'rightanalogtrigger', 6, 1), SX_L2 : ('XG', 'leftanalogtrigger', 5, 1), SX_L1 : ('XG', 'black', 0, 0), SX_L3 : ('XG', 'leftthumbbutton', 0, 0), SX_R3 : ('XG', 'rightthumbbutton', 0, 0), } # (data index, left map, left action, right map, right action) axismap_sixaxis = { SX_LSTICK_X : ('XG', 'leftthumbstickleft' , 'leftthumbstickright'), SX_LSTICK_Y : ('XG', 'leftthumbstickup' , 'leftthumbstickdown'), SX_RSTICK_X : ('XG', 'rightthumbstickleft', 'rightthumbstickright'), SX_RSTICK_Y : ('XG', 'rightthumbstickup' , 'rightthumbstickdown'), } # to make sure all combination keys are checked first # we sort the keymap's button codes in reverse order # this guarantees that any bit combined button code # will be processed first keymap_sixaxis_keys = keymap_sixaxis.keys() keymap_sixaxis_keys.sort() keymap_sixaxis_keys.reverse() def getkeys(bflags): keys = []; for k in keymap_sixaxis_keys: if (k & bflags) == k: keys.append(k) bflags = bflags & ~k return keys; def normalize(val): upperlimit = 65281 lowerlimit = 2 val_range = upperlimit - lowerlimit offset = 10000 val = (val + val_range / 2) % val_range upperlimit -= offset lowerlimit += offset if val < lowerlimit: val = lowerlimit if val > upperlimit: val = upperlimit val = ((float(val) - offset) / (float(upperlimit) - lowerlimit)) * 65535.0 if val <= 0: val = 1 return val def normalize_axis(val, deadzone): val = float(val) - 127.5 val = val / 127.5 if abs(val) < deadzone: return 0.0 if val > 0.0: val = (val - deadzone) / (1.0 - deadzone) else: val = (val + deadzone) / (1.0 - deadzone) return 65536.0 * val def normalize_angle(val, valrange): valrange *= 2 val = val / valrange if val > 1.0: val = 1.0 if val < -1.0: val = -1.0 return (val + 0.5) * 65535.0 def average(array): val = 0 for i in array: val += i return val / len(array) def smooth(arr, val): cnt = len(arr) arr.insert(0, val) arr.pop(cnt) return average(arr) def set_l2cap_mtu2(sock, mtu): SOL_L2CAP = 6 L2CAP_OPTIONS = 1 s = sock.getsockopt (SOL_L2CAP, L2CAP_OPTIONS, 12) o = list( struct.unpack ("HHHBBBH", s) ) o[0] = o[1] = mtu s = struct.pack ("HHHBBBH", *o) try: sock.setsockopt (SOL_L2CAP, L2CAP_OPTIONS, s) except: print("Warning: Unable to set mtu") class sixaxis(): def __init__(self, xbmc, control_sock, interrupt_sock): self.xbmc = xbmc self.num_samples = 16 self.sumx = [0] * self.num_samples self.sumy = [0] * self.num_samples self.sumr = [0] * self.num_samples self.axis_amount = [0, 0, 0, 0] self.released = set() self.pressed = set() self.pending = set() self.held = set() self.psflags = 0 self.psdown = 0 self.mouse_enabled = 0 set_l2cap_mtu2(control_sock, 64) set_l2cap_mtu2(interrupt_sock, 64) time.sleep(0.25) # If we ask to quickly here, it sometimes doesn't start # sixaxis needs this to enable it # 0x53 => HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE control_sock.send("\x53\xf4\x42\x03\x00\x00") data = control_sock.recv(1) # This command will turn on the gyro and set the leds # I wonder if turning on the gyro makes it draw more current?? # it's probably a flag somewhere in the following command # HID Command: HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUTPUT # HID Report:1 bytes = [0x52, 0x1] bytes.extend([0x00, 0x00, 0x00]) bytes.extend([0xFF, 0x72]) bytes.extend([0x00, 0x00, 0x00, 0x00]) bytes.extend([0x02]) # 0x02 LED1, 0x04 LED2 ... 0x10 LED4 # The following sections should set the blink frequency of # the leds on the controller, but i've not figured out how. # These values where suggested in a mailing list, but no explanation # for how they should be combined to the 5 bytes per led #0xFF = 0.5Hz #0x80 = 1Hz #0x40 = 2Hz bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED4 [0xff, 0xff, 0x10, 0x10, 0x10] bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED3 [0xff, 0x40, 0x08, 0x10, 0x10] bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED2 [0xff, 0x00, 0x10, 0x30, 0x30] bytes.extend([0xFF, 0x00, 0x01, 0x00, 0x01]) #LED1 [0xff, 0x00, 0x10, 0x40, 0x10] bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) bytes.extend([0x00, 0x00, 0x00, 0x00, 0x00]) control_sock.send(struct.pack("42B", *bytes)) data = control_sock.recv(1) def __del__(self): self.close() def close(self): for key in (self.held | self.pressed): (mapname, action, amount, axis) = keymap_sixaxis[key] self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) self.held = set() self.pressed = set() def process_socket(self, isock): data = isock.recv(50) if data == None: return False return self.process_data(data) def process_data(self, data): if len(data) < 3: return False # make sure this is the correct report if struct.unpack("BBB", data[0:3]) != (0xa1, 0x01, 0x00): return False if len(data) >= 48: v1 = struct.unpack("h", data[42:44]) v2 = struct.unpack("h", data[44:46]) v3 = struct.unpack("h", data[46:48]) else: v1 = [0,0] v2 = [0,0] v3 = [0,0] if len(data) >= 50: v4 = struct.unpack("h", data[48:50]) else: v4 = [0,0] ax = float(v1[0]) ay = float(v2[0]) az = float(v3[0]) rz = float(v4[0]) at = math.sqrt(ax*ax + ay*ay + az*az) bflags = struct.unpack(" 27: pressure = struct.unpack("BBBBBBBBBBBB", data[15:27]) else: pressure = [0,0,0,0,0,0,0,0,0,0,0,0,0] roll = -math.atan2(ax, math.sqrt(ay*ay + az*az)) pitch = math.atan2(ay, math.sqrt(ax*ax + az*az)) pitch -= math.radians(20); xpos = normalize_angle(roll, math.radians(30)) ypos = normalize_angle(pitch, math.radians(30)) axis = struct.unpack("BBBB", data[7:11]) return self.process_input(bflags, pressure, axis, xpos, ypos) def process_input(self, bflags, pressure, axis, xpos, ypos): xval = smooth(self.sumx, xpos) yval = smooth(self.sumy, ypos) analog = False for i in range(4): config = axismap_sixaxis[i] self.axis_amount[i] = self.send_singleaxis(axis[i], self.axis_amount[i], config[0], config[1], config[2]) if self.axis_amount[i] != 0: analog = True # send the mouse position to xbmc if self.mouse_enabled == 1: self.xbmc.send_mouse_position(xval, yval) if (bflags & SX_POWER) == SX_POWER: if self.psdown: if (time.time() - self.psdown) > 5: for key in (self.held | self.pressed): (mapname, action, amount, axis) = keymap_sixaxis[key] self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) raise Exception("PS3 Sixaxis powering off, user request") else: self.psdown = time.time() else: if self.psdown: self.mouse_enabled = 1 - self.mouse_enabled self.psdown = 0 keys = set(getkeys(bflags)) self.released = (self.pressed | self.held) - keys self.held = (self.pressed | self.held) - self.released self.pressed = (keys - self.held) & self.pending self.pending = (keys - self.held) for key in self.released: (mapname, action, amount, axis) = keymap_sixaxis[key] self.xbmc.send_button_state(map=mapname, button=action, amount=0, down=0, axis=axis) for key in self.held: (mapname, action, amount, axis) = keymap_sixaxis[key] if amount > 0: amount = pressure[amount-1] * 256 self.xbmc.send_button_state(map=mapname, button=action, amount=amount, down=1, axis=axis) for key in self.pressed: (mapname, action, amount, axis) = keymap_sixaxis[key] if amount > 0: amount = pressure[amount-1] * 256 self.xbmc.send_button_state(map=mapname, button=action, amount=amount, down=1, axis=axis) if analog or keys or self.mouse_enabled: return True else: return False def send_singleaxis(self, axis, last_amount, mapname, action_min, action_pos): amount = normalize_axis(axis, 0.30) if last_amount < 0: last_action = action_min elif last_amount > 0: last_action = action_pos else: last_action = None if amount < 0: new_action = action_min elif amount > 0: new_action = action_pos else: new_action = None if last_action and new_action != last_action: self.xbmc.send_button_state(map=mapname, button=last_action, amount=0, axis=1) if new_action and amount != last_amount: self.xbmc.send_button_state(map=mapname, button=new_action, amount=abs(amount), axis=1) return amount