Wiiewer

From WiiLi

Jump to: navigation, search

Wiiewer is a very simple Python script that plots the motion sensor data. I found WMD very powerful but also very difficult to modify and adapt. Wiiewer is just a short example that you can reuse for your own purposes.

Contents

[edit] Screenshot

Image:Wiiewer.jpg

[edit] Code

#!/usr/bin/python

# Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
#
#    This program is free software. It comes without any warranty, to 
#    the extent permitted by applicable law. You can redistribute it
#    and/or modify it under the terms of the Do What The Fuck You Want
#    To Public License, Version 2, as published by Sam Hocevar. See
#    http://sam.zoy.org/wtfpl/COPYING for more details.

import pygame
from pygame.locals import QUIT
from bluetooth import BluetoothSocket, BluetoothError, L2CAP
import thread, time

WIDTH = 512
HEIGHT = 256 # Don't change this one
ADDRESS = '00:19:1D:88:56:BA' # Change this one, of course

# Listening thread
def listener():
    global connected, sensor
    fdin.settimeout(0.1)
    while connected == 1:
        try:
            msg = fdin.recv(23)
        except BluetoothError:
            continue
        if len(msg) >= 7:
            for c in range(3):
                sensor[c] = ord(msg[4 + c])
    fdin.close()
    fdout.close()
    connected = -1

# Try to connect to Wiimote
print 'connecting to Wiimote ' + ADDRESS + ', please press buttons 1 and 2'
fdin = BluetoothSocket(L2CAP)
fdin.connect((ADDRESS, 0x13))
fdout = BluetoothSocket(L2CAP)
fdout.connect((ADDRESS, 0x11))
if not fdin or not fdout:
    raise 'could not connect to Wiimote, check the address'
# Open window
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT), 0)
pygame.display.set_caption('Wiiewer')
# Run listener
old = [127] * 3
sensor = [127] * 3
connected = 1
thread.start_new_thread(listener, ())
fdout.send("\x52\x12\x00\x31")
# Main display loop
while connected == 1:
    pixels = pygame.surfarray.pixels3d(window)
    for c in range(3):
        for t in range(min(old[c], sensor[c]) + 1, max(old[c], sensor[c])):
            pixels[WIDTH - 3][t][c] = 255
        pixels[WIDTH - 2][sensor[c]][c] = 255
        old[c] = sensor[c]
    del pixels
    window.unlock()
    window.blit(window, (-1, 0))
    pygame.display.flip()
    for ev in pygame.event.get():
        if ev.type == QUIT:
            connected = 0
    time.sleep(0.01)
while connected == 0:
    time.sleep(0.01)
pygame.display.quit()


[edit] With camera tracking

I kinda hacked around a bit with the above to work with the IR camera as well. It's horrible code, I know, but it does work.

#!/usr/bin/python

# Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
#
# Camera code hacked in by Pete Ryland <pdr@pdr.cx> on 9 April 2008.
#
#    This program is free software. It comes without any warranty, to
#    the extent permitted by applicable law. You can redistribute it
#    and/or modify it under the terms of the Do What The Fuck You Want
#    To Public License, Version 2, as published by Sam Hocevar. See
#    http://sam.zoy.org/wtfpl/COPYING for more details.

import pygame
from pygame.locals import QUIT
from bluetooth import BluetoothSocket, BluetoothError, L2CAP
import thread, time

WIDTH = 512
HEIGHT = 256 # Don't change this one
ADDRESS = '00:1E:35:E3:0A:F8' # Change this one, of course

# Listening thread
def listener():
    global connected, sensor, buttons, last_buttons, ir
    fdin.settimeout(0.1)
    while connected == 1:
        try:
            msg = fdin.recv(23)
        except BluetoothError:
            continue
        if ord(msg[1]) == 0x33:
          #print 'recv' + ' '.join([ord(x).__hex__() for x in msg])
          buttons = (ord(msg[2]) & 0x1f) + ((ord(msg[3]) & 0x9f) << 5)
          if buttons != last_buttons:
            button_string = ''
            for i in xrange(13):
              if buttons & (1 << i):
                button_string += '<>v^+21BA-??H'[i]
            print button_string
            last_buttons = buttons
          if len(msg) >= 7:
              for c in range(3):
                  sensor[c] = ord(msg[4 + c])
          for i in xrange(4):
            ir[i].x = ord(msg[7+i*3]) + ((ord(msg[9+i*3]) & 0x30) << 4)
            ir[i].y = ord(msg[8+i*3]) + ((ord(msg[9+i*3]) & 0xc0) << 2)
            ir[i].s = ord(msg[9+i*3]) & 0x0f
            #print i, ir[i].x, ir[i].y, ir[i].s
    fdin.close()
    fdout.close()
    connected = -1

