aboutsummaryrefslogtreecommitdiff
path: root/w32mote/WiiYourself/wiimote.h
blob: 1db2c09847efe668211359a4dc1557ce0368309e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
// _______________________________________________________________________________
//
//	 - WiiYourself! - native C++ Wiimote library  v1.15
//	  (c) gl.tter 2007-10 - http://gl.tter.org
//
//	  see License.txt for conditions of use.  see History.txt for change log.
// _______________________________________________________________________________
//
//  wiimote.h  (tab = 4 spaces)

#ifdef _MSC_VER // VC
# pragma once
#endif

#ifndef _WIIMOTE_H
# define _WIIMOTE_H

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>		// auto Unicode/Ansi support
#include <queue>		// for HID write method
#include <list>			// for state recording
 
#ifndef QWORD
 typedef unsigned __int64 QWORD;
#endif

#ifdef _MSC_VER			   // VC-specific: _DEBUG build only _ASSERT() sanity checks
# include <crtdbg.h>
#elif defined(__MINGW32__) // define NDEBUG to disable assert
# include <assert.h>
# define _ASSERT assert
#else
# define _ASSERT(x) ((void)0) // (add your compiler's implementation if you like)
#endif

#ifdef SWIGWRAPPER		// Python Wrapper
#include "Python/wiimote_state.i"
#else
#include "wiimote_state.h"
#endif

// configs:
//#define USE_DYNAMIC_HIDQUEUE // deprecated

//  we request periodic status report updates to refresh the battery level
//   and to detect connection loss (through failed writes)
#define REQUEST_STATUS_EVERY_MS		1000
#define DETECT_MPLUS_EVERY_MS		1000
#define DETECT_MPLUS_COUNT			1	 // # of tries in quick succession

//  all threads (read/parse, audio streaming, async rumble...) use this priority
#define WORKER_THREAD_PRIORITY		THREAD_PRIORITY_HIGHEST

 // internals
#define	WIIYOURSELF_VERSION_MAJOR	1
#define	WIIYOURSELF_VERSION_MINOR1	1
#define	WIIYOURSELF_VERSION_MINOR2	5
//#define WIIYOURSELF_VERSION_BETA	// not defined for non-beta releases
#define WIIYOURSELF_VERSION_STR		_T("1.15")

// array sizes
#define	TOTAL_BUTTON_BITS	16	// Number of bits for (Classic)ButtonNameFromBit[]
#define	TOTAL_FREQUENCIES	10	// Number of frequencies (see speaker_freq[])

 // clarity
typedef HANDLE EVENT;


// state data changes can be signalled to the app via a callback.  Set the wiimote
//  object's 'ChangedCallback' any time to enable them, or alternatively inherit
//   from the wiimote object and override the ChangedNotifier() virtual method.

//  of flags indicating which state has changed since the last callback.
typedef void (*state_changed_callback)	(class wiimote	     &owner,
										 state_change_flags  changed,
										 const wiimote_state &new_state);

// internals
typedef BOOLEAN (__stdcall *hidwrite_ptr)(HANDLE HidDeviceObject,
										  PVOID  ReportBuffer,
										  ULONG  ReportBufferLength);

// (global due to Python wrapper)
struct wiimote_state_event {
	DWORD		  time_ms;	// system timestamp in milliseconds
	wiimote_state state;
	};

