diff options
Diffstat (limited to 'wiiyourself/wiimote.h')
-rw-r--r-- | wiiyourself/wiimote.h | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/wiiyourself/wiimote.h b/wiiyourself/wiimote.h new file mode 100644 index 00000000..1db2c098 --- /dev/null +++ b/wiiyourself/wiimote.h @@ -0,0 +1,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
\ No newline at end of file |