# Try to connect to Wiimote
print 'connecting to Wiimote ' + ADDRESS + ', please press buttons 1 and 2'
fdin = BluetoothSocket(L2CAP)
fdin.connect((ADDRESS, 0x13))
fdout = BluetoothSocket(L2CAP)
fdout.connect((ADDRESS, 0x11))
if not fdin or not fdout:
    raise 'could not connect to Wiimote, check the address'
# Open window
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT), 0)
pygame.display.set_caption('Wiiewer')
# Run listener
old = [127] * 3
sensor = [127] * 3
buttons = 0
last_buttons = 0
class Ir:
  x, y, s = 0, 0, 0
ir = [Ir(), Ir(), Ir(), Ir()]
last_ir = [Ir(), Ir(), Ir(), Ir()]
connected = 1
thread.start_new_thread(listener, ())
fdout.send("\x52\x12\x00\x33")
fdout.send("\x52\x13\x04")
fdout.send("\x52\x1a\x04")
fdout.send("\x52\x16\x04\xb0\x00\x30\x01\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
fdout.send("\x52\x16\x04\xb0\x00\x00\x09\x02\x00\x00\x71\x01\x00\x64\x00\xfe\x00\x00\x00\x00\x00\x00\x00")
fdout.send("\x52\x16\x04\xb0\x00\x1a\x02\xfd\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
fdout.send("\x52\x16\x04\xb0\x00\x33\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
fdout.send("\x52\x16\x04\xb0\x00\x30\x01\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
# Main display loop
while connected == 1:
    pixels = pygame.surfarray.pixels3d(window)
    for i in range(4):
      if last_ir[i].x != 1023 and last_ir[i].y != 1023 and last_ir[i].s != 15:
        (real_x, real_y) = ((((1023-last_ir[i].x)*WIDTH) >> 10), (last_ir[i].y*HEIGHT) >> 10)
        if real_x >= 0:
          pixels[real_x-1][real_y][0] ^= 255
          pixels[real_x-1][real_y][1] ^= 255
          pixels[real_x-1][real_y][2] ^= 255
    for c in range(3):
        for t in range(min(old[c], sensor[c]) + 1, max(old[c], sensor[c])):
            pixels[WIDTH - 3][t][c] = 255
        pixels[WIDTH - 2][sensor[c]][c] = 255
        old[c] = sensor[c]
    for i in range(4):
      if ir[i].x != 1023 and ir[i].y != 1023 and ir[i].s != 15:
        (real_x, real_y) = ((((1023-ir[i].x)*WIDTH) >> 10), (ir[i].y*HEIGHT) >> 10)
        pixels[real_x][real_y][0] ^= 255
        pixels[real_x][real_y][1] ^= 255
        pixels[real_x][real_y][2] ^= 255
      last_ir[i].x = ir[i].x
      last_ir[i].y = ir[i].y
      last_ir[i].s = ir[i].s
    del pixels
    window.unlock()
    window.blit(window, (-1, 0))
    pygame.display.flip()
    for ev in pygame.event.get():
        if ev.type == QUIT:
            connected = 0
    time.sleep(0.01)
while connected == 0:
    time.sleep(0.01)
pygame.display.quit()

[edit] Similar code from WMD

[edit] Perhaps it could be made stand-alone?

Latest version at: https://svn.forthewiin.org/wmd/trunk/wmd/UI/PyGame.py

from wmd.Common import *
import time

class Grapher:
  # These four values sould go into the configuration file
  winheight = 800     # Window height at startup
  width = 700      # Window width at startup
  
  save_tga = 0   # Set this to 1 if you want to save the force log as a series of TGA files

  bgcolor = (255,255,255)          # Background color for the window
  axcolors = [ (255,0,0), (0,255,0), (0,0,255) ]   # Colors for each axis:  x,y,z
  sepcolor = (190,190,190) # Color for the separation lines
  mmcolor = (25, 25, 25) # Color of the min/max text

            # This is the width in number of pixels to use for every point in the graph.
  tInc = 1.5  # Increase (use value between 1-4 for best results) to have a more faster, more precise graph

  init_ignore = 10 # Number of frames to ignore completely while initializing
  init_skip = 10   # Number of frames to use for calibration, without graphing them

  blank_ahead_width = 80 # How many pixels should be blanked ahead of the new graph when redrawing?
  vsep_period = 1.0 # Draw a vertical separator every how many seconds? Set to 0 to disable

  fontheight = 20
  fontpadding = 5

  # You shouldn't have to touch these
  headerheight = 2*(fontheight+fontpadding)
  height = winheight - headerheight

  init = init_skip + init_ignore
  ok = 0
  min = 120 # Initial calibration values
  max = 160
  t = 0     # Start graphing at the left side of the graph
  id = 0
  spf = 4 #Split factor
  prev_ui_info_rect = 0

  def __init__( self, ev ):
    self.ev = ev
    self.try_import()

    if self.ok:
      self.ev.subscribe( WM_ACC, self.ev_wm_acc ) # subscribe to wiimote force/accelerometer reports, handle with ev_wm_acc()
      self.ev.subscribe( UI_INFO, self.ev_ui_info ) # subscribe to UI_INFO messages and display them, handle with ev_ui_info()
      self.ev.subscribe( WMDPOWER, self.ev_wmdpower ) # let's use this to control graphing speed with ev_wmdpower()

      # Initialize pygame screen with selected height, width, background colour; set background colour immediately
      self.pygame.init()
      self.init_font()


      self.screen = self.pygame.display.set_mode( (self.width, self.winheight), self.pygame.RESIZABLE )
      self.pygame.display.set_caption( "HELLO WORLD THIS IS WMD 0.1.2" )
      self.screen.fill( self.bgcolor )
      self.draw_separators()
      self.pygame.display.flip()

      # Register for event processing of these event types only. However, other events still seem to get through.
      pgevlist = [ self.pygame.VIDEORESIZE, self.pygame.VIDEOEXPOSE ]
      self.pygame.event.set_allowed( pgevlist )

  def init_font( self ):
    if self.pygame.font:
      try:
	self.font = self.pygame.font.Font("wmd/VeraMono.ttf", self.fontheight)
      except:
	try:
	  self.font = self.pygame.font.Font(None, self.fontheight)
	except:
	  self.font = 0
	  log(LOG_ERR, "Cannot load font")

  def try_import( self ):
    try:
      import pygame
      self.ok = 1
      self.pygame = pygame
      
    except:
      log(LOG_ERR, "Could not import pygame. Set DISABLE_PYGAME=1")
      self.ok = 0

  def scale( self, n ):
    # Adaptive scaling. It should be modified to gradually revert back to a more precise scale
    #   if the force hasn't exceeded a certain peak for some time
    sn = (n - self.min) * (self.height / (self.max - self.min))
    return self.height -sn
  
  def ev_wm_acc( self, force ):
    # first 10 (by default) frames are ignored
    if self.init > self.init_skip:
      self.init -= 1
      return

    axes = [ 'x', 'y', 'z' ]
    
    fv = []    # sfv: scaled force values
    sfv = []   # ofv: old force values (from one wiimote tick ago)

    for ax in axes:
      v = force[ax]
      fv.append( v )
      sfv.append( self.scale( v ) )
      
    self.graphForce( fv, sfv )

  # graph the next set of x,y,z force values
  def graphForce( self, fv, sfv ):
    # frames 10-20 (by default) are used for calculating min/max values, but not graphed
    # set self.init_skip to choose the number of frames to be skipped
    if self.init > 0:
      self.init -= 1
    elif self.init == 0:
      self.draw_minmax()
      self.pygame.display.flip()
      self.init -= 1
    else:
      self.draw_lines( sfv )

    self.proc_events( ) 

    # store x,y,z values for next line
    self.ofv = sfv

    # Store new mins and maxs for scaling purposes
    for ax in fv:
      if ax < self.min:
        self.min = ax
      elif ax > self.max:
        self.max = ax

  def draw_lines( self, sfv ):
    # draw the lines
    tInc = self.tInc   # For every tick, let's increment t by this value. Higher t, faster scroll. t is used as the x-value

    for i in range(3):
      color = self.axcolors[i]
      t = self.t
      spf = self.spf  # Split the window into four equal horizontal regions

      y2 = self.ofv[i]  /spf+(i*self.height/spf)  # This is the y-value of the startpoint (it's also the previous endpoint)
      y1 = sfv[i]       /spf+(i*self.height/spf)  # And this is the y-value of the endpoint of the aaline

      yc2 = self.ofv[i]  /spf+((spf-1)*self.height/spf) # And these are used for the combined display
      yc1 = sfv[i]       /spf+((spf-1)*self.height/spf)

      # Let's draw anti-aliased lines
      self.pygame.draw.aaline(self.screen, color, (t, y2), (t+tInc, y1), 1)
      self.pygame.draw.aaline(self.screen, color, (t, yc2), (t+tInc, yc1), 1)

    self.check_time()

    updRect = self.pygame.Rect( t, 0, self.blank_ahead_width, self.height )
    self.pygame.display.update( updRect )

    # increment time counter (x-position on screen)
    self.t += tInc

    # if we have reached end of the screen
    if self.t >= self.width:
      self.end_of_cycle()

  def check_time( self ): # Every second or so, draw a vertical line
    if not self.vsep_period:
      return

    now = time.time()

    if not self.t:
      self.last_vsep = now 
    else:
      if now > self.last_vsep + self.vsep_period:
	self.last_vsep = now
	t = self.t
	self.pygame.draw.aaline( self.screen, self.sepcolor, (t,0), (t,self.height), 1)

  def proc_events( self ):
  ## This processes events, and allows us to resize the window
    pgev = self.pygame.event.poll()
    while pgev:
      if pgev:
        if pgev.type == self.pygame.VIDEORESIZE:
          self.width = pgev.w
          self.winheight = pgev.h
	  self.height = self.winheight - self.headerheight
          self.screen = self.pygame.display.set_mode( (self.width, self.winheight), self.pygame.RESIZABLE )
          self.screen.fill( self.bgcolor )
	  self.draw_minmax()
	  self.draw_separators()
	  self.pygame.display.flip()
	  self.t = 0
	  self.init = 10
	elif pgev.type == self.pygame.QUIT:
	  self.ev.send( EV_SHUTDOWN, '' ) 
      pgev = self.pygame.event.poll()
	
  def end_of_cycle( self ):
    # PNG saving requires pygame >= 1.8, otherwise it can only save as TGA !
    if self.save_tga:
      self.pygame.image.save(self.screen, "wii%03d.tga" % self.id)

    self.id += 1
    self.t = 0

    # clear screen
    self.screen.fill( self.bgcolor )
    self.draw_separators()
    self.draw_minmax()

  def draw_minmax( self ):
    # draw min/max values at the top of the screen
    if self.font:
      textStr = "WMD Accelerometer Graph - Range: [%s, %s] " % (int(self.min) , int(self.max))
      mmcolor = self.mmcolor
      text = self.font.render(textStr, 1, mmcolor)
      textpos = text.get_rect(centerx=self.width/2,top=self.height)
      self.screen.blit(text, textpos)
      self.pygame.display.update( textpos )

  def ev_ui_info( self, msg ):
    if self.font:
      textStr = msg
      mmcolor = self.mmcolor
      text = self.font.render(textStr, 1, mmcolor)
      textpos = text.get_rect(centerx=self.width/2,top=self.height + self.fontheight + self.fontpadding)

      if self.prev_ui_info_rect:
        self.screen.fill(self.bgcolor, self.prev_ui_info_rect)

      self.screen.blit(text, textpos)

      if self.prev_ui_info_rect:
        self.pygame.display.update( self.prev_ui_info_rect )
      else:
        self.pygame.display.update( textpos )

      self.prev_ui_info_rect = textpos

  def ev_wmdpower( self, pwrchange ):
    if (self.tInc + pwrchange) < 0:
      return
    else:
      self.tInc += pwrchange

  def draw_separators( self ):
    spf = self.spf
    sepcolor = self.sepcolor

    for i in range(spf+1):
      y = (i*self.height/spf)
      self.pygame.draw.aaline( self.screen, sepcolor, (0, y), (self.width, y), 1)


Windows
cWiimote | GlovePIE | RMX Automation | Wiim | wiimote-api | WiinRemote | WiimoteLib | WiiYourself!

Linux
CWiid | WMD | Perlwiimote | libwiimote | lg3d-wii| Python HTDP Driver

OSX
DarwiinRemote | Remote Buddy | The Wiinstrument | Wiiji (download)

Multiplatform
WiimoteCommander | OpenPIE | Wiimote_Simple | WiiremoteJ | wiiuse | WiiJuce | WiiuseJ | Wii Device Library

PyBluez Scripts: Wiiewer | Wiimotecomm


Personal tools