// wiimote class - connects and manages a wiimote and its optional extensions
//                 (Nunchuk/Classic Controller), and exposes their state
class wiimote : public wiimote_state
	{
	public:
		wiimote ();
		virtual ~wiimote ();

	public:
		// these can be used to identify Connect()ed wiimote objects (if both
		//  are unconnected they will pass the compare as their handles are invalid)
		inline bool operator == (const wiimote& remote)
			{ return Handle == remote.Handle; }
		inline bool operator != (const wiimote& remote)
			{ return Handle != remote.Handle; }

		// wiimote data input mode (use with SetReportType())
		//  (only enable what you need to save battery power)
		enum input_report
			{
			// combinations if buttons/acceleration/IR/Extension data
			IN_BUTTONS				 = 0x30,
			IN_BUTTONS_ACCEL		 = 0x31,
			IN_BUTTONS_ACCEL_IR		 = 0x33, // reports IR EXTENDED data (dot sizes)
			IN_BUTTONS_ACCEL_EXT	 = 0x35,
			IN_BUTTONS_ACCEL_IR_EXT	 = 0x37, // reports IR BASIC data (no dot sizes)
			IN_BUTTONS_BALANCE_BOARD = 0x32, // must use this for the balance board
			};
		// string versions 
		static const TCHAR* ReportTypeName [];


	public: // convenience accessors:
		inline bool	IsConnected			  () const { return bStatusReceived; }
		// if IsConnected() unexpectedly returns false, connection was probably lost
		inline bool	ConnectionLost		  () const { return bConnectionLost; }
		inline bool IsBalanceBoard		  () const { return (Internal.bExtension &&
							(Internal.ExtensionType==wiimote_state::BALANCE_BOARD)); }
		inline bool	NunchukConnected	  () const { return (Internal.bExtension &&
							(Internal.ExtensionType==wiimote_state::NUNCHUK)); }
		inline bool	ClassicConnected	  () const { return (Internal.bExtension &&
							(Internal.ExtensionType==wiimote_state::CLASSIC)); }
		inline bool MotionPlusConnected   () const { return bMotionPlusDetected; }
		inline bool MotionPlusEnabled	  () const { return bMotionPlusEnabled; }
		inline bool MotionPlusHasExtension() const { return bMotionPlusExtension; }
		inline bool	IsPlayingAudio		  () const { return (Internal.Speaker.Freq &&
															 Internal.Speaker.Volume); }
		inline bool	IsPlayingSample		  () const { return IsPlayingAudio() &&
															(CurrentSample != NULL); }
		inline bool	IsUsingHIDwrites	  () const { return bUseHIDwrite; }
		inline bool	IsRecordingState	  () const { return Recording.bEnabled; }

		static inline unsigned TotalConnected() { return _TotalConnected; }


	public: // data
		QWORD UniqueID;		   // constructed from device-specific calibration info.
							   //  Note this is not guaranteed to be truly unique
							   //  as several devices may contain the same calibration
							   //  vluaes - but unique amongst a small number of 
							   //  devices.
#ifdef ID2_FROM_DEVICEPATH
		QWORD UniqueID2;	   // (low-reliabilty, left for reference)
							   //  constructed from the 'device path' string (as
							   //  reported by the OS/stack).  This is hopefully
							   //  unique as long as the devices remain installed
							   //  (or at least paired).
#endif
		// optional callbacks - set these to your own fuctions (if required)
		state_changed_callback  ChangedCallback;
		//  you can avoid unnecessary callback overhead by specifying a mask
		//   of which state changes should trigger them (default is any)
		state_change_flags		CallbackTriggerFlags;
		// alternatively, inherit from this class and override this virtual function:
		virtual void			ChangedNotifier (state_change_flags  changed,
												 const wiimote_state &new_state) {};

		// get the button name from its bit index (some bits are unused)
		static const TCHAR*		   ButtonNameFromBit [TOTAL_BUTTON_BITS];
		static const TCHAR*		GetButtonNameFromBit (unsigned index)
			{
			_ASSERT(index < TOTAL_BUTTON_BITS);
			if(index >= TOTAL_BUTTON_BITS)
				return _T("[invalid index]");
			return ButtonNameFromBit[index];
			}

		// same for the Classic Controller
		static const TCHAR*		   ClassicButtonNameFromBit [TOTAL_BUTTON_BITS];
		static const TCHAR*		GetClassicButtonNameFromBit (unsigned index)
			{
			_ASSERT(index < TOTAL_BUTTON_BITS);
			if(index >= TOTAL_BUTTON_BITS)
				return _T("[invalid index]");
			return ClassicButtonNameFromBit[index];
			}

		// get the frequency from speaker_freq enum
		static const unsigned	   FreqLookup [TOTAL_FREQUENCIES];
		static const unsigned	GetFreqLookup (unsigned index)
			{
			_ASSERT(index < TOTAL_FREQUENCIES);
			if(index >= TOTAL_FREQUENCIES)
				return 0;
			return FreqLookup[index];
			}

	public: // methods
		// call Connect() first - returns true if wiimote was found & enabled
		//  - 'wiimote_index' specifies which *installed* (not necessarily
		//     *connected*) wiimote should be tried (1 = first, 2 = 2nd etc).
		//    if you just want the first *available* wiimote that isn't already
		//     in use, pass in FIRST_AVAILABLE (default).
		//  - 'force_hidwrites' forces HID output method (it's auto-selected
		//     when needed and less efficient, so only force for testing).
		static const unsigned FIRST_AVAILABLE = 0xffffffff;
		bool Connect				 (unsigned wiimote_index = FIRST_AVAILABLE,
									  bool force_hidwrites   = false);
		// disconnect from the controller and stop reading data from it
		void Disconnect				 ();
		// set wiimote reporting mode (call after Connnect())
		//  continous = true forces the wiimote to send constant updates, even when
		//			     nothing has changed.
		//			  = false only sends data when something has changed (note that
		//			     acceleration data will cause frequent updates anyway as it
		//			     jitters even when the wiimote is stationary)
		void SetReportType			 (input_report type, bool continuous = false);
		
		// toggle the MotionPlus extension.  Call MotionPlusDetected() first to
		//  see if it's attached.  Unlike normal extensions, the MotionPlus does
		//  not report itself as one until enabled.  Once done, it then replaces
		//  any standard extension attached to it, so be sure to disable it
		//  if you want to read those (it's not currently known of both can
		//  be read simultaneously).
		bool EnableMotionPlus  ();
		bool DisableMotionPlus ();
	
		// this is used to remove unwanted 'at rest' offsets, currently only from
		//  the Balance Board.  make sure there is no weight on the board before
		//  calling this. it reads the current sensor values and then removes them
		//  offsets from all subsequent KG and LB state values (the 'raw' values
		//  are never modified).
		void CalibrateAtRest		 ();
		// NOTE: the library automatically calls this when the first weight values
		//        come in after Connect()ion, but if the device wasn't at rest at
		//        the time the app can call it again later.

		// to read the state via polling (reading the public state data direct from
		//  the wiimote object) you must call RefreshState() at the top of every pass.
		//  returns a combination of flags to indicate which state (if any) has
		//  changed since the last call.
		state_change_flags RefreshState ();

		// reset the wiimote (changes report type to non-continuous buttons-only,
		//					  clears LEDs & rumble, mutes & disables speaker)
		void Reset					 ();
		// set/clear the wiimote LEDs
		void SetLEDs				 (BYTE led_bits); // bits 0-3 are valid
		// set/clear rumble
		void SetRumble				 (bool on);
		// alternative - rumble for a fixed amount of time (asynchronous)
		void RumbleForAsync			 (unsigned milliseconds);

		// *experimental* speaker support:
		bool MuteSpeaker			 (bool on);
		bool EnableSpeaker			 (bool on);
		bool PlaySquareWave			 (speaker_freq freq, BYTE volume = 0x40);
		//  note: PlaySample currently streams from the passed-in wiimote_sample -
		//		   don't delete it until playback has stopped.
		bool PlaySample				 (const		   wiimote_sample &sample,
									  BYTE		   volume		 = 0x40,
									  speaker_freq freq_override = FREQ_NONE);

		//  16bit mono sample loading/conversion to native format:
		//   .wav sample
		static bool Load16bitMonoSampleWAV	 (const TCHAR*   filepath,
											  wiimote_sample &out);
		//   raw 16bit mono audio data (can be signed or unsigned)
		static bool Load16BitMonoSampleRAW	 (const TCHAR*   filepath,
											  bool		     _signed,
											  speaker_freq   freq,
											  wiimote_sample &out);
		//   converts a 16bit mono sample array to a wiimote_sample
		static bool Convert16bitMonoSamples  (const short*   samples,
											  bool		     _signed,
											  DWORD		     length,
											  speaker_freq   freq,
											  wiimote_sample &out);
		
		// state recording - records state snapshots to a 'state_history' supplied
		//					  by the caller.  states are timestamped and only added
		//					  to the list when the specified state changes.
#ifndef SWIG // !Python Wrapper
		typedef wiimote_state_event state_event;
#endif
		typedef std::list<state_event> state_history;
		static const unsigned UNTIL_STOP = 0xffffffff;
		// - pass in a 'state_history' list, and don't destroy/change it until
		//	  recording is stopped.  note the list will be cleared first.
		// - you can request a specific duration (and use IsRecordingState() to detect
		//    the end), or UNTIL_STOP.  StopRecording() can be called either way.
		// - you can use 'change trigger' to specify specific state changes that will
		//    trigger an insert into the history (others are then ignored).
		void RecordState  (state_history	  &events_out,
						   unsigned			  max_time_ms	 = UNTIL_STOP,
						   state_change_flags change_trigger = CHANGED_ALL);
		void StopRecording ();


	private: // methods
		// start reading asynchronously from the controller
		bool BeginAsyncRead			();
		// request status update (battery level, extension status etc)
		void RequestStatusReport	();
		// read address or register from Wiimote asynchronously (the result is
		//  parsed internally whenever it arrives)
		bool ReadAddress			(int address, short size);
		// write a single BYTE to a wiimote address or register
		inline void WriteData		(int address, BYTE data) { WriteData(address, 1, &data); }
		// write a BYTE array to a wiimote address or register
		void WriteData				(int address, BYTE size, const BYTE* buff);
		// callback when data is ready to be processed
		void OnReadData				(DWORD bytes_read);
		// parse individual reports by type
		int	 ParseInput				(BYTE* buff);
		// detects if MotionPlus is attached (it doesn't report as a normal
		//  extesnion until it is enabled)
		void DetectMotionPlusExtensionAsync ();
		// initializes an extension when plugged in.
		void InitializeExtension	();
		// parses a status report
		int	 ParseStatus		    (BYTE* buff);
		// parses the buttons
		int	 ParseButtons			(BYTE* buff);
		// parses accelerometer data
		int	 ParseAccel				(BYTE* buff);
		bool EstimateOrientationFrom(wiimote_state::acceleration &accel);
		void ApplyJoystickDeadZones (wiimote_state::joystick &joy);
		// parses IR data from report
		int  ParseIR				(BYTE* buff);
		// parses data from an extension.
		int  ParseExtension			(BYTE* buff, unsigned offset);
		// parses data returned from a read report
		int  ParseReadAddress		(BYTE* buff);
		// reads calibration information stored on Wiimote
		void ReadCalibration		();
		float GetBalanceValue(short sensor, short min, short mid, short max);
		// turns on the IR sensor (the mode must match the reporting mode caps)
		void EnableIR				(wiimote_state::ir::mode mode);
		// disables the IR sensor
		void DisableIR				();
		// writes a report to the Wiimote (NULL = use 'WriteBuff')
		bool WriteReport			(BYTE* buff);
		bool StartSampleThread		();
		// returns the rumble BYTE that needs to be sent with reports.
		inline BYTE  GetRumbleBit	()	const { return Internal.bRumble? 0x01 : 0x00; }

		// static thread funcs:
		static unsigned __stdcall ReadParseThreadfunc   (void* param);
		static unsigned __stdcall AsyncRumbleThreadfunc (void* param);
		static unsigned __stdcall SampleStreamThreadfunc(void* param);
		static unsigned __stdcall HIDwriteThreadfunc	(void* param);

	private: // data
		// wiimote output comands
		enum output_report
			{
			OUT_NONE			= 0x00,
			OUT_LEDs			= 0x11,
			OUT_TYPE			= 0x12,
			OUT_IR				= 0x13,
			OUT_SPEAKER_ENABLE	= 0x14,
			OUT_STATUS			= 0x15,
			OUT_WRITEMEMORY		= 0x16,
			OUT_READMEMORY		= 0x17,
			OUT_SPEAKER_DATA	= 0x18,
			OUT_SPEAKER_MUTE	= 0x19,
			OUT_IR2				= 0x1a,
			};
		// input reports used only internally:
		static const int IN_STATUS						= 0x20;
		static const int IN_READADDRESS					= 0x21;
		// wiimote device IDs:
		static const int VID							= 0x057e; // 'Nintendo'
		static const int PID							= 0x0306; // 'Wiimote'
		// we could find this out the hard way using HID, but it's 22
		static const int REPORT_LENGTH					= 22;
		// wiimote registers
		static const int REGISTER_CALIBRATION			= 0x0016;
		static const int REGISTER_IR					= 0x4b00030;
		static const int REGISTER_IR_SENSITIVITY_1		= 0x4b00000;
		static const int REGISTER_IR_SENSITIVITY_2		= 0x4b0001a;
		static const int REGISTER_IR_MODE				= 0x4b00033;
		static const int REGISTER_EXTENSION_INIT1		= 0x4a400f0;
		static const int REGISTER_EXTENSION_INIT2		= 0x4a400fb;
		static const int REGISTER_EXTENSION_TYPE		= 0x4a400fa;
		static const int REGISTER_EXTENSION_CALIBRATION	= 0x4a40020;
		static const int REGISTER_BALANCE_CALIBRATION	= 0x4a40024;
		static const int REGISTER_MOTIONPLUS_DETECT		= 0x4a600fa;
		static const int REGISTER_MOTIONPLUS_INIT		= 0x4a600f0;
		static const int REGISTER_MOTIONPLUS_ENABLE		= 0x4a600fe;

		HANDLE			 Handle;		  // read/write device handle
		OVERLAPPED		 Overlapped;	  // for async Read/WriteFile() IO
		HANDLE			 ReadParseThread; // waits for overlapped reads & parses result
		EVENT			 DataRead;		  // signals overlapped read complete
		bool			 bUseHIDwrite;	  // alternative write method (less efficient
										  //  but required for some BT stacks (eg. MS')
		// HidD_SetOutputReport is only supported from XP onwards, so detect &
		//  load it dynamically:
		static HMODULE	 HidDLL;	
		static hidwrite_ptr _HidD_SetOutputReport;
		
		volatile bool	 bStatusReceived;	  // for output method detection
		volatile bool	 bConnectInProgress;  // don't handle extensions until complete
		volatile bool	 bInitInProgress;	  // stop regular requests until complete
		volatile bool	 bEnablingMotionPlus; // for special init codepath
		volatile bool	 bConnectionLost;	  // auto-Disconnect()s if set
volatile int	 MotionPlusDetectCount;		  // waiting for the result
		volatile bool	 bMotionPlusDetected;
		volatile bool	 bMotionPlusEnabled;
		volatile bool	 bMotionPlusExtension;// detected one plugged into MotionPlus
		volatile bool	 bCalibrateAtRest;	  // as soon as the first sensor values 											  //  come in after a Connect() call.
		static unsigned	 _TotalCreated;
		static unsigned	 _TotalConnected;
		input_report	 ReportType;	      // type of data the wiimote delivers	
		// read buffer
		BYTE			 ReadBuff  [REPORT_LENGTH];
		// for polling: state is updated on a thread internally, and made only
		//  made public via RefreshState()
		CRITICAL_SECTION StateLock;	
		wiimote_state	 Internal;
		state_change_flags InternalChanged;   // state changes since last RefreshState()
		// periodic status report requests (for battery level and connection loss
		//  detection)
		DWORD			  NextStatusTime;
		DWORD			  NextMPlusDetectTime;// gap between motion plus detections
		DWORD			  MPlusDetectCount;	  // # of detection tries in quick succesion
		// async Hidd_WriteReport() thread
		HANDLE			  HIDwriteThread;
#ifdef USE_DYNAMIC_HIDQUEUE
		std::queue<BYTE*> HIDwriteQueue;
#else
		// fixed-size queue (to eliminate glitches caused by frequent dynamic memory
		//	allocations)
		struct hid
			{
			hid () : Queue(NULL), ReadIndex(0), WriteIndex(0) {}

			// Increase the static queue size if you get ASSERTs signalling an
			//  overflow (too many reports queued up before being sent by the write
			//  thread).  These asserts are harmless though if caused as a result of
			//  loosing the wiimote connection (eg. battery runs out, or wiimote is
			//  unpaired by holding the power button).
			// Note: MAX_QUEUE_ENTRIES _must_ be a power-of-2, as it
			//  uses index wraparound optimisations.
			static const unsigned MAX_QUEUE_ENTRIES = 1<<7;
		
			inline bool IsEmpty() const { return (ReadIndex == WriteIndex); }

			bool Allocate	()	{ // allocate memory (only when needed)
								_ASSERT(!Queue); if(Queue) return true;
								ReadIndex = WriteIndex = 0;
								Queue = new queue_entry[MAX_QUEUE_ENTRIES];
								_ASSERT(Queue); return (Queue != NULL);
								}
			void Deallocate ()	{
								if(!Queue) return;
								delete[] Queue; Queue = NULL;
								ReadIndex = WriteIndex = 0;
								}
								
			struct queue_entry
				{
				queue_entry() { memset(Report, 0, sizeof(Report)); }
				
				BYTE Report [REPORT_LENGTH];
				} *Queue;
			
			unsigned ReadIndex, WriteIndex;
			} HID;
#endif
		CRITICAL_SECTION  HIDwriteQueueLock;  // queue must be locked before being  modified

		// async rumble
		HANDLE			 AsyncRumbleThread;	  // automatically disables rumble if requested
		volatile DWORD	 AsyncRumbleTimeout;
		// orientation estimation
		unsigned		 WiimoteNearGUpdates;
		unsigned		 NunchukNearGUpdates;
		// audio
		HANDLE			 SampleThread;
		const wiimote_sample* volatile CurrentSample;	// otherwise playing square wave
		// state recording
		struct recording
			{
			volatile bool	bEnabled;
			state_history	*StateHistory;
			volatile DWORD	StartTimeMS;
			volatile DWORD	EndTimeMS;		// can be UNTIL_STOP
			unsigned		TriggerFlags;	// wiimote changes trigger a state event
			unsigned		ExtTriggerFlags;// extension changes "
			} Recording;
	};

#endif // _WIIMOTE_H