From 3941c631a7a6aedeb0c7d49c994f5c79cd7424c5 Mon Sep 17 00:00:00 2001 From: Alexander Eckart Date: Sun, 9 Dec 2012 19:07:30 +0100 Subject: [PATCH] added missing files --- Vocaluxe/Base/CInput.cs | 61 +++ Vocaluxe/Lib/Input/CHIDAPI.cs | 267 +++++++++ Vocaluxe/Lib/Input/IInput.cs | 145 +++++ Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs | 327 +++++++++++ Vocaluxe/Lib/Input/WiiMote/WiiMoteLib.cs | 669 +++++++++++++++++++++++ 5 files changed, 1469 insertions(+) create mode 100644 Vocaluxe/Base/CInput.cs create mode 100644 Vocaluxe/Lib/Input/CHIDAPI.cs create mode 100644 Vocaluxe/Lib/Input/IInput.cs create mode 100644 Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs create mode 100644 Vocaluxe/Lib/Input/WiiMote/WiiMoteLib.cs diff --git a/Vocaluxe/Base/CInput.cs b/Vocaluxe/Base/CInput.cs new file mode 100644 index 00000000..28af9bf8 --- /dev/null +++ b/Vocaluxe/Base/CInput.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Vocaluxe.Lib.Input; +using Vocaluxe.Lib.Input.WiiMote; + +namespace Vocaluxe.Base +{ + static class CInput + { + static IInput _Input; + + public static void Init() + { + _Input = new CWiiMote(); + + _Input.Init(); + } + + public static void Close() + { + _Input.Close(); + } + + public static bool Connect() + { + return _Input.Connect(); + } + + public static bool Disconnect() + { + return _Input.Disconnect(); + } + + public static bool IsConnected() + { + return _Input.IsConnected(); + } + + public static void Update() + { + _Input.Update(); + } + + public static bool PollKeyEvent(ref KeyEvent KeyEvent) + { + return _Input.PollKeyEvent(ref KeyEvent); + } + + public static bool PollMouseEvent(ref MouseEvent MouseEvent) + { + return _Input.PollMouseEvent(ref MouseEvent); + } + + public static void SetRumble(float Duration) + { + _Input.SetRumble(Duration); + } + } +} diff --git a/Vocaluxe/Lib/Input/CHIDAPI.cs b/Vocaluxe/Lib/Input/CHIDAPI.cs new file mode 100644 index 00000000..f7157205 --- /dev/null +++ b/Vocaluxe/Lib/Input/CHIDAPI.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using Vocaluxe.Base; + +namespace Vocaluxe.Lib.Input +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct HIDDeviceInfo + { + [MarshalAsAttribute(UnmanagedType.LPTStr)] + public String Path; + public ushort VendorString; + public ushort ProductID; + public String SerialNumber; + public ushort ReleaseNumber; + public String ManufacturerString; + public String ProductString; + public ushort UsagePage; + public ushort Usage; + public int InterfaceNumber; + public IntPtr Next; + } + public static class CHIDAPI + { +#if ARCH_X86 +#if WIN + private const string HIDapiDll = "x86\\hidapi.dll"; +#endif + +#if LINUX + private const string HIDapiDll = "libhidapi.so"; +#endif +#endif + +#if ARCH_X64 +#if WIN + private const string HIDapiDll = "x64\\hidapi.dll"; +#endif + +#if LINUX + private const string HIDapiDll = "libhidapi.so"; +#endif +#endif + + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_init", CharSet = CharSet.Unicode)] + private static extern int hid_init(); + public static bool Init() + { + int result = -1; + try + { + result = hid_init(); + } + catch(Exception e) + { + CLog.LogError("Error CHIDAPI.Init(): " + e.ToString()); + return false; + } + + if (result == 0) + return true; + + return false; + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_exit", CharSet = CharSet.Unicode)] + private static extern int hid_exit(); + public static bool Exit() + { + int result = -1; + try + { + result = hid_exit(); + } + catch (Exception e) + { + CLog.LogError("Error CHIDAPI.Exit(): " + e.ToString()); + return false; + } + + if (result == 0) + return true; + + return false; + } + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_enumerate", CharSet = CharSet.Ansi)] + private static extern IntPtr hid_enumerate(ushort VendorID, ushort ProductID); //HIDDeviceInfo + public static IntPtr Enumerate(ushort VendorID, ushort ProductID) //HIDDeviceInfo + { + return hid_enumerate(VendorID, ProductID); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_free_enumeration", CharSet = CharSet.Unicode)] + private static extern void hid_free_enumeration(HIDDeviceInfo devs); + public static void FreeEnumeration(HIDDeviceInfo devs) + { + hid_free_enumeration(devs); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_open", CharSet = CharSet.Unicode)] + private static extern IntPtr hid_open(ushort VendorID, ushort ProductID, IntPtr SerialNumber); + public static bool Open(ushort VendorID, ushort ProductID, out IntPtr Handle) + { + Handle = IntPtr.Zero; + try + { + Handle = hid_open(VendorID, ProductID, IntPtr.Zero); + } + catch (Exception e) + { + CLog.LogError("Error CHIDAPI.Open(): " + e.ToString()); + return false; + } + + if (Handle != IntPtr.Zero) + return true; + + return false; + + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_open_path", CharSet = CharSet.Unicode)] + private static extern IntPtr hid_open_path(string Path); + public static IntPtr OpenPath(string Path) + { + return hid_open_path(Path); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_write", CharSet = CharSet.Unicode)] + private static extern int hid_write(IntPtr device, byte[] data, int length); + public static int Write(IntPtr Device, byte[] Data) + { + return hid_write(Device, Data, Data.Length); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_read_timeout", CharSet = CharSet.Unicode)] + private static extern int hid_read_timeout(IntPtr device, IntPtr data, int length, int milliseconds); + public static int ReadTimeout(IntPtr Device, out byte[] Data, int length, int milliseconds) + { + Data = new byte[length]; + IntPtr data = Marshal.AllocHGlobal(length); + + int result = -1; + try + { + result = hid_read_timeout(Device, data, length, milliseconds); + } + catch (Exception e) + { + result = -1; + CLog.LogError("Error CHIDAPI.ReadTimeout(): " + e.ToString()); + } + + if (result != -1) + Marshal.Copy(data, Data, 0, result); + else + Data = null; + + Marshal.FreeHGlobal(data); + return result; + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_read", CharSet = CharSet.Unicode)] + private static extern int hid_read(IntPtr device, IntPtr data, int length); + public static int Read(IntPtr Device, out byte[] Data, int length) + { + Data = new byte[length]; + IntPtr data = Marshal.AllocHGlobal(length); + + int result = -1; + try + { + result = hid_read(Device, data, length); + } + catch (Exception e) + { + result = -1; + CLog.LogError("Error CHIDAPI.Read(): " + e.ToString()); + } + + if (result != -1) + Marshal.Copy(data, Data, 0, result); + else + Data = null; + + Marshal.FreeHGlobal(data); + return result; + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_set_nonblocking", CharSet = CharSet.Unicode)] + private static extern int hid_set_nonblocking(IntPtr device, bool nonblock); + public static int Read(IntPtr Device, bool Nonblocking) + { + return hid_set_nonblocking(Device, Nonblocking); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_send_feature_report", CharSet = CharSet.Unicode)] + private static extern int hid_send_feature_report(IntPtr device, string data, int length); + public static int SendFeatureReport(IntPtr Device, string Data) + { + return hid_send_feature_report(Device, Data, Data.Length); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_get_feature_report", CharSet = CharSet.Unicode)] + private static extern int hid_get_feature_report(IntPtr device, string Data, int length); + public static int GetFeatureReport(IntPtr Device, string Data) + { + return hid_get_feature_report(Device, Data, Data.Length); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_close", CharSet = CharSet.Unicode)] + private static extern void hid_close(IntPtr device); + public static bool Close(IntPtr Device) + { + try + { + hid_close(Device); + } + catch (Exception e) + { + CLog.LogError("Error CHIDAPI.Close(): " + e.ToString()); + return false; + } + return true; + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_get_manufacturer_string", CharSet = CharSet.Unicode)] + private static extern int hid_get_manufacturer_string(IntPtr device, string Data, int maxlength); + public static int GetManufacturerString(IntPtr Device, string Data, int MaxLength) + { + return hid_get_manufacturer_string(Device, Data, MaxLength); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_get_product_string", CharSet = CharSet.Unicode)] + private static extern int hid_get_product_string(IntPtr device, string Data, int maxlength); + public static int GetProductString(IntPtr Device, string Data, int MaxLength) + { + return hid_get_product_string(Device, Data, MaxLength); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_get_serial_number_string", CharSet = CharSet.Unicode)] + private static extern int hid_get_serial_number_string(IntPtr device, string Data, int maxlength); + public static int GetSerialNumberString(IntPtr Device, string Data, int MaxLength) + { + return hid_get_serial_number_string(Device, Data, MaxLength); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_get_indexed_string", CharSet = CharSet.Unicode)] + private static extern int hid_get_indexed_string(IntPtr device, string Data, int maxlength); + public static int GetIndexedString(IntPtr Device, string Data, int MaxLength) + { + return hid_get_indexed_string(Device, Data, MaxLength); + } + + [DllImport(HIDapiDll, ExactSpelling = false, CallingConvention = CallingConvention.Cdecl, EntryPoint = "hid_error", CharSet = CharSet.Unicode)] + private static extern string hid_error(IntPtr device); + public static string Error(IntPtr Device) + { + return hid_error(Device); + } + } +} diff --git a/Vocaluxe/Lib/Input/IInput.cs b/Vocaluxe/Lib/Input/IInput.cs new file mode 100644 index 00000000..86dcde04 --- /dev/null +++ b/Vocaluxe/Lib/Input/IInput.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Text; + +using Vocaluxe.Base; + +namespace Vocaluxe.Lib.Input +{ + enum EGesture + { + None, + Back, + ScrollDown, + ScrollUp + } + + interface IInput + { + bool Init(); + void Close(); + bool Connect(); + bool Disconnect(); + + bool IsConnected(); + void Update(); + + bool PollKeyEvent(ref KeyEvent KeyEvent); + bool PollMouseEvent(ref MouseEvent MouseEvent); + + void SetRumble(float Duration); + } + + class RumbleTimer + { + private Stopwatch _timer; + private float _duration; + + public bool ShouldStart + { + get + { + if (!_timer.IsRunning && _duration != 0f) + { + _timer.Start(); + return true; + } + + return false; + } + } + + public bool ShouldStop + { + get + { + if (_timer.IsRunning && _timer.ElapsedMilliseconds / 1000f >= _duration) + { + _timer.Reset(); + _duration = 0f; + return true; + } + + return false; + } + } + + public RumbleTimer() + { + _timer = new Stopwatch(); + _duration = 0f; + } + + public void Set(float Duration) + { + if (!_timer.IsRunning) + { + _duration = Duration; + _timer.Reset(); + } + } + + public void Reset() + { + _duration = 0f; + } + } + + class CGesture + { + Point _Begin; + bool _locked; + + public CGesture() + { + _locked = false; + } + + public void SetLockPosition(Point Position) + { + _locked = true; + _Begin = new Point(Position.X, Position.Y); + } + + public void Reset() + { + _locked = false; + } + + public EGesture GetGesture(Point NewPosition) + { + if (!_locked) + return EGesture.None; + + int dx = NewPosition.X - _Begin.X; + int dy = NewPosition.Y - _Begin.Y; + + //Back/Escape + if (dx < -150 && Math.Abs(dy) < 150) + { + _locked = false; + return EGesture.Back; + } + + //ScrollDown + if (Math.Abs(dx) < 150 && dy > 30) + { + _Begin.Y = NewPosition.Y; + _Begin.X = NewPosition.X; + return EGesture.ScrollDown; + } + + //ScrollUp + if (Math.Abs(dx) < 150 && dy < -30) + { + _Begin.Y = NewPosition.Y; + _Begin.X = NewPosition.X; + return EGesture.ScrollUp; + } + + return EGesture.None; + } + } +} diff --git a/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs b/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs new file mode 100644 index 00000000..4578723c --- /dev/null +++ b/Vocaluxe/Lib/Input/WiiMote/CWiiMote.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Windows.Forms; +using System.Text; +using System.Threading; + +using Vocaluxe.Base; + +namespace Vocaluxe.Lib.Input.WiiMote +{ + class CWiiMote : IInput + { + WiiMoteLib _WiiMote; + + private List _KeysPool; + private List _CurrentKeysPool; + private Object _KeyCopyLock = new Object(); + + private List _MousePool; + private List _CurrentMousePool; + private Object _MouseCopyLock = new Object(); + + private bool[] _ButtonStates; + private Point _OldPosition; + private bool _Connected; + + private Thread _HandlerThread; + private Object _Sync; + private bool _Active; + private RumbleTimer _RumbleTimer; + + private CGesture _Gesture; + + public bool Init() + { + _Sync = new Object(); + _RumbleTimer = new RumbleTimer(); + _Gesture = new CGesture(); + + _Active = true; + _HandlerThread = new Thread(MainLoop); + _HandlerThread.Priority = ThreadPriority.BelowNormal; + + _KeysPool = new List(); + _CurrentKeysPool = new List(); + + _MousePool = new List(); + _CurrentMousePool = new List(); + + _ButtonStates = new bool[11]; + _OldPosition = new Point(); + + _HandlerThread.Start(); + + return true; + } + + public void Close() + { + _Active = false; + } + + public bool Connect() + { + if (!_Active) + { + _Active = true; + _HandlerThread.Start(); + } + return true; + } + + + public bool Disconnect() + { + _Active = false; + return true; + } + + public bool IsConnected() + { + return _Connected; + } + + public void Update() + { + CopyEvents(); + } + + public bool PollKeyEvent(ref KeyEvent KeyEvent) + { + if (_CurrentKeysPool.Count > 0) + { + KeyEvent = _CurrentKeysPool[0]; + _CurrentKeysPool.RemoveAt(0); + return true; + } + else return false; + } + + public bool PollMouseEvent(ref MouseEvent MouseEvent) + { + if (_CurrentMousePool.Count > 0) + { + MouseEvent = _CurrentMousePool[0]; + _CurrentMousePool.RemoveAt(0); + return true; + } + else return false; + } + + public void SetRumble(float Duration) + { + lock (_Sync) + { + _RumbleTimer.Set(Duration); + } + } + + private void MainLoop() + { + _WiiMote = new WiiMoteLib(); + _WiiMote.WiiMoteChanged += wm_WiiMoteChanged; + + while (_Active) + { + Thread.Sleep(5); + + if (!_WiiMote.Connected) + { + if (!DoConnect()) + Thread.Sleep(1000); + } + else + { + bool StartRumble = false; + bool StopRumble = false; + lock (_Sync) + { + StartRumble = _RumbleTimer.ShouldStart; + StopRumble = _RumbleTimer.ShouldStop; + } + + if (StartRumble) + _WiiMote.SetRumble(true); + + if (StopRumble) + _WiiMote.SetRumble(false); + } + } + + _WiiMote.SetRumble(false); + _WiiMote.Disconnect(); + _Connected = false; + } + + private bool DoConnect() + { + try + { + if (!_WiiMote.Connect()) + return false; + } + catch + { + return false; ; + } + + _WiiMote.SetReportType(InputReport.IRAccel, IRSensitivity.Max, false); + _WiiMote.SetLEDs(false, false, true, false); + + _WiiMote.SetRumble(true); + Thread.Sleep(250); + _WiiMote.SetRumble(false); + Thread.Sleep(250); + _WiiMote.SetRumble(true); + Thread.Sleep(250); + _WiiMote.SetRumble(false); + + _Connected = true; + return true; + } + + private void wm_WiiMoteChanged(object sender, WiiMoteChangedEventArgs args) + { + if (!_Active) + return; + + WiiMoteStatus ws = args.WiiMoteState; + + Point p = ws.IRState.Position; + p.X = 1023 - p.X; + + //key events + bool alt = false; + bool shift = false; + bool ctrl = false; + bool pressed = false; + char unicode = char.MinValue; + + EGesture gesture = _Gesture.GetGesture(p); + bool lb = false; + bool rb = gesture == EGesture.Back; + + Keys key = Keys.None; + + if (ws.ButtonState.A && !_ButtonStates[0]) + lb = true; + else if (ws.ButtonState.B && !_ButtonStates[1]) + _Gesture.SetLockPosition(p); + else if (!ws.ButtonState.B && _ButtonStates[1]) + _Gesture.Reset(); + else if (ws.ButtonState.Down && !_ButtonStates[2]) + key = Keys.Right; + else if (ws.ButtonState.Up && !_ButtonStates[3]) + key = Keys.Left; + else if (ws.ButtonState.Left && !_ButtonStates[4]) + key = Keys.Down; + else if (ws.ButtonState.Right && !_ButtonStates[5]) + key = Keys.Up; + else if (ws.ButtonState.Home && !_ButtonStates[6]) + key = Keys.Space; + else if (ws.ButtonState.Minus && !_ButtonStates[7]) + key = Keys.PageDown; + else if (ws.ButtonState.Plus && !_ButtonStates[8]) + key = Keys.PageUp; + else if (ws.ButtonState.One && !_ButtonStates[9]) + key = Keys.Enter; + else if (ws.ButtonState.Two && !_ButtonStates[10]) + key = Keys.Escape; + + _ButtonStates[0] = ws.ButtonState.A; + _ButtonStates[1] = ws.ButtonState.B; + _ButtonStates[2] = ws.ButtonState.Down; + _ButtonStates[3] = ws.ButtonState.Up; + _ButtonStates[4] = ws.ButtonState.Left; + _ButtonStates[5] = ws.ButtonState.Right; + _ButtonStates[6] = ws.ButtonState.Home; + _ButtonStates[7] = ws.ButtonState.Minus; + _ButtonStates[8] = ws.ButtonState.Plus; + _ButtonStates[9] = ws.ButtonState.One; + _ButtonStates[10] = ws.ButtonState.Two; + + + if (alt || shift || ctrl || pressed || unicode != char.MinValue || key != Keys.None) + { + KeyEvent pool = new KeyEvent(ESender.WiiMote, alt, shift, ctrl, pressed, unicode, key); + + lock (_KeyCopyLock) + { + _KeysPool.Add(pool); + } + } + + //mouse events + float reducing = 0.15f; + float factor = 1f / (1f - reducing * 2f); + float rx = (((float)p.X / 1024f) - reducing) * factor; + float ry = (((float)p.Y / 768f) - reducing) * factor; + + int x = (int)(rx * (float)CSettings.iRenderW); + int y = (int)(ry * (float)CSettings.iRenderH); + + + bool ld = false; + bool lbh = !lb && ws.ButtonState.A; + bool rbh = false; + bool mb = false; + bool mbh = false; + + int wheel = 0; + if (gesture == EGesture.ScrollUp) + wheel = -1; + if (gesture == EGesture.ScrollDown) + wheel = 1; + + MouseEvent mpool = new MouseEvent(); + bool trigger = false; + + if (!lb && !rb && (p.X != _OldPosition.X || p.Y != _OldPosition.Y)) + { + mpool = new MouseEvent(ESender.WiiMote, alt, shift, ctrl, x, y, false, false, false, wheel, lbh, rbh, false, mbh); + trigger = true; + } + else if (lb || rb) + { + mpool = new MouseEvent(ESender.WiiMote, alt, shift, ctrl, x, y, lb, ld, rb, wheel, false, false, mb, false); + trigger = true; + } + + + if (trigger) + { + lock (_MouseCopyLock) + { + _MousePool.Add(mpool); + } + } + + _OldPosition.X = p.X; + _OldPosition.Y = p.Y; + } + + private void CopyEvents() + { + lock (_KeyCopyLock) + { + foreach (KeyEvent e in _KeysPool) + { + _CurrentKeysPool.Add(e); + } + _KeysPool.Clear(); + } + + lock (_MouseCopyLock) + { + foreach (MouseEvent e in _MousePool) + { + _CurrentMousePool.Add(e); + } + _MousePool.Clear(); + } + } + } +} diff --git a/Vocaluxe/Lib/Input/WiiMote/WiiMoteLib.cs b/Vocaluxe/Lib/Input/WiiMote/WiiMoteLib.cs new file mode 100644 index 00000000..888301e5 --- /dev/null +++ b/Vocaluxe/Lib/Input/WiiMote/WiiMoteLib.cs @@ -0,0 +1,669 @@ +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Threading; + +using Vocaluxe.Base; +using Vocaluxe.Lib.Draw; +using Vocaluxe.Lib.Input; + +namespace Vocaluxe.Lib.Input.WiiMote +{ + #region DataTypes + public class WiiMoteStatus + { + public AccelCalibrationInfo AccelCalibrationInfo = new AccelCalibrationInfo(); + + public AccelStatus AccelState = new AccelStatus(); + + public Buttons ButtonState = new Buttons(); + + public IRState IRState = new IRState(); + + public byte Battery; + + public bool Rumble; + + public LEDStatus LEDState; + + public WiiMoteStatus() + { + IRState.Sensors = new IR[4]; + } + } + + public struct LEDStatus + { + public bool LED1; + public bool LED2; + public bool LED3; + public bool LED4; + } + + public enum IRMode : byte + { + Off = 0x00, + Basic = 0x01, // 10 bytes + Extended = 0x03 // 12 bytes + }; + + public struct IR + { + public bool Active; + public Point Position; // X: 0-1023; Y: 0-767 + public int Width; // 0-15 + } + + public struct IRState + { + public IRMode Mode; + public IR[] Sensors; + public Point Position; //0..1023, 0..767 + public Point Distance; //between Point 0 and Point 1 + } + + public struct AccelStatus + { + public SPoint3 RawValues; //0-255 + public SPoint3f Values; //0-3 + } + + public struct AccelCalibrationInfo + { + public byte X0, Y0, Z0; //zero point + public byte XG, YG, ZG; //gravity + } + + public struct Buttons + { + public bool Up, Down, Left, Right, A, B, Plus, Minus, One, Two, Home; + } + + public enum InputReport : byte + { + Status = 0x20, + ReadData = 0x21, + Ack = 0x22, + Buttons = 0x30, + ButtonsAccel = 0x31, + Buttons8Bytes = 0x32, + IRAccel = 0x33, + ButtonsExtension = 0x34, + ExtensionAccel = 0x35, + IRExtensionAccel = 0x37, + }; + + public enum IRSensitivity + { + Level1, + Level2, + Level3, + Level4, + Level5, + Max + } + #endregion DataTypes + + #region Events + public class WiiMoteChangedEventArgs: EventArgs + { + public WiiMoteStatus WiiMoteState; + + public WiiMoteChangedEventArgs(WiiMoteStatus ws) + { + WiiMoteState = ws; + } + } + #endregion Events + + public class WiiMoteLib : IDisposable + { + private const ushort VID = 0x057e; + private const ushort PID = 0x0306; //Wiimotion + private const ushort PIDPlus = 0x0330; //Wiimotion Plus + + // registers + private const int REGISTER_IR = 0x04b00030; + private const int REGISTER_IR_SENSITIVITY_1 = 0x04b00000; + private const int REGISTER_IR_SENSITIVITY_2 = 0x04b0001a; + private const int REGISTER_IR_MODE = 0x04b00033; + + private const int REPORT_LENGTH = 22; + + // output commands + private enum OutputReport : byte + { + LEDs = 0x11, + Type = 0x12, + IR = 0x13, + Status = 0x15, + WriteMemory = 0x16, + ReadMemory = 0x17, + SpeakerData = 0x18, + SpeakerMute = 0x19, + IR2 = 0x1a, + }; + + // data handling + private IntPtr _Handle; + private readonly byte[] _Buff = new byte[REPORT_LENGTH]; + private byte[] _ReadBuff; + private int _Address; + private short _Size; + + private Thread _Reader; + private bool _Active; + + private bool _Connected; + public bool Connected + { + get + { + return _Connected; + } + } + + private readonly WiiMoteStatus _WiiMoteState = new WiiMoteStatus(); + private readonly AutoResetEvent _ReadDone = new AutoResetEvent(false); + public event EventHandler WiiMoteChanged; + + #region Interface + public WiiMoteLib() + { + _Connected = false; + _Active = true; + _Reader = new Thread(ReaderLoop); + _Reader.Start(); + } + + public bool Connect() + { + _Connected = false; + CHIDAPI.Exit(); + + if (!CHIDAPI.Init()) + { + CLog.LogError("WiiMoteLib: Can't initialize HID API"); + return false; + } + + //Try WiiMotion + if (_Connected = CHIDAPI.Open(VID, PID, out _Handle)) + { + if (!(_Connected = ReadCalibration())) + CHIDAPI.Close(_Handle); + } + + if (_Connected) + return true; + + //Try WiiMotion Plus + if (_Connected = CHIDAPI.Open(VID, PIDPlus, out _Handle)) + { + if (!(_Connected = ReadCalibration())) + CHIDAPI.Close(_Handle); + } + + return _Connected; + } + + public void Disconnect() + { + _Connected = false; + _Active = false; + CHIDAPI.Exit(); + } + + public void SetReportType(InputReport type, IRSensitivity irSensitivity, bool continuous) + { + if (!_Connected) + return; + + switch (type) + { + case InputReport.IRAccel: + EnableIR(IRMode.Extended, irSensitivity); + break; + default: + DisableIR(); + break; + } + + ClearReport(); + _Buff[0] = (byte)OutputReport.Type; + _Buff[1] = (byte)((continuous ? 0x04 : 0x00) | (byte)(_WiiMoteState.Rumble ? 0x01 : 0x00)); + _Buff[2] = (byte)type; + + CHIDAPI.Write(_Handle, _Buff); + } + + public void SetLEDs(bool led1, bool led2, bool led3, bool led4) + { + _WiiMoteState.LEDState.LED1 = led1; + _WiiMoteState.LEDState.LED2 = led2; + _WiiMoteState.LEDState.LED3 = led3; + _WiiMoteState.LEDState.LED4 = led4; + + if (!_Connected) + return; + + ClearReport(); + + _Buff[0] = (byte)OutputReport.LEDs; + _Buff[1] = (byte)( + (led1 ? 0x10 : 0x00) | + (led2 ? 0x20 : 0x00) | + (led3 ? 0x40 : 0x00) | + (led4 ? 0x80 : 0x00) | + RumbleBit); + + CHIDAPI.Write(_Handle, _Buff); + } + + public void SetRumble(bool on) + { + _WiiMoteState.Rumble = on; + + // the LED report also handles rumble + SetLEDs(_WiiMoteState.LEDState.LED1, + _WiiMoteState.LEDState.LED2, + _WiiMoteState.LEDState.LED3, + _WiiMoteState.LEDState.LED4); + } + + public WiiMoteStatus WiiMoteState + { + get { return _WiiMoteState; } + } + #endregion Interface + + #region Private stuff + private void ReaderLoop() + { + while (_Active) + { + Thread.Sleep(5); + + if (_Handle != IntPtr.Zero && _Connected) + { + byte[] buff = new byte[REPORT_LENGTH]; + + try + { + CHIDAPI.ReadTimeout(_Handle, out buff, REPORT_LENGTH, 100); + } + catch (Exception e) + { + CLog.LogError("(WiiMoteLib) Error reading from device: " + e.ToString()); + } + + if (ParseInputReport(buff)) + { + if (WiiMoteChanged != null) + WiiMoteChanged(this, new WiiMoteChangedEventArgs(_WiiMoteState)); + } + } + } + } + + private bool ParseInputReport(byte[] buff) + { + if (buff == null) + { + _Connected = false; + return false; + } + + InputReport type = (InputReport)buff[0]; + + switch(type) + { + case InputReport.Buttons: + ParseButtons(buff); + break; + + case InputReport.ButtonsAccel: + ParseButtons(buff); + ParseAccel(buff); + break; + + case InputReport.IRAccel: + ParseButtons(buff); + ParseAccel(buff); + ParseIR(buff); + break; + + case InputReport.ButtonsExtension: + ParseButtons(buff); + break; + + case InputReport.ExtensionAccel: + ParseButtons(buff); + ParseAccel(buff); + break; + + case InputReport.IRExtensionAccel: + ParseButtons(buff); + ParseAccel(buff); + ParseIR(buff); + break; + + case InputReport.Status: + ParseButtons(buff); + _WiiMoteState.Battery = buff[6]; + + _WiiMoteState.LEDState.LED1 = (buff[3] & 0x10) != 0; + _WiiMoteState.LEDState.LED2 = (buff[3] & 0x20) != 0; + _WiiMoteState.LEDState.LED3 = (buff[3] & 0x40) != 0; + _WiiMoteState.LEDState.LED4 = (buff[3] & 0x80) != 0; + break; + + case InputReport.ReadData: + ParseButtons(buff); + ParseReadData(buff); + break; + + case InputReport.Ack: + return false; + + case InputReport.Buttons8Bytes: + break; + + default: + CLog.LogError("(WiiMoteLib) Unknown report type: " + type.ToString("x")); + return false; + } + + return true; + } + + private byte[] DecryptBuffer(byte[] buff) + { + for(int i = 0; i < buff.Length; i++) + buff[i] = (byte)(((buff[i] ^ 0x17) + 0x17) & 0xff); + + return buff; + } + + private void ParseButtons(byte[] buff) + { + _WiiMoteState.ButtonState.A = (buff[2] & 0x08) != 0; + _WiiMoteState.ButtonState.B = (buff[2] & 0x04) != 0; + _WiiMoteState.ButtonState.Minus = (buff[2] & 0x10) != 0; + _WiiMoteState.ButtonState.Home = (buff[2] & 0x80) != 0; + _WiiMoteState.ButtonState.Plus = (buff[1] & 0x10) != 0; + _WiiMoteState.ButtonState.One = (buff[2] & 0x02) != 0; + _WiiMoteState.ButtonState.Two = (buff[2] & 0x01) != 0; + _WiiMoteState.ButtonState.Up = (buff[1] & 0x08) != 0; + _WiiMoteState.ButtonState.Down = (buff[1] & 0x04) != 0; + _WiiMoteState.ButtonState.Left = (buff[1] & 0x01) != 0; + _WiiMoteState.ButtonState.Right = (buff[1] & 0x02) != 0; + } + + private void ParseAccel(byte[] buff) + { + _WiiMoteState.AccelState.RawValues.X = buff[3]; + _WiiMoteState.AccelState.RawValues.Y = buff[4]; + _WiiMoteState.AccelState.RawValues.Z = buff[5]; + + _WiiMoteState.AccelState.Values.X = (float)((float)_WiiMoteState.AccelState.RawValues.X - _WiiMoteState.AccelCalibrationInfo.X0) / + ((float)_WiiMoteState.AccelCalibrationInfo.XG - _WiiMoteState.AccelCalibrationInfo.X0); + _WiiMoteState.AccelState.Values.Y = (float)((float)_WiiMoteState.AccelState.RawValues.Y - _WiiMoteState.AccelCalibrationInfo.Y0) / + ((float)_WiiMoteState.AccelCalibrationInfo.YG - _WiiMoteState.AccelCalibrationInfo.Y0); + _WiiMoteState.AccelState.Values.Z = (float)((float)_WiiMoteState.AccelState.RawValues.Z - _WiiMoteState.AccelCalibrationInfo.Z0) / + ((float)_WiiMoteState.AccelCalibrationInfo.ZG - _WiiMoteState.AccelCalibrationInfo.Z0); + } + + private void ParseIR(byte[] buff) + { + switch(_WiiMoteState.IRState.Mode) + { + case IRMode.Basic: + if (_WiiMoteState.IRState.Sensors[0].Active = !(buff[6] == 0xff && buff[7] == 0xff)) + { + _WiiMoteState.IRState.Sensors[0].Position.X = buff[6] | ((buff[8] >> 4) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[0].Position.Y = buff[7] | ((buff[8] >> 6) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[0].Width = 0; + } + + if (_WiiMoteState.IRState.Sensors[1].Active = !(buff[9] == 0xff && buff[10] == 0xff)) + { + _WiiMoteState.IRState.Sensors[1].Position.X = buff[9] | ((buff[8] >> 0) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[1].Position.Y = buff[10] | ((buff[8] >> 2) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[1].Width = 0; + } + break; + case IRMode.Extended: + for (int i = 0; i < 4; i++) + { + if (_WiiMoteState.IRState.Sensors[i].Active = !(buff[6 + i * 3] == 0xff && buff[7 + i * 3] == 0xff && buff[8 + i * 3] == 0xff)) + { + _WiiMoteState.IRState.Sensors[i].Position.X = buff[6 + i * 3] | ((buff[8 + i * 3] >> 4) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[i].Position.Y = buff[7 + i * 3] | ((buff[8 + i * 3] >> 6) & 0x03) << 8; + _WiiMoteState.IRState.Sensors[i].Width = buff[8 + i * 3] & 0x0f; + } + } + break; + } + + if (_WiiMoteState.IRState.Sensors[0].Active && _WiiMoteState.IRState.Sensors[1].Active) + { + _WiiMoteState.IRState.Position.X = (_WiiMoteState.IRState.Sensors[1].Position.X + _WiiMoteState.IRState.Sensors[0].Position.X) / 2; + _WiiMoteState.IRState.Position.Y = (_WiiMoteState.IRState.Sensors[1].Position.Y + _WiiMoteState.IRState.Sensors[0].Position.Y) / 2; + + _WiiMoteState.IRState.Distance.X = Math.Abs(_WiiMoteState.IRState.Sensors[1].Position.X - _WiiMoteState.IRState.Sensors[0].Position.X); + _WiiMoteState.IRState.Distance.Y = Math.Abs(_WiiMoteState.IRState.Sensors[1].Position.Y - _WiiMoteState.IRState.Sensors[0].Position.Y); + } + else if (_WiiMoteState.IRState.Sensors[0].Active) + { + if (_WiiMoteState.IRState.Sensors[0].Position.X > 512) + _WiiMoteState.IRState.Position.X = _WiiMoteState.IRState.Sensors[0].Position.X - _WiiMoteState.IRState.Distance.X / 2; + else + _WiiMoteState.IRState.Position.X = _WiiMoteState.IRState.Sensors[0].Position.X + _WiiMoteState.IRState.Distance.X / 2; + + if (_WiiMoteState.IRState.Sensors[0].Position.X < 384) + _WiiMoteState.IRState.Position.Y = _WiiMoteState.IRState.Sensors[0].Position.Y - _WiiMoteState.IRState.Distance.Y / 2; + else + _WiiMoteState.IRState.Position.Y = _WiiMoteState.IRState.Sensors[0].Position.Y + _WiiMoteState.IRState.Distance.Y / 2; + } + + } + + private void ParseReadData(byte[] buff) + { + if ((buff[3] & 0x08) != 0) + { + CLog.LogError("Error reading data from WiiMote: Bytes do not exist"); + _Connected = false; + } + + if ((buff[3] & 0x07) != 0) + { + CLog.LogError("Error reading data from WiiMote: Attempt to read from write-only registers."); + _Connected = false; + } + + int size = (buff[3] >> 4) + 1; + int offset = (buff[4] << 8 | buff[5]); + + Array.Copy(buff, 6, _ReadBuff, offset - _Address, size); + + if(_Address + _Size == offset + size) + _ReadDone.Set(); + } + + private byte RumbleBit + { + get + { + if (_WiiMoteState.Rumble) + return (byte)0x1; + return (byte)0x0; + } + } + + private bool ReadCalibration() + { + // this appears to change the report type to 0x31 + byte[] buff = ReadData(0x0016, 7); + if (buff == null) + return false; + + _WiiMoteState.AccelCalibrationInfo.X0 = buff[0]; + _WiiMoteState.AccelCalibrationInfo.Y0 = buff[1]; + _WiiMoteState.AccelCalibrationInfo.Z0 = buff[2]; + _WiiMoteState.AccelCalibrationInfo.XG = buff[4]; + _WiiMoteState.AccelCalibrationInfo.YG = buff[5]; + _WiiMoteState.AccelCalibrationInfo.ZG = buff[6]; + + return true; + } + + private void EnableIR(IRMode mode, IRSensitivity Sensitivity) + { + _WiiMoteState.IRState.Mode = mode; + + ClearReport(); + _Buff[0] = (byte)OutputReport.IR; + _Buff[1] = (byte)(0x04 | RumbleBit); + CHIDAPI.Write(_Handle, _Buff); + Thread.Sleep(50); + + ClearReport(); + _Buff[0] = (byte)OutputReport.IR2; + _Buff[1] = (byte)(0x04 | RumbleBit); + CHIDAPI.Write(_Handle, _Buff); + Thread.Sleep(50); + + WriteData(REGISTER_IR, 0x08); + Thread.Sleep(50); + + switch(Sensitivity) + { + case IRSensitivity.Level1: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0x64, 0x00, 0xfe}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0xfd, 0x05}); + break; + case IRSensitivity.Level2: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0x96, 0x00, 0xb4}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0xb3, 0x04}); + break; + case IRSensitivity.Level3: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xaa, 0x00, 0x64}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0x63, 0x03}); + break; + case IRSensitivity.Level4: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x02, 0x00, 0x00, 0x71, 0x01, 0x00, 0xc8, 0x00, 0x36}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0x35, 0x03}); + break; + case IRSensitivity.Level5: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x07, 0x00, 0x00, 0x71, 0x01, 0x00, 0x72, 0x00, 0x20}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0x1, 0x03}); + break; + case IRSensitivity.Max: + WriteData(REGISTER_IR_SENSITIVITY_1, 9, new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x41}); + Thread.Sleep(50); + WriteData(REGISTER_IR_SENSITIVITY_2, 2, new byte[] {0x40, 0x00}); + break; + } + Thread.Sleep(50); + WriteData(REGISTER_IR_MODE, (byte)mode); + Thread.Sleep(50); + WriteData(REGISTER_IR, 0x08); + Thread.Sleep(50); + } + + private void DisableIR() + { + _WiiMoteState.IRState.Mode = IRMode.Off; + + ClearReport(); + _Buff[0] = (byte)OutputReport.IR; + _Buff[1] = RumbleBit; + CHIDAPI.Write(_Handle, _Buff); + + ClearReport(); + _Buff[0] = (byte)OutputReport.IR2; + _Buff[1] = RumbleBit; + CHIDAPI.Write(_Handle, _Buff); + } + + private void ClearReport() + { + Array.Clear(_Buff, 0, REPORT_LENGTH); + } + + private byte[] ReadData(int address, short size) + { + ClearReport(); + + _ReadBuff = new byte[size]; + _Address = address & 0xffff; + _Size = size; + + _Buff[0] = (byte)OutputReport.ReadMemory; + _Buff[1] = (byte)(((address & 0xff000000) >> 24) | RumbleBit); + _Buff[2] = (byte)((address & 0x00ff0000) >> 16); + _Buff[3] = (byte)((address & 0x0000ff00) >> 8); + _Buff[4] = (byte)(address & 0x000000ff); + + _Buff[5] = (byte)((size & 0xff00) >> 8); + _Buff[6] = (byte)(size & 0xff); + + CHIDAPI.Write(_Handle, _Buff); + + if (!_ReadDone.WaitOne(1000, false)) + { + _Connected = false; + return null; + } + + return _ReadBuff; + } + + private void WriteData(int address, byte data) + { + WriteData(address, 1, new byte[] { data }); + } + + private void WriteData(int address, byte size, byte[] buff) + { + ClearReport(); + + _Buff[0] = (byte)OutputReport.WriteMemory; + _Buff[1] = (byte)(((address & 0xff000000) >> 24) | RumbleBit); + _Buff[2] = (byte)((address & 0x00ff0000) >> 16); + _Buff[3] = (byte)((address & 0x0000ff00) >> 8); + _Buff[4] = (byte)(address & 0x000000ff); + _Buff[5] = size; + Array.Copy(buff, 0, _Buff, 6, size); + + CHIDAPI.Write(_Handle, _Buff); + + Thread.Sleep(100); + } + #endregion Private stuff + + #region IDisposable Members + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if(disposing) + Disconnect(); + } + #endregion + } +} \ No newline at end of file