# LICENSE: MIT (X11) License which follows: # # Copyright (c) 2008 Stephane Duchesneau # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # Modified by Pere Negre and Pietro Pilolli # # import bluetooth import code from datetime import datetime from math import atan, asin, acos #ZDR import threading from time import time,sleep import time def i2bs(val): lst = [] while val: lst.append(val&0xff) val = val >> 8 lst.reverse() return lst class WiimoteState: Battery = None class ButtonState: A = False B = False Down = False Home = False Left = False Minus = False One = False Plus = False Right = False Two = False Up = False class GyroState: calibrated = False int_yaw = 0 int_roll = 0 int_pitch = 0 zero_yaw = 8192 zero_roll = 8159 zero_pitch = 8208 class AccelState: X = 0 Y = 0 Z = 0 RawX = 0 RawY = 0 RawZ = 0 zeroX = 0 zeroY = 0 zeroZ = 0 class IRState: RawX1 = 0 RawX2 = 0 RawX3 = 0 RawX4 = 0 RawY1 = 0 RawY2 = 0 RawY3 = 0 RawY4 = 0 Found1 = 0 Found2 = 0 Found3 = 0 Found4 = 0 Size1 = 0 Size2 = 0 Size3 = 0 Size4 = 0 X1 = X2 = X3 = X4 = 0.0 Y1 = Y2 = Y3 = Y4 = 0.0 #Mode = None MidX = 0 MidY = 0 RawMidX = 0 RawMidY = 0 class LEDState: LED1 = False LED2 = False LED3 = False LED4 = False class Parser: """ Sets the values contained in a signal """ A = 0x0008 B = 0x0004 Down = 0x0400 Home = 0x0080 Left = 0x0100 Minus = 0x0010 One = 0x0002 Plus = 0x1000 Right = 0x0200 Two = 0x0001 Up = 0x0800 # ZDR gyro_deg_s = 1./8192./200. def parseButtons(self,signal, ButtonState): #signal is 16bit long intl ButtonState.A = bool(signal&self.A) ButtonState.B = bool(signal&self.B) ButtonState.Down = bool(signal&self.Down) ButtonState.Home = bool(signal&self.Home) ButtonState.Left = bool(signal&self.Left) ButtonState.Minus = bool(signal&self.Minus) ButtonState.One = bool(signal&self.One) ButtonState.Plus = bool(signal&self.Plus) ButtonState.Right = bool(signal&self.Right) ButtonState.Two = bool(signal&self.Two) ButtonState.Up = bool(signal&self.Up) #ZDR def parseAccelerometer(self, signal, AccelState): AccelState.RawX = signal[0] - 512 AccelState.RawY = signal[1] - 512 AccelState.RawZ = signal[2] - 512 #ZDR def parseGyro(self, signal, slow, GyroState): if GyroState.calibrated==False: return yaw_slow = 440 if slow[0]==1 else 2000 roll_slow = 440 if slow[1]==1 else 2000 pitch_slow = 440 if slow[2]==1 else 2000 GyroState.int_yaw += (signal[0] - GyroState.zero_yaw)*yaw_slow # *self.gyro_deg_s GyroState.int_roll += (signal[1] - GyroState.zero_roll)*roll_slow GyroState.int_pitch += (signal[2] - GyroState.zero_pitch)*pitch_slow #print '%d\t\t%d\t\t%d' % (GyroState.int_yaw, GyroState.int_roll, GyroState.int_pitch ) def parseIR(self,signal,irstate): irstate.RawX1 = signal[0] + ((signal[2] & 0x30) >>4 << 8) irstate.RawY1 = signal[1] + (signal[2] >> 6 << 8) irstate.Size1 = signal[2] & 0x0f if irstate.RawY1 == 1023: irstate.Found1 = False else: irstate.Found1 = True irstate.RawX2 = signal[3] + ((signal[5] & 0x30) >>4 << 8) irstate.RawY2 = signal[4] + (signal[5] >> 6 << 8) irstate.Size2 = signal[5] & 0x0f if irstate.RawY2 == 1023: irstate.Found2 = False else: irstate.Found2 = True irstate.RawX3 = signal[6] + ((signal[8] & 0x30) >>4 << 8) irstate.RawY3 = signal[7] + (signal[8] >> 6 << 8) irstate.Size3 = signal[8] & 0x0f if irstate.RawY3 == 1023: irstate.Found3 = False else: irstate.Found3 = True irstate.RawX4 = signal[9] + ((signal[11] & 0x30) >>4 << 8) irstate.RawY4 = signal[10] + (signal[11] >> 6 << 8) irstate.Size4 = signal[11] & 0x0f if irstate.RawY4 == 1023: irstate.Found4 = False else: irstate.Found4 = True if irstate.Found1: if irstate.Found2: irstate.RawMidX = (irstate.RawX1 + irstate.RawX2) / 2 irstate.RawMidY = (irstate.RawY1 + irstate.RawY2) / 2 else: irstate.RawMidX = irstate.RawX1 irstate.RawMidY = irstate.RawY1 irstate.MidX = float(irstate.RawMidX) / 1024 irstate.MidY = float(irstate.RawMidY) / 768 else: irstate.MidX = irstate.MidY = 0 class Setter: """The opposite from the Parser class: returns the signal needed to set the values in the wiimote""" LED1 = 0x10 LED2 = 0x20 LED3 = 0x40 LED4 = 0x80 def SetLEDs(self,ledstate): signal = 0 if ledstate.LED1: signal += self.LED1 if ledstate.LED2: signal += self.LED2 if ledstate.LED3: signal += self.LED3 if ledstate.LED4: signal += self.LED4 return signal class InputReport: Buttons = 2 #2 to 8 not implemented yet !!! only IR is implemented Status = 4 ReadData = 5 ButtonsExtension = 6 class Wiimote(threading.Thread): state = None running = False WiimoteState = WiimoteState InputReport = InputReport gyroCalibration = [] # ZDR packet_number = 0 packet_time = 0 def __init__(self): threading.Thread.__init__(self) self.parser = Parser() self.setter = Setter() self.IRCallback = None def Connect(self, device): self.bd_addr = device[0] self.name = device[1] self.controlsocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) try: self.controlsocket.connect((self.bd_addr,17)) except: return False self.datasocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) self.datasocket.connect((self.bd_addr,19)) self.sendsocket = self.controlsocket self.CMD_SET_REPORT = 0x52 if self.name == "Nintendo RVL-CNT-01-TR": self.CMD_SET_REPORT = 0xa2 self.sendsocket = self.datasocket try: self.datasocket.settimeout(1) except NotImplementedError: print "socket timeout not implemented with this bluetooth module" print "Connected to ", self.bd_addr self._get_battery_status() self.start_time = datetime.now() #self.f = open('wiimote.log', 'w') #self.measurement_ended = False self.start() #start this thread return True def char_to_binary_string(self,char): ascii = ord(char) bin = [] while (ascii > 0): if (ascii & 1) == 1: bin.append("1") else: bin.append("0") ascii = ascii >> 1 bin.reverse() binary = "".join(bin) zerofix = (8 - len(binary)) * '0' return zerofix + binary def SetLEDs(self, led1,led2,led3,led4): self.WiimoteState.LEDState.LED1 = led1 self.WiimoteState.LEDState.LED2 = led2 self.WiimoteState.LEDState.LED3 = led3 self.WiimoteState.LEDState.LED4 = led4 self._send_data((0x11,self.setter.SetLEDs(self.WiimoteState.LEDState))) def run(self): print "starting" #i = 0 self.running = True while self.running: try: x= map(ord,self.datasocket.recv(32)) #t = datetime.now() - self.start_time #self.packet_time = t.seconds * 1000.0 + t.microseconds / 1000.0 #if t_millis <= 60000.0: # self.f.write("Packet %i Zeit %f\n" % (self.i, t_millis)) #elif not self.measurement_ended: # self.f.close() # print "Measurement ended" # self.measurement_ended = True #print "New Data %i" % self.i #self.packet_number = self.packet_number + 1 except bluetooth.BluetoothError: continue self.state = "" for each in x[:17]: self.state += self.char_to_binary_string(chr(each)) + " " if len(x) >= 4: self.parser.parseButtons((x[2]<<8) + x[3], self.WiimoteState.ButtonState) #ZDR xyz = x[4:7] # 0x31: core buttons and accelerometer # 0x35: buttens accelerometer and extension data if len(x)>4 and (x[1]==0x31 or x[1]==0x35): # assume 10 bit precision ax,ay,az = [i<<2 for i in x[4:7]] #print ax-512,ay-512,az-512 # add LSB from button values ax += x[2]>>5 & 0x3 ay += x[3]>>4 & 0x2 az += x[3]>>5 & 0x2 self.parser.parseAccelerometer([ax,ay,az], self.WiimoteState.AccelState) # ZDR gyroscope data if len(x)>7 and x[1]==0x35: data = x[7:13] yaw = (data[3] & 0xfc)<<6 | data[0] roll = (data[4] & 0xfc)<<6 | data[1] pitch = (data[5] & 0xfc)<<6 | data[2] slow_mode_yaw = (data[3] & 0x2)>>1 slow_mode_pitch = (data[3] & 0x1) slow_mode_roll = (data[4] & 0x2)>>1 clen = 3000.0 self.WiimoteState.GyroState.calibrated = True if self.WiimoteState.GyroState.calibrated==False and len(self.gyroCalibration)!=clen: self.gyroCalibration.append([yaw,roll,pitch]) if len(self.gyroCalibration)==clen: # gc = self.gyroCalibration self.WiimoteState.GyroState.calibrated = True self.WiimoteState.GyroState.zero_yaw = sum([y for y,r,p in self.gyroCalibration])/clen self.WiimoteState.GyroState.zero_roll = sum([r for y,r,p in self.gyroCalibration])/clen self.WiimoteState.GyroState.zero_pitch = sum([p for y,r,p in self.gyroCalibration])/clen print self.WiimoteState.GyroState.zero_yaw print self.WiimoteState.GyroState.zero_roll print self.WiimoteState.GyroState.zero_pitch code.interact(local=locals()) #print yaw, roll, pitch, slow_mode_yaw, slow_mode_roll, slow_mode_pitch, self.parser.parseGyro([yaw,roll,pitch], [slow_mode_yaw,slow_mode_roll,slow_mode_pitch], self.WiimoteState.GyroState) #print roll #if len(x) >= 19: # self.parser.parseIR(x[7:19],self.WiimoteState.IRState) # self.doIRCallback() #sleep(0.01) self.datasocket.close() self.controlsocket.close() print "Bluetooth socket closed succesfully." self.Dispose() print "stopping" def Dispose(self): self.Disconnect() def Disconnect(self): self.running = False self.WiimoteState.Battery = None def join(self):#will be called last... self.Dispose() def _send_data(self,data): str_data = "" for each in data: str_data += chr(each) self.sendsocket.send(chr(self.CMD_SET_REPORT) + str_data) def _write_to_mem(self, address, value): val = i2bs(value) val_len=len(val) val += [0]*(16-val_len) msg = [0x16] + i2bs(address) + [val_len] +val self._send_data(msg) def SetRumble(self,on): if on: self._send_data((0x11,0x01)) else: self._send_data((0x11,0x00)) def activate_IR(self, sens=6): self._send_data(i2bs(0x120033)) #mode IR self._send_data(i2bs(0x1304))#enable transmission self._send_data(i2bs(0x1a04))#enable transmission self.setIRSensitivity(sens) # ZDR def SetAccelerometerMode(self, mode=0x0): # see: http://wiibrew.org/wiki/Wiimote#Data_Reporting # set data reporting mode 0x31: (a1) 31 BB BB AA AA AA self._send_data((0x12,0x0,0x31)) # ZDR def SetGyroMode(self): """ initialize motion plus the extension """ self._write_to_mem(0x4A600FE,0x04) self._send_data((0x12,0x4,0x35)) # ZDR def getAccelState(self): return (self.WiimoteState.AccelState.RawX - WiimoteState.AccelState.zeroX, self.WiimoteState.AccelState.RawY - WiimoteState.AccelState.zeroY, self.WiimoteState.AccelState.RawZ - WiimoteState.AccelState.zeroZ) # ZDR def calibrateAccelerometer(self): WiimoteState.AccelState.zeroX = self.WiimoteState.AccelState.RawX WiimoteState.AccelState.zeroY = self.WiimoteState.AccelState.RawY WiimoteState.AccelState.zeroZ = self.WiimoteState.AccelState.RawZ # ZDR def getGyroState(self, deg_scale=1./8192./200): return (self.WiimoteState.GyroState.int_yaw*deg_scale, self.WiimoteState.GyroState.int_roll*deg_scale, self.WiimoteState.GyroState.int_pitch*deg_scale) def calibrateGyro(self, yaw=None, roll=None, pitch=None): if yaw is not None: self.WiimoteState.GyroState.int_yaw = yaw if roll is not None: self.WiimoteState.GyroState.int_roll = roll if pitch is not None: self.WiimoteState.GyroState.int_pitch = pitch def setIRSensitivity(self, n): if n < 1 or n > 6: return self._write_to_mem(0x04b00030,0x08) time.sleep(0.1) if n == 1: self._write_to_mem(0x04b00000,0x0200007101006400fe) time.sleep(0.1) self._write_to_mem(0x04b0001a,0xfd05) elif n == 2: self._write_to_mem(0x04b00000,0x0200007101009600b4) time.sleep(0.1) self._write_to_mem(0x04b0001a,0xb304) elif n == 3: self._write_to_mem(0x04b00000,0x020000710100aa0064) time.sleep(0.1) self._write_to_mem(0x04b0001a,0x6303) elif n == 4: self._write_to_mem(0x04b00000,0x020000710100c80036) time.sleep(0.1) self._write_to_mem(0x04b0001a,0x3503) elif n == 5: self._write_to_mem(0x04b00000,0x070000710100720020) time.sleep(0.1) self._write_to_mem(0x04b0001a,0x1f03) # MAX elif n == 6: self._write_to_mem(0x04b00000,0x000000000000900041) time.sleep(0.1) self._write_to_mem(0x04b0001a,0x4000) time.sleep(0.1) self._write_to_mem(0x04b00033,0x33) def _get_battery_status(self): self._send_data((0x15,0x00)) self.running2 = True while self.running2: try: x= map(ord,self.datasocket.recv(32)) except bluetooth.BluetoothError: continue self.state = "" for each in x[:17]: if len(x) >= 7: self.running2 = False battery_level = x[7] self.WiimoteState.Battery = float(battery_level) / float(208) def setIRCallBack(self, func): self.IRCallback = func def doIRCallback(self): if self.IRCallback == None: return irstate = self.WiimoteState.IRState if irstate.Found1: self.IRCallback(irstate.RawX1, irstate.RawY1) elif irstate.Found2: self.IRCallback(irstate.RawX2, irstate.RawY2) elif irstate.Found3: self.IRCallback(irstate.RawX3, irstate.RawY3) elif irstate.Found4: self.IRCallback(irstate.RawX4, irstate.RawY4) if __name__ == "__main__": device = ('40:F4:07:C2:08:B9', 'Nintendo RVL-CNT-01-TR') print device connected = False wiimote = Wiimote() print "Press SYNC on wiimote to make it discoverable" while (not connected): connected = wiimote.Connect(device) leds = [False]*4 for i in xrange(16): leds[i&0x3] = not leds[i&0x3] wiimote.SetLEDs(*leds); #time.sleep(0.1) #ZDR # see: http://wiibrew.org/wiki/Wiimote#Data_Reporting # If bit 2 (0x04) is set, the Wii Remote will send reports # whether there has been any change to the data or # not. Otherwise, the Wii Remote will only send an output # report when the data has changed. set data reporting mode # 0x31: (a1) 31 BB BB AA AA AA wiimote._send_data((0x12,0x4,0x31)) #time.sleep(0.1) print wiimote.WiimoteState.AccelState.RawX, wiimote.WiimoteState.AccelState.RawY, wiimote.WiimoteState.AccelState.RawZ, wiimote.WiimoteState.ButtonState.A # 0x35: Core Buttons and Accelerometer with 16 Extension Bytes # (a1) 35 BB BB AA AA AA EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE EE # init motion plus: the extension is initialised by writing 0x55 to 0x(4)a600f0 wiimote._write_to_mem(0x4A600FE,0x04) wiimote._send_data((0x12,0x4,0x35)) code.interact(local=locals()) while 1: print "%.2f\t%.2f\t%.2f" % wiimote.getGyroState() if wiimote.WiimoteState.ButtonState.A: wiimote.calibrateGyro(yaw=0, roll=0) #time.sleep(0.1) # wiimote._send_data((0x12,0x4,0x30)) # wiimote.activate_IR() # while 1: # time.sleep(0.1) # #print wiimote.state # print wiimote.WiimoteState.ButtonState.A, wiimote.WiimoteState.ButtonState.B, wiimote.WiimoteState.ButtonState.Up, wiimote.WiimoteState.ButtonState.Down, wiimote.WiimoteState.ButtonState.Left, wiimote.WiimoteState.ButtonState.Right, wiimote.WiimoteState.ButtonState.Minus, wiimote.WiimoteState.ButtonState.Plus, wiimote.WiimoteState.ButtonState.Home, wiimote.WiimoteState.ButtonState.One, wiimote.WiimoteState.ButtonState.Two, wiimote.WiimoteState.IRState.RawX1, wiimote.WiimoteState.IRState.RawY1, wiimote.WiimoteState.IRState.Size1, wiimote.WiimoteState.IRState.RawX2, wiimote.WiimoteState.IRState.RawY2, wiimote.WiimoteState.IRState.Size2 # #print wiimote.IRState.Found1