lazarus-ccr/examples/bluetooth/wiimotetools.pas
mgaertner 2359462c96 bluetooth: fixed bluetooth compilation
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2730 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2013-04-20 00:02:40 +00:00

1020 lines
27 KiB
ObjectPascal

{ Access Wii-Remote devices.
Copyright (C) 2008 Mattias Gaertner mattias@freepascal.org
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your 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 Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}
unit WiiMoteTools;
{$mode objfpc}{$H+}
{$linklib bluetooth}
interface
uses
Classes, SysUtils, Bluetooth, ctypes, Sockets;
//------------------------------------------------------------------------------
// mini libc
type
__time_t = longint;
__suseconds_t = longint;
Ptimeval = ^timeval;
timeval = record
tv_sec : __time_t;
tv_usec : __suseconds_t;
end;
__fd_mask = dWord;
const
__FD_SETSIZE = 1024;
__NFDBITS = 8 * sizeof(__fd_mask);
type
__fd_set = record
fds_bits: packed array[0..(__FD_SETSIZE div __NFDBITS)-1] of __fd_mask;
end;
TFdSet = __fd_set;
PFdSet = ^TFdSet;
Type
Pfd_set = ^_fd_set;
_fd_set = __fd_set;
const
FD_SETSIZE = __FD_SETSIZE;
Type
Pfd_mask = ^fd_mask;
fd_mask = __fd_mask;
const
NFDBITS = __NFDBITS;
Function __FDELT(d: longint): Integer;
Function __FDMASK(d: longint): __fd_mask;
procedure FD_ZERO(out fdset: _fd_set);
procedure FD_SET(fd: longint; var fdset: _fd_Set);
procedure FD_CLR(fd: longint; var fdset: _fd_set);
function FD_ISSET(fd: longint; const fdset: _fd_set): Boolean;
function select(__nfds:longint; __readfds:Pfd_set; __writefds:Pfd_set; __exceptfds:Pfd_set; __timeout:Ptimeval):longint;cdecl;external 'c' name 'select';
//------------------------------------------------------------------------------
const
WM_OUTPUT_CHANNEL = $11;
WM_INPUT_CHANNEL = $13;
// led bit masks
WIIMOTE_LED_NONE = $00;
WIIMOTE_LED_1 = $10;
WIIMOTE_LED_2 = $20;
WIIMOTE_LED_3 = $40;
WIIMOTE_LED_4 = $80;
WIIMOTE_LED_ALL = $F0;
WM_SET_REPORT = $50;
// commands
WM_CMD_LED =$11;
WM_CMD_REPORT_TYPE =$12;
WM_CMD_RUMBLE =$13;
WM_CMD_IR =$13;
WM_CMD_CTRL_STATUS =$15;
WM_CMD_WRITE_DATA =$16;
WM_CMD_READ_DATA =$17;
WM_CMD_IR_2 =$1A;
// input report ids
WM_RPT_CTRL_STATUS =$20;
WM_RPT_READ =$21;
WM_RPT_WRITE =$22;
WM_RPT_BTN =$30;
WM_RPT_BTN_ACC =WM_RPT_BTN+1;
WM_RPT_BTN_IR =WM_RPT_BTN+2;
WM_RPT_BTN_EXP =WM_RPT_BTN+4;
WM_BT_INPUT =$01;
WM_BT_OUTPUT =$02;
// Identify the wiimote device by its class
WM_DEV_CLASS_0 =$04;
WM_DEV_CLASS_1 =$25;
WM_DEV_CLASS_2 =$00;
WM_VENDOR_ID =$057E;
WM_PRODUCT_ID =$0306;
// controller status stuff
WM_MAX_BATTERY_CODE =$C8;
// offsets in wiimote memory
WM_MEM_OFFSET_CALIBRATION =$16;
WM_EXP_MEM_BASE =$04A40000;
WM_EXP_MEM_ENABLE =$04A40040;
WM_EXP_MEM_CALIBR =$04A40020;
WM_REG_IR =$04B00030;
WM_REG_IR_BLOCK1 =$04B00000;
WM_REG_IR_BLOCK2 =$04B0001A;
WM_REG_IR_MODENUM =$04B00033;
// ir block data
// WM_IR_BLOCK1_CLIFF "\x02\x00\x00\x71\x01\x00\xAA\x00\x64"
// WM_IR_BLOCK2_CLIFF "\x63\x03"
WM_IR_TYPE_BASIC =$01;
WM_IR_TYPE_EXTENDED =$03;
// controller status flags for the first message byte
// bit 1 is unknown
WM_CTRL_STATUS_BYTE1_ATTACHMENT =$02;
WM_CTRL_STATUS_BYTE1_SPEAKER_ENABLED =$04;
WM_CTRL_STATUS_BYTE1_IR_ENABLED =$08;
WM_CTRL_STATUS_BYTE1_LED_1 =$10;
WM_CTRL_STATUS_BYTE1_LED_2 =$20;
WM_CTRL_STATUS_BYTE1_LED_3 =$40;
WM_CTRL_STATUS_BYTE1_LED_4 =$80;
// aspect ratio
WM_ASPECT_16_9_X =660;
WM_ASPECT_16_9_Y =370;
WM_ASPECT_4_3_X =560;
WM_ASPECT_4_3_Y =420;
// Expansion stuff
// encrypted expansion id codes (located at =$04A400FC)
EXP_ID_CODE_NUNCHUK =$9A1EFEFE;
EXP_ID_CODE_CLASSIC_CONTROLLER =$9A1EFDFD;
EXP_ID_CODE_GUITAR =$9A1EFDFB;
EXP_HANDSHAKE_LEN =224;
// button codes
WIIMOTE_BUTTON_TWO = $0001;
WIIMOTE_BUTTON_ONE = $0002;
WIIMOTE_BUTTON_B = $0004;
WIIMOTE_BUTTON_A = $0008;
WIIMOTE_BUTTON_MINUS = $0010;
WIIMOTE_BUTTON_ZACCEL_BIT6 = $0020;
WIIMOTE_BUTTON_ZACCEL_BIT7 = $0040;
WIIMOTE_BUTTON_HOME = $0080;
WIIMOTE_BUTTON_LEFT = $0100;
WIIMOTE_BUTTON_RIGHT = $0200;
WIIMOTE_BUTTON_DOWN = $0400;
WIIMOTE_BUTTON_UP = $0800;
WIIMOTE_BUTTON_PLUS = $1000;
WIIMOTE_BUTTON_ZACCEL_BIT4 = $2000;
WIIMOTE_BUTTON_ZACCEL_BIT5 = $4000;
WIIMOTE_BUTTON_UNKNOWN = $8000;
WIIMOTE_BUTTON_ALL = $1F9F;
// nunchul button codes
NUNCHUK_BUTTON_Z = $01;
NUNCHUK_BUTTON_C = $02;
NUNCHUK_BUTTON_ALL = $03;
// classic controller button codes
CLASSIC_CTRL_BUTTON_UP = $0001;
CLASSIC_CTRL_BUTTON_LEFT = $0002;
CLASSIC_CTRL_BUTTON_ZR = $0004;
CLASSIC_CTRL_BUTTON_X = $0008;
CLASSIC_CTRL_BUTTON_A = $0010;
CLASSIC_CTRL_BUTTON_Y = $0020;
CLASSIC_CTRL_BUTTON_B = $0040;
CLASSIC_CTRL_BUTTON_ZL = $0080;
CLASSIC_CTRL_BUTTON_FULL_R = $0200;
CLASSIC_CTRL_BUTTON_PLUS = $0400;
CLASSIC_CTRL_BUTTON_HOME = $0800;
CLASSIC_CTRL_BUTTON_MINUS = $1000;
CLASSIC_CTRL_BUTTON_FULL_L = $2000;
CLASSIC_CTRL_BUTTON_DOWN = $4000;
CLASSIC_CTRL_BUTTON_RIGHT = $8000;
CLASSIC_CTRL_BUTTON_ALL = $FEFF;
// guitar hero 3 button codes
GUITAR_HERO_3_BUTTON_STRUM_UP = $0001;
GUITAR_HERO_3_BUTTON_YELLOW = $0008;
GUITAR_HERO_3_BUTTON_GREEN = $0010;
GUITAR_HERO_3_BUTTON_BLUE = $0020;
GUITAR_HERO_3_BUTTON_RED = $0040;
GUITAR_HERO_3_BUTTON_ORANGE = $0080;
GUITAR_HERO_3_BUTTON_PLUS = $0400;
GUITAR_HERO_3_BUTTON_MINUS = $1000;
GUITAR_HERO_3_BUTTON_STRUM_DOWN = $4000;
GUITAR_HERO_3_BUTTON_ALL = $FEFF;
// ir block data (cliff is the person who found out and documented it first)
WM_IR_BLOCK1_CLIFF : string = #$02#$00#$00#$71#$01#$00#$AA#$00#$64;
WM_IR_BLOCK2_CLIFF : string = #$63#$03;
type
TWiimoteEventType = (
WiiMote_NONE,
WiiMote_EVENT,
WiiMote_STATUS,
WiiMote_DISCONNECT,
WiiMote_NUNCHUK_INSERTED,
WiiMote_NUNCHUK_REMOVED,
WiiMote_CLASSIC_CTRL_INSERTED,
WiiMote_CLASSIC_CTRL_REMOVED,
WiiMote_GUITAR_HERO_3_CTRL_INSERTED,
WiiMote_GUITAR_HERO_3_CTRL_REMOVED
);
type
TAccelVector = record
x,y,z: integer;
end;
TAccelCalibration = record
cal_zero: TAccelVector;
cal_g: TAccelVector;
end;
TWiiMoteReadRequest = class;
TWiiMoteReadCallback = procedure(Request: TWiiMoteReadRequest) of object;
{ TWiiMoteReadRequest }
TWiiMoteReadRequest = class
public
Callback: TWiiMoteReadCallback;
Addr: cardinal;
BufSize: word;
Buf: PByte;
Received: word;
constructor Create(const TheCallback: TWiiMoteReadCallback;
TheAddr: cardinal; TheBufSize: word);
destructor Destroy; override;
end;
TWiiMoteDot = record
X: word;
Y: word;
Size: word;
Visible: boolean;
end;
TWiimotes = class;
{ TWiiMote }
TWiimote = class
private
FLEDS: integer;
FRealizedIR: boolean;
FReadRequests: TFPList;// list of TWiiMoteReadRequest
function SendCmd(ReportType: byte; Msg: PByte; Count: integer): PtrInt;
function SendData(Addr: cuint; Data: Pointer; Count: byte): PtrInt;
function RequestRead(const Callback: TWiiMoteReadCallback;
Addr: cuint; BufSize: cushort): TWiiMoteReadRequest;
procedure SendNextReadRequest;
procedure OnHandShake(Request: TWiiMoteReadRequest);
procedure HandleEvents;
procedure HandleRead;
public
WiiMotes: TWiiMotes;
ID: integer;
Name: string;
bdaddr: bdaddr_t;
Found: boolean;
Connected: boolean;
OutSocket: cint;
InSocket: cint;
Handshaking: boolean;
HandshakeComplete: boolean;
Event: TWiimoteEventType;
EventBuf: array[0..31] of byte;
// properties
LEDs: integer;
Rumble: boolean;
Continuous: boolean;
ReportMotion: boolean;
ReportIR: boolean;
ReportExpansion: boolean;
Buttons: word; // current button state
Accel: TAccelVector; // current accelerator state
AccelCalibration: TAccelCalibration;
Dots: array[0..3] of TWiiMoteDot;
constructor Create;
destructor Destroy; override;
procedure SetLEDs(const AValue: integer);
function Connect: boolean;
procedure Disconnect;
procedure EnableHandshake;
function RealizeReportType: boolean;
procedure RealizeIR;
end;
{ TWiiMotes }
TWiimotes = class
private
FItems: TFPList;
function GetItems(Index: integer): TWiimote;
public
constructor Create;
destructor Destroy; override;
function Add(Item: TWiimote): integer;
function Count: integer;
property Items[Index: integer]: TWiimote read GetItems; default;
procedure FindWiiMotes(timeout: integer);
function Connect: integer;
procedure Disconnect;
function HandleEvents: boolean;
end;
function c_close(fd: cint): cint; external name 'close';
implementation
function __FDELT(d: longint): Integer;
begin
Result:=d div __NFDBITS;
end;
function __FDMASK(d: longint): __fd_mask;
begin
Result:=1 shl (d mod __NFDBITS);
end;
procedure FD_ZERO(out fdset: _fd_set);
var
I: Integer;
begin
with fdset do
for I:=Low(fds_bits) to High(fds_bits) do
fds_bits[I]:=0;
end;
procedure FD_SET(fd: longint; var fdset: _fd_Set);
begin
fdset.fds_bits[__FDELT(fd)]:=fdset.fds_bits[__FDELT(fd)] or __FDMASK(fd);
end;
procedure FD_CLR(fd: longint; var fdset: _fd_set);
begin
fdset.fds_bits[__FDELT(fd)]:=fdset.fds_bits[__FDELT(fd)] and (not __FDMASK(fd));
end;
function FD_ISSET(fd: longint; const fdset: _fd_set): Boolean;
begin
Result:=(fdset.fds_bits[__FDELT(fd)] and __FDMASK(fd))<>0;
end;
procedure TWiimotes.FindWiiMotes(timeout: integer);
var
device_id, device_sock: cint;
scan_info: array[0..127] of inquiry_info;
scan_info_ptr: Pinquiry_info;
found_devices: cint;
DevName: PCChar;
CurWiiMote: TWiimote;
i: Integer;
begin
// get the id of the first bluetooth device.
device_id := hci_get_route(nil);
if (device_id < 0) then
raise Exception.Create('FindWiiMotes: hci_get_route');
// create a socket to the device
device_sock := hci_open_dev(device_id);
if (device_sock < 0) then
raise Exception.Create('hci_open_dev');
// scan for bluetooth devices for 'timeout' seconds
scan_info_ptr:=@scan_info[0];
FillByte(scan_info[0],SizeOf(inquiry_info)*128,0);
found_devices := hci_inquiry_1(device_id, timeout, 128, nil,
@scan_info_ptr, IREQ_CACHE_FLUSH);
if (found_devices < 0) then
raise Exception.Create('hci_inquiry');
writeln('found_devices=',found_devices);
// display discovered devices
DevName:=nil;
GetMem(DevName,20);
for i:=0 to found_devices-1 do begin
if ((scan_info[i].dev_class[0] = WM_DEV_CLASS_0) and
(scan_info[i].dev_class[1] = WM_DEV_CLASS_1) and
(scan_info[i].dev_class[2] = WM_DEV_CLASS_2)) then
begin
CurWiiMote:=TWiimote.Create;
Add(CurWiiMote);
// found a device
ba2str(@scan_info[i].bdaddr, DevName);
CurWiiMote.Name:=PChar(DevName);
CurWiiMote.bdaddr:=scan_info[i].bdaddr;
CurWiiMote.Found:=true;
writeln(i,' Device=',CurWiiMote.Name);
end;
end;
FreeMem(DevName);
c_close(device_sock);
end;
function TWiimotes.Connect: integer;
var
CurWiiMote: TWiimote;
i: Integer;
begin
Result:=0;
for i:=0 to Count-1 do begin
CurWiiMote:=Items[i];
if not CurWiiMote.Found then
// if the device address is not set, skip it
continue;
if CurWiiMote.Connect then
inc(Result);
end;
end;
procedure TWiimotes.Disconnect;
var
i: Integer;
begin
for i:=0 to Count-1 do Items[i].Disconnect;
end;
function TWiimotes.HandleEvents: boolean;
var
fds: _fd_set;
highest_fd: integer;
tv: timeval;
i: integer;
r: PtrInt;
begin
highest_fd:=0;
Result:=false;
// block select() for 1/2000th of a second
tv.tv_sec := 0;
tv.tv_usec := 500;
FD_ZERO(fds);
for i:=0 to Count-1 do begin
// only poll it if it is connected
if Items[i].Connected then begin
FD_SET(Items[i].InSocket, fds);
// find the highest fd of the connected wiimotes
if (Items[i].InSocket > highest_fd) then
highest_fd := Items[i].InSocket;
end;
Items[i].Event:= WiiMote_NONE;
end;
if (select(highest_fd + 1, @fds, nil, nil, @tv) = -1) then
raise Exception.Create('Unable to select() the wiimote interrupt socket(s)');
// check each socket for an event
for i:=0 to Count-1 do begin
// if this wiimote is not connected, skip it
if not Items[i].Connected then
continue;
if (FD_ISSET(Items[i].InSocket, fds)) then begin
// clear out the event buffer
FillByte(Items[i].EventBuf[0],32,0);
// read the pending message into the buffer
r := fprecv(Items[i].InSocket,@Items[i].EventBuf[0], 32,0);
if (r = -1) then begin
// error reading data
writeln('Receiving wiimote data '+IntToStr(Items[i].ID));
// remote disconnect
Items[i].Disconnect;
Result:=true;
continue;
end;
// propagate the event
if Items[i].EventBuf[1]<>0 then begin
Items[i].HandleEvents;
Result:=true;
end;
end else begin
//idle_cycle(wm[i]);
end;
end;
end;
{ TWiimotes }
function TWiimotes.GetItems(Index: integer): TWiimote;
begin
Result:=TWiimote(FItems[Index]);
end;
constructor TWiimotes.Create;
begin
FItems:=TFPList.Create;
end;
destructor TWiimotes.Destroy;
var
i: Integer;
begin
for i:=0 to FItems.Count-1 do TObject(FItems[i]).Free;
FreeAndNil(FItems);
inherited Destroy;
end;
function TWiimotes.Add(Item: TWiimote): integer;
begin
Item.ID:=Count;
Item.WiiMotes:=Self;
Result:=FItems.Add(Item);
end;
function TWiimotes.Count: integer;
begin
Result:=FItems.Count;
end;
{ TWiimote }
procedure TWiimote.SetLEDs(const AValue: integer);
var
buf: byte;
begin
// remove the lower 4 bits because they control rumble
FLEDs:=AValue and $F0;
// make sure if the rumble is on that we keep it on
if Rumble then
FLEDs := FLEDs or 1;
if Connected then begin
buf := FLEDs;
SendCmd(WM_CMD_LED, @buf, 1);
end;
end;
function TWiimote.Connect: boolean;
var
Addr: sockaddr_l2;
begin
Addr.l2_family:=AF_BLUETOOTH;
Addr.l2_bdaddr:=bdaddr;
// OUTPUT CHANNEL
OutSocket:=fpsocket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (OutSocket = -1) then
exit(false);
{$IFDEF BIG_ENDIAN}
{$ERROR ToDo BIG_ENDIAN}
{$ENDIF}
Addr.l2_psm := WM_OUTPUT_CHANNEL; // htobs
// connect to wiimote
if (fpconnect(OutSocket, psockaddr(@addr), SizeOf(addr)) < 0) then
raise Exception.Create('fpconnect output');
// INPUT CHANNEL
InSocket:=fpsocket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (InSocket = -1) then begin
CloseSocket(OutSocket);
OutSocket := -1;
exit(false);
end;
Addr.l2_psm := WM_INPUT_CHANNEL; // htobs
// connect to wiimote
if (fpconnect(InSocket, psockaddr(@addr), SizeOf(addr)) < 0) then
begin
CloseSocket(OutSocket);
OutSocket := -1;
raise Exception.Create('fpconnect input');
end;
writeln('Connected to wiimote ',ID);
// do the handshake
Connected:=true;
EnableHandshake;
RealizeReportType;
Result:=true;
end;
procedure TWiimote.Disconnect;
begin
if not Connected then exit;
CloseSocket(OutSocket);
CloseSocket(InSocket);
OutSocket:=-1;
InSocket:=-1;
Connected:=false;
Handshaking:=false;
HandshakeComplete:=false;
FRealizedIR:=false;
end;
procedure TWiimote.EnableHandshake;
begin
// send request to wiimote for accelerometer calibration
Handshaking:=true;
SetLEDs(WIIMOTE_LED_ALL);
RequestRead(@OnHandShake,WM_MEM_OFFSET_CALIBRATION, 7);
//WiiMote_read_data(wm, WiiMote_handshake, buf, WM_MEM_OFFSET_CALIBRATION, 7);
//inc(WiiMote.Handshake_State);
//WiiMote_set_leds(wm, WIIMOTE_LED_NONE);
end;
function TWiimote.RealizeReportType: boolean;
var
buf: array[0..1] of byte;
begin
if not Connected then exit(false);
if Continuous then
buf[0] := 4 // set to 0x04 for continuous reporting
else
buf[0] :=0;
buf[1] := 0;
// if rumble is enabled, make sure we keep it
if Rumble then
buf[0] := buf[0] or 1;
if ReportMotion then buf[1] := buf[1] or WM_RPT_BTN_ACC;
if ReportExpansion then buf[1] := buf[1] or WM_RPT_BTN_EXP;
if ReportIR then buf[1] := buf[1] or WM_RPT_BTN_IR;
writeln('TWiiMote.RealizeReportType ',buf[1]);
if SendCmd(WM_CMD_REPORT_TYPE,@buf[0],2)<=0 then begin
writeln('TWiiMote.RealizeReportType FAILED');
exit(false);
end;
Result:=true;
end;
procedure TWiimote.RealizeIR;
var
buf: byte;
begin
if not HandshakeComplete then begin
writeln('TWiiMote.EnableIR still handshaking');
exit;
end;
if ReportIR=FRealizedIR then exit;
writeln('TWiiMote.RealizeIR ReportIR=',ReportIR);
// set camera 1 and 2
if ReportIR then
buf:=4
else
buf:=0;
SendCmd(WM_CMD_IR,@buf,1);
SendCmd(WM_CMD_IR_2,@buf,1);
if ReportIR then begin
// enable IR, set sensitivity
buf:=8;
SendData(WM_REG_IR,@buf,1);
// wait for the wiimote to catch up
Sleep(50);
// write sensitivity blocks
SendData(WM_REG_IR_BLOCK1, Pointer(WM_IR_BLOCK1_CLIFF), length(WM_IR_BLOCK1_CLIFF));
SendData(WM_REG_IR_BLOCK2, Pointer(WM_IR_BLOCK2_CLIFF), length(WM_IR_BLOCK2_CLIFF));
// set the IR mode
if ReportExpansion then
buf := WM_IR_TYPE_BASIC
else
buf := WM_IR_TYPE_EXTENDED;
SendData(WM_REG_IR_MODENUM,@buf,1);
// wait for the wiimote to catch up
Sleep(50);
// set the wiimote report type
RealizeReportType;
writeln('TWiiMote.RealizeIR IR enabled');
end;
end;
procedure TWiimote.HandleEvents;
var
Data: PByte;
i: Integer;
begin
// don't know what WiiMote.EventBuf[0] is
case EventBuf[1] of
WM_RPT_CTRL_STATUS:
begin
writeln('TWiiMote.HandleEvent WM_RPT_CTRL_STATUS');
end;
WM_RPT_READ:
begin
writeln('TWiiMote.HandleEvent WM_RPT_READ');
HandleRead;
end;
WM_RPT_WRITE:
begin
writeln('TWiiMote.HandleEvent WM_RPT_WRITE');
end;
WM_RPT_BTN:
begin
Buttons:=PWord(@EventBuf[2])^ and WIIMOTE_BUTTON_ALL;
writeln('TWiiMote.HandleEvent Button ',Buttons);
end;
WM_RPT_BTN_ACC or WM_RPT_BTN_IR:
begin
Buttons:=PWord(@EventBuf[2])^ and WIIMOTE_BUTTON_ALL;
Accel.x:=EventBuf[4];
Accel.y:=EventBuf[5];
Accel.z:=EventBuf[6];
// 7,8,9,10
Data:=PByte(@EventBuf[7]);
for i := 0 to 3 do begin
Dots[i].x := 1023 - (data[3*i] or ((data[(3*i)+2] and $30) shl 4));
Dots[i].y := data[(3*i)+1] or ((data[(3*i)+2] and $C0) shl 2);
Dots[i].size := data[(3*i)+2] and $0F;
// if in range set to visible
Dots[i].visible := (Dots[i].y <> 1023);
//if dot[i].visible then write(' ',dot[i].rx,' ',dot[i].ry,' ',dot[i].size,' ',dot[i].visible);
end;
//for i:=0 to 20 do write(IntToHex(ord(EventBuf[i]),2),' ');
//writeln(' TWiiMotes.HandleEvent x=',Accel.x,' y=',Accel.y,' z=',Accel.z);
//Sleep(300);
end;
else
writeln('TWiiMotes.HandleEvent other decimal=',EventBuf[1],' hex=',IntToHex(EventBuf[1],2));
end;
end;
procedure TWiimote.HandleRead;
var
Error: byte;
len: byte;
offset: word;
Request: TWiiMoteReadRequest;
begin
// always assume the packet received is from the most recent request
Buttons:=PWord(@EventBuf[2])^ and WIIMOTE_BUTTON_ALL;
// if we don't have a request out then we didn't ask for this packet
if FReadRequests.Count=0 then begin
writeln('TWiiMote.HandleRead Received data packet whithout request');
exit;
end;
Error := EventBuf[4] and $0F;
if (Error = $08) then
writeln('Unable to read data - address does not exist.')
else if (Error = $07) then
writeln('Unable to read data - address is for write-only registers.')
else if (Error>0) then
writeln('Unable to read data - unknown error code ',Error);
if (Error>0) then begin
// skip this request
TObject(FReadRequests[0]).Free;
FReadRequests.Delete(0);
// send next request to wiimote
if (FReadRequests.Count>0) then
SendNextReadRequest;
exit;
end;
len := ((EventBuf[4] and $F0) shr 4) + 1;
offset := BEtoN(PWord(@EventBuf[5])^);
Request:=TWiiMoteReadRequest(FReadRequests[0]);
inc(Request.Received,len);
if (Request.Received>Request.BufSize) then begin
// this should never happen
writeln('WARNING: TWiiMote.HandleRead Request.Received>Request.BufSize');
Request.Received:=Request.BufSize;
end;
writeln('Received read packet:');
writeln(' Packet read offset: ',offset,' bytes');
writeln(' Request read offset: ',Request.Addr,' bytes');
writeln(' Read offset into buf: ',offset-Request.Addr,' bytes');
writeln(' Read data size: ',len,' bytes');
writeln(' Still need: ',Request.BufSize-Request.Received,' bytes');
// reconstruct this part of the data
System.Move(EventBuf[7],(Request.Buf+offset-Request.Addr)^,len);
// if all data has been received, execute the read event callback
if Request.Received=Request.BufSize then begin
Request.Callback(Request);
if TWiiMoteReadRequest(FReadRequests[0])<>Request then
raise Exception.Create('TWiiMote.HandleRead inconsistency');
// skip this request
Request.Free;
FReadRequests.Delete(0);
// send next request to wiimote
if (FReadRequests.Count>0) then
SendNextReadRequest;
end;
end;
function TWiimote.SendCmd(ReportType: byte; Msg: PByte; Count: integer): PtrInt;
var
Buf: array[0..31] of byte;
CurRumble: boolean;
begin
writeln('TWiiMote.SendCmd ReportType=',ReportType,' Count=',Count);
buf[0] := WM_SET_REPORT or WM_BT_OUTPUT;
buf[1] := ReportType;
CurRumble:=false;
case ReportType of
WM_CMD_LED,
WM_CMD_RUMBLE,
WM_CMD_CTRL_STATUS:
begin
// Rumble flag for: =$11, =$13, =$14, =$15, =$19 or =$1a
if Rumble then
CurRumble := true;
end;
end;
System.Move(Msg^,Buf[2],Count);
if CurRumble then
buf[2] := buf[2] or 1;
Result:=fpsend(OutSocket,@Buf[0],Count+2,0);
end;
function TWiimote.SendData(Addr: cuint; Data: Pointer; Count: byte): PtrInt;
var
Buf: array[0..20] of byte;
begin
writeln('TWiiMote.SendData Addr=',IntToHex(Addr,8),' Count=',Count);
if Count>16 then
raise Exception.Create('TWiiMote.SendData too many bytes');
Buf[0]:=0;
FillByte(Buf[0],21,0);
PDWord(@Buf[0])^:=NtoBE(cardinal(Addr));// Addr as big endian
Buf[4]:=Count;
System.Move(Data^,Buf[5],Count);
Result:=SendCmd(WM_CMD_WRITE_DATA,@Buf[0],21);
end;
function TWiimote.RequestRead(const Callback: TWiiMoteReadCallback;
Addr: cuint; BufSize: cushort): TWiiMoteReadRequest;
begin
Result:=nil;
if not Connected then exit;
if (BufSize=0) or (not Assigned(Callback)) then exit;
Result:=TWiiMoteReadRequest.Create(Callback,Addr,BufSize);
FReadRequests.Add(Result);
if FReadRequests.Count=1 then begin
// this is the only request => send immediately
SendNextReadRequest;
end;
end;
procedure TWiimote.SendNextReadRequest;
var
Request: TWiiMoteReadRequest;
buf: array[1..6] of byte;
begin
if (FReadRequests.Count=0) or (not Connected) then exit;
Request:=TWiiMoteReadRequest(FReadRequests.First);
// addr and bufsize in big endian
PDWord(@buf[1])^:=NtoBE(cardinal(Request.Addr));
PWord(@buf[5])^:=NtoBE(word(Request.BufSize));
writeln('TWiiMote.SendNextReadRequest Addr=',Request.Addr,' BufSize=',Request.BufSize);
SendCmd(WM_CMD_READ_DATA, @buf[1], 6);
end;
procedure TWiimote.OnHandShake(Request: TWiiMoteReadRequest);
begin
writeln('TWiiMote.OnHandShake ');
// received read data
AccelCalibration.cal_zero.x := Request.buf[0];
AccelCalibration.cal_zero.y := Request.buf[1];
AccelCalibration.cal_zero.z := Request.buf[2];
AccelCalibration.cal_g.x := Request.buf[4] - AccelCalibration.cal_zero.x;
AccelCalibration.cal_g.y := Request.buf[5] - AccelCalibration.cal_zero.y;
AccelCalibration.cal_g.z := Request.buf[6] - AccelCalibration.cal_zero.z;
// handshake complete
writeln('TWiiMote.OnHandShake Calibration: Idle: ',
' x=',AccelCalibration.cal_zero.x,
' y=',AccelCalibration.cal_zero.y,
' z=',AccelCalibration.cal_zero.z,
' +1g: ',
' x=',AccelCalibration.cal_g.x,
' y=',AccelCalibration.cal_g.y,
' z=',AccelCalibration.cal_g.z
);
// request the status of the wiimote to see if there is an expansion
// ToDo
Handshaking:=false;
HandshakeComplete:=true;
// now enable IR
if ReportIR then begin
RealizeIR;
end;
end;
constructor TWiimote.Create;
begin
InSocket:=-1;
OutSocket:=-1;
FReadRequests:=TFPList.Create;
end;
destructor TWiimote.Destroy;
var
i: Integer;
begin
for i:=0 to FReadRequests.Count-1 do TObject(FReadRequests[i]).Free;
FreeAndNil(FReadRequests);
inherited Destroy;
end;
{ TWiiMoteReadRequest }
constructor TWiiMoteReadRequest.Create(const TheCallback: TWiiMoteReadCallback;
TheAddr: cardinal; TheBufSize: word);
begin
Callback:=TheCallback;
Addr:=TheAddr;
BufSize:=TheBufSize;
ReAllocMem(Buf,BufSize);
end;
destructor TWiiMoteReadRequest.Destroy;
begin
ReAllocMem(Buf,0);
inherited Destroy;
end;
end.