// _______________________________________________________________________________ // // - 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) #pragma once #include "warns-begin.hpp" #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include #include // auto Unicode/Ansi support #include // for HID write method #include // for state recording typedef unsigned __int64 QWORD; #ifdef _MSC_VER // VC-specific: _DEBUG build only _ASSERT() sanity checks # include #elif defined(__MINGW32__) // define NDEBUG to disable assert # include # 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* const 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* const 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 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_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 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; }; #include "warns-end.hpp"