diff --git a/Assets/Packages.meta b/Assets/Packages.meta new file mode 100644 index 0000000..ad993ff --- /dev/null +++ b/Assets/Packages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 977c5076cce48c44d8c96b2910de7b90 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins.meta b/Assets/Packages/JoyconLib_plugins.meta new file mode 100644 index 0000000..680f4ea --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d4993fbb6035f5f429e2a832337013cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac.meta b/Assets/Packages/JoyconLib_plugins/mac.meta new file mode 100644 index 0000000..5d11c21 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 15f0825d2ef934df2922bed07a85c87d +folderAsset: yes +timeCreated: 1428351278 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle.meta b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle.meta new file mode 100644 index 0000000..fbd2c5e --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle.meta @@ -0,0 +1,68 @@ +fileFormatVersion: 2 +guid: f735e311710bf4dff9c88fc40dde6fc8 +folderAsset: yes +timeCreated: 1440740276 +licenseType: Free +PluginImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + platformData: + Android: + enabled: 0 + settings: + CPU: AnyCPU + Any: + enabled: 1 + settings: {} + Editor: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: OSX + Linux: + enabled: 0 + settings: + CPU: x86 + Linux64: + enabled: 0 + settings: + CPU: x86_64 + LinuxUniversal: + enabled: 0 + settings: + CPU: None + OSXIntel: + enabled: 1 + settings: + CPU: AnyCPU + OSXIntel64: + enabled: 1 + settings: + CPU: AnyCPU + OSXUniversal: + enabled: 1 + settings: + CPU: AnyCPU + SamsungTV: + enabled: 0 + settings: + STV_MODEL: STANDARD_13 + Win: + enabled: 0 + settings: + CPU: AnyCPU + Win64: + enabled: 0 + settings: + CPU: AnyCPU + iOS: + enabled: 0 + settings: + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents.meta b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents.meta new file mode 100644 index 0000000..64a8127 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 82e3be018a2c848249d81909ca06aecb +folderAsset: yes +timeCreated: 1440740276 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist new file mode 100644 index 0000000..faea747 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + 14F27 + CFBundleDevelopmentRegion + en + CFBundleExecutable + hidapi + CFBundleIdentifier + com.signal11.hidapi + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + hidapi + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 6E35b + DTPlatformVersion + GM + DTSDKBuild + 14D125 + DTSDKName + macosx10.10 + DTXcode + 0640 + DTXcodeBuild + 6E35b + NSHumanReadableCopyright + Copyright © 2015 signal11. All rights reserved. + + diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist.meta b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist.meta new file mode 100644 index 0000000..c56cd35 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/Info.plist.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9f5358b3db23a49fbba414e2930f127e +timeCreated: 1440740277 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS.meta b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS.meta new file mode 100644 index 0000000..38a3eea --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: bf449f9900e31488980ab02b943a91e4 +folderAsset: yes +timeCreated: 1440740277 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi new file mode 100644 index 0000000..9f6ab9f Binary files /dev/null and b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi differ diff --git a/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi.meta b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi.meta new file mode 100644 index 0000000..58465e7 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/mac/hidapi.bundle/Contents/MacOS/hidapi.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 175018180ed414521b0d47d393f347df +timeCreated: 1440740277 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/win32.meta b/Assets/Packages/JoyconLib_plugins/win32.meta new file mode 100644 index 0000000..f61fe57 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/win32.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 343916bd94f865c4588899b9beb2ed4b +folderAsset: yes +timeCreated: 1428204712 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll b/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll new file mode 100644 index 0000000..7f9a6dc Binary files /dev/null and b/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll differ diff --git a/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll.meta b/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll.meta new file mode 100644 index 0000000..205c01d --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/win32/hidapi.dll.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: ee0f84db49b44d7448a02c2d48ba6060 +timeCreated: 1428024569 +licenseType: Free +PluginImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + platformData: + Android: + enabled: 0 + settings: + CPU: AnyCPU + Any: + enabled: 0 + settings: {} + Editor: + enabled: 1 + settings: + CPU: x86 + DefaultValueInitialized: true + OS: Windows + Linux: + enabled: 1 + settings: + CPU: x86 + Linux64: + enabled: 1 + settings: + CPU: x86_64 + LinuxUniversal: + enabled: 1 + settings: + CPU: AnyCPU + OSXIntel: + enabled: 1 + settings: + CPU: AnyCPU + OSXIntel64: + enabled: 1 + settings: + CPU: AnyCPU + OSXUniversal: + enabled: 1 + settings: + CPU: AnyCPU + SamsungTV: + enabled: 0 + settings: + STV_MODEL: STANDARD_13 + WP8: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + Win: + enabled: 1 + settings: + CPU: AnyCPU + Win64: + enabled: 0 + settings: + CPU: None + WindowsStoreApps: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + SDK: AnySDK + iOS: + enabled: 0 + settings: + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/win64.meta b/Assets/Packages/JoyconLib_plugins/win64.meta new file mode 100644 index 0000000..d4cc087 --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/win64.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 471c2f708219a7a4a8cb7c55f984a1d1 +folderAsset: yes +timeCreated: 1428204958 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll b/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll new file mode 100644 index 0000000..bfb6c27 Binary files /dev/null and b/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll differ diff --git a/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll.meta b/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll.meta new file mode 100644 index 0000000..53aaf3b --- /dev/null +++ b/Assets/Packages/JoyconLib_plugins/win64/hidapi.dll.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: 7dcce2f108b04db43aa674e44c1c1235 +timeCreated: 1428204958 +licenseType: Free +PluginImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + platformData: + Android: + enabled: 0 + settings: + CPU: AnyCPU + Any: + enabled: 0 + settings: {} + Editor: + enabled: 1 + settings: + CPU: x86_64 + DefaultValueInitialized: true + OS: Windows + Linux: + enabled: 1 + settings: + CPU: x86 + Linux64: + enabled: 1 + settings: + CPU: x86_64 + LinuxUniversal: + enabled: 1 + settings: + CPU: AnyCPU + OSXIntel: + enabled: 1 + settings: + CPU: AnyCPU + OSXIntel64: + enabled: 1 + settings: + CPU: AnyCPU + OSXUniversal: + enabled: 1 + settings: + CPU: AnyCPU + SamsungTV: + enabled: 0 + settings: + STV_MODEL: STANDARD_13 + WP8: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + Win: + enabled: 0 + settings: + CPU: None + Win64: + enabled: 1 + settings: + CPU: AnyCPU + WindowsStoreApps: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + SDK: AnySDK + iOS: + enabled: 0 + settings: + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_scripts.meta b/Assets/Packages/JoyconLib_scripts.meta new file mode 100644 index 0000000..d94c2e3 --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eb3c833dad0d68844b05fd5b4ee1f0d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_scripts/HIDapi.cs b/Assets/Packages/JoyconLib_scripts/HIDapi.cs new file mode 100644 index 0000000..a277a38 --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/HIDapi.cs @@ -0,0 +1,77 @@ +using UnityEngine; +using System.Runtime.InteropServices; +using System.Collections; +using System; +using System.Text; + + +public class HIDapi { + + [DllImport("hidapi")] + public static extern int hid_init(); + + [DllImport("hidapi")] + public static extern int hid_exit(); + + [DllImport("hidapi")] + public static extern IntPtr hid_error(IntPtr device); + + [DllImport("hidapi")] + public static extern IntPtr hid_enumerate(ushort vendor_id, ushort product_id); + + [DllImport("hidapi")] + public static extern void hid_free_enumeration(IntPtr devs); + + [DllImport("hidapi")] + public static extern int hid_get_feature_report(IntPtr device, byte[] data, UIntPtr length); + + [DllImport("hidapi")] + public static extern int hid_get_indexed_string(IntPtr device, int string_index, StringBuilder str, UIntPtr maxlen); + + [DllImport("hidapi")] + public static extern int hid_get_manufacturer_string(IntPtr device, StringBuilder str, UIntPtr maxlen); + + [DllImport("hidapi")] + public static extern int hid_get_product_string(IntPtr device, StringBuilder str, UIntPtr maxlen); + + [DllImport("hidapi")] + public static extern int hid_get_serial_number_string(IntPtr device, StringBuilder str, UIntPtr maxlen); + + [DllImport("hidapi")] + public static extern IntPtr hid_open(ushort vendor_id, ushort product_id, string serial_number); + + [DllImport("hidapi")] + public static extern void hid_close(IntPtr device); + + [DllImport("hidapi")] + public static extern IntPtr hid_open_path(string path); + + [DllImport("hidapi")] + public static extern int hid_read(IntPtr device, byte[] data, UIntPtr length); + + [DllImport("hidapi")] + public static extern int hid_read_timeout(IntPtr dev, byte[] data, UIntPtr length, int milliseconds); + + [DllImport("hidapi")] + public static extern int hid_send_feature_report(IntPtr device, byte[] data, UIntPtr length); + + [DllImport("hidapi")] + public static extern int hid_set_nonblocking(IntPtr device, int nonblock); + + [DllImport("hidapi")] + public static extern int hid_write(IntPtr device, byte[] data, UIntPtr length); +} + +struct hid_device_info { + public string path; + public ushort vendor_id; + public ushort product_id; + public string serial_number; + public ushort release_number; + public string manufacturer_string; + public string product_string; + public ushort usage_page; + public ushort usage; + public int interface_number; + public IntPtr next; +} \ No newline at end of file diff --git a/Assets/Packages/JoyconLib_scripts/HIDapi.cs.meta b/Assets/Packages/JoyconLib_scripts/HIDapi.cs.meta new file mode 100644 index 0000000..8d59bc4 --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/HIDapi.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 36c08f14028ff5d499d8408db9ea95a6 +timeCreated: 1506536874 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_scripts/Joycon.cs b/Assets/Packages/JoyconLib_scripts/Joycon.cs new file mode 100644 index 0000000..b7de9e5 --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/Joycon.cs @@ -0,0 +1,659 @@ +#define DEBUG + +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System; + +using System.Threading; +using UnityEngine; + +public class Joycon +{ + public enum DebugType : int + { + NONE, + ALL, + COMMS, + THREADING, + IMU, + RUMBLE, + }; + public DebugType debug_type = DebugType.NONE; + public bool isLeft; + public enum state_ : uint + { + NOT_ATTACHED, + DROPPED, + NO_JOYCONS, + ATTACHED, + INPUT_MODE_0x30, + IMU_DATA_OK, + }; + public state_ state; + public enum Button : int + { + DPAD_DOWN = 0, + DPAD_RIGHT = 1, + DPAD_LEFT = 2, + DPAD_UP = 3, + SL = 4, + SR = 5, + MINUS = 6, + HOME = 7, + PLUS = 8, + CAPTURE = 9, + STICK = 10, + SHOULDER_1 = 11, + SHOULDER_2 = 12 + }; + private bool[] buttons_down = new bool[13]; + private bool[] buttons_up = new bool[13]; + private bool[] buttons = new bool[13]; + private bool[] down_ = new bool[13]; + + private float[] stick = { 0, 0 }; + + private + IntPtr handle; + + byte[] default_buf = { 0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40 }; + + private byte[] stick_raw = { 0, 0, 0 }; + private UInt16[] stick_cal = { 0, 0, 0, 0, 0, 0 }; + private UInt16 deadzone; + private UInt16[] stick_precal = { 0, 0 }; + + private bool stop_polling = false; + private int timestamp; + private bool first_imu_packet = true; + private bool imu_enabled = false; + private Int16[] acc_r = { 0, 0, 0 }; + private Vector3 acc_g; + + private Int16[] gyr_r = { 0, 0, 0 }; + private Int16[] gyr_neutral = { 0, 0, 0 }; + private Vector3 gyr_g; + private bool do_localize; + private float filterweight; + private const uint report_len = 49; + private struct Report + { + byte[] r; + System.DateTime t; + public Report(byte[] report, System.DateTime time) + { + r = report; + t = time; + } + public System.DateTime GetTime() + { + return t; + } + public void CopyBuffer(byte[] b) + { + for (int i = 0; i < report_len; ++i) + { + b[i] = r[i]; + } + } + }; + private struct Rumble + { + private float h_f, amp, l_f; + public float t; + public bool timed_rumble; + + public void set_vals(float low_freq, float high_freq, float amplitude, int time = 0) + { + h_f = high_freq; + amp = amplitude; + l_f = low_freq; + timed_rumble = false; + t = 0; + if (time != 0) + { + t = time / 1000f; + timed_rumble = true; + } + } + public Rumble(float low_freq, float high_freq, float amplitude, int time = 0) + { + h_f = high_freq; + amp = amplitude; + l_f = low_freq; + timed_rumble = false; + t = 0; + if (time != 0) + { + t = time / 1000f; + timed_rumble = true; + } + } + private float clamp(float x, float min, float max) + { + if (x < min) return min; + if (x > max) return max; + return x; + } + public byte[] GetData() + { + + byte[] rumble_data = new byte[8]; + l_f = clamp(l_f, 40.875885f, 626.286133f); + amp = clamp(amp, 0.0f, 1.0f); + h_f = clamp(h_f, 81.75177f, 1252.572266f); + UInt16 hf = (UInt16)((Mathf.Round(32f * Mathf.Log(h_f * 0.1f, 2)) - 0x60) * 4); + byte lf = (byte)(Mathf.Round(32f * Mathf.Log(l_f * 0.1f, 2)) - 0x40); + byte hf_amp; + if (amp == 0) hf_amp = 0; + else if (amp < 0.117) hf_amp = (byte)(((Mathf.Log(amp * 1000, 2) * 32) - 0x60) / (5 - Mathf.Pow(amp, 2)) - 1); + else if (amp < 0.23) hf_amp = (byte)(((Mathf.Log(amp * 1000, 2) * 32) - 0x60) - 0x5c); + else hf_amp = (byte)((((Mathf.Log(amp * 1000, 2) * 32) - 0x60) * 2) - 0xf6); + + UInt16 lf_amp = (UInt16)(Mathf.Round(hf_amp) * .5); + byte parity = (byte)(lf_amp % 2); + if (parity > 0) + { + --lf_amp; + } + + lf_amp = (UInt16)(lf_amp >> 1); + lf_amp += 0x40; + if (parity > 0) lf_amp |= 0x8000; + rumble_data = new byte[8]; + rumble_data[0] = (byte)(hf & 0xff); + rumble_data[1] = (byte)((hf >> 8) & 0xff); + rumble_data[2] = lf; + rumble_data[1] += hf_amp; + rumble_data[2] += (byte)((lf_amp >> 8) & 0xff); + rumble_data[3] += (byte)(lf_amp & 0xff); + for (int i = 0; i < 4; ++i) + { + rumble_data[4 + i] = rumble_data[i]; + } + //Debug.Log(string.Format("Encoded hex freq: {0:X2}", encoded_hex_freq)); + //Debug.Log(string.Format("lf_amp: {0:X4}", lf_amp)); + //Debug.Log(string.Format("hf_amp: {0:X2}", hf_amp)); + //Debug.Log(string.Format("l_f: {0:F}", l_f)); + //Debug.Log(string.Format("hf: {0:X4}", hf)); + //Debug.Log(string.Format("lf: {0:X2}", lf)); + return rumble_data; + } + } + private Queue reports = new Queue(); + private Rumble rumble_obj; + + private byte global_count = 0; + private string debug_str; + + public Joycon(IntPtr handle_, bool imu, bool localize, float alpha, bool left) + { + handle = handle_; + imu_enabled = imu; + do_localize = localize; + rumble_obj = new Rumble(160, 320, 0); + filterweight = alpha; + isLeft = left; + } + public void DebugPrint(String s, DebugType d) + { + if (debug_type == DebugType.NONE) return; + if (d == DebugType.ALL || d == debug_type || debug_type == DebugType.ALL) + { + Debug.Log(s); + } + } + public bool GetButtonDown(Button b) + { + bool value = buttons_down[(int) b]; + buttons_down[(int) b] = false; + return value; + } + public bool GetButton(Button b) + { + return buttons[(int)b]; + } + public bool GetButtonUp(Button b) + { + return buttons_up[(int)b]; + } + public float[] GetStick() + { + return stick; + } + public Vector3 GetGyro() + { + return gyr_g; + } + public Vector3 GetAccel() + { + return acc_g; + } + public Quaternion GetVector() + { + Vector3 v1 = new Vector3(j_b.x, -k_b.x, i_b.x); + Vector3 v2 = new Vector3(j_b.z, -k_b.z, i_b.z); + if (v2 != Vector3.zero){ + return Quaternion.LookRotation(v1, v2); + }else{ + return Quaternion.identity; + } + } + public int Attach(byte leds_ = 0x0) + { + state = state_.ATTACHED; + byte[] a = { 0x0 }; + // Input report mode + Subcommand(0x3, new byte[] { 0x3f }, 1, false); + a[0] = 0x1; + dump_calibration_data(); + // Connect + a[0] = 0x01; + Subcommand(0x1, a, 1); + a[0] = 0x02; + Subcommand(0x1, a, 1); + a[0] = 0x03; + Subcommand(0x1, a, 1); + a[0] = leds_; + Subcommand(0x30, a, 1); + Subcommand(0x40, new byte[] { (imu_enabled ? (byte)0x1 : (byte)0x0) }, 1, true); + Subcommand(0x3, new byte[] { 0x30 }, 1, true); + Subcommand(0x48, new byte[] { 0x1 }, 1, true); + DebugPrint("Done with init.", DebugType.COMMS); + return 0; + } + public void SetFilterCoeff(float a) + { + filterweight = a; + } + public void Detach() + { + stop_polling = true; + PrintArray(max, format: "Max {0:S}", d: DebugType.IMU); + PrintArray(sum, format: "Sum {0:S}", d: DebugType.IMU); + if (state > state_.NO_JOYCONS) + { + Subcommand(0x30, new byte[] { 0x0 }, 1); + Subcommand(0x40, new byte[] { 0x0 }, 1); + Subcommand(0x48, new byte[] { 0x0 }, 1); + Subcommand(0x3, new byte[] { 0x3f }, 1); + } + if (state > state_.DROPPED) + { + HIDapi.hid_close(handle); + } + state = state_.NOT_ATTACHED; + } + private byte ts_en; + private byte ts_de; + private System.DateTime ts_prev; + private int ReceiveRaw() + { + if (handle == IntPtr.Zero) return -2; + HIDapi.hid_set_nonblocking(handle, 0); + byte[] raw_buf = new byte[report_len]; + int ret = HIDapi.hid_read(handle, raw_buf, new UIntPtr(report_len)); + if (ret > 0) + { + lock (reports) + { + reports.Enqueue(new Report(raw_buf, System.DateTime.Now)); + } + if (ts_en == raw_buf[1]) + { + DebugPrint(string.Format("Duplicate timestamp enqueued. TS: {0:X2}", ts_en), DebugType.THREADING); + } + ts_en = raw_buf[1]; + DebugPrint(string.Format("Enqueue. Bytes read: {0:D}. Timestamp: {1:X2}", ret, raw_buf[1]), DebugType.THREADING); + } + return ret; + } + private Thread PollThreadObj; + private void Poll() + { + int attempts = 0; + while (!stop_polling & state > state_.NO_JOYCONS) + { + SendRumble(rumble_obj.GetData()); + int a = ReceiveRaw(); + a = ReceiveRaw(); + if (a > 0) + { + state = state_.IMU_DATA_OK; + attempts = 0; + } + else if (attempts > 1000) + { + state = state_.DROPPED; + DebugPrint("Connection lost. Is the Joy-Con connected?", DebugType.ALL); + break; + } + else + { + DebugPrint("Pause 5ms", DebugType.THREADING); + Thread.Sleep((Int32)5); + } + ++attempts; + } + DebugPrint("End poll loop.", DebugType.THREADING); + } + float[] max = { 0, 0, 0 }; + float[] sum = { 0, 0, 0 }; + public void Update() + { + if (state > state_.NO_JOYCONS) + { + byte[] report_buf = new byte[report_len]; + while (reports.Count > 0) + { + Report rep; + lock (reports) + { + rep = reports.Dequeue(); + rep.CopyBuffer(report_buf); + } + if (imu_enabled) + { + if (do_localize) + { + ProcessIMU(report_buf); + } + else + { + ExtractIMUValues(report_buf, 0); + } + } + if (ts_de == report_buf[1]) + { + DebugPrint(string.Format("Duplicate timestamp dequeued. TS: {0:X2}", ts_de), DebugType.THREADING); + } + ts_de = report_buf[1]; + //DebugPrint(string.Format("Dequeue. Queue length: {0:d}. Packet ID: {1:X2}. Timestamp: {2:X2}. Lag to dequeue: {3:s}. Lag between packets (expect 15ms): {4:s}", + // reports.Count, report_buf[0], report_buf[1], System.DateTime.Now.Subtract(rep.GetTime()), rep.GetTime().Subtract(ts_prev)), DebugType.THREADING); + ts_prev = rep.GetTime(); + } + ProcessButtonsAndStick(report_buf); + if (rumble_obj.timed_rumble) { + if (rumble_obj.t < 0) { + rumble_obj.set_vals (160, 320, 0, 0); + } else { + rumble_obj.t -= Time.deltaTime; + } + } + } + } + private int ProcessButtonsAndStick(byte[] report_buf) + { + if (report_buf[0] == 0x00) return -1; + + stick_raw[0] = report_buf[6 + (isLeft ? 0 : 3)]; + stick_raw[1] = report_buf[7 + (isLeft ? 0 : 3)]; + stick_raw[2] = report_buf[8 + (isLeft ? 0 : 3)]; + + stick_precal[0] = (UInt16)(stick_raw[0] | ((stick_raw[1] & 0xf) << 8)); + stick_precal[1] = (UInt16)((stick_raw[1] >> 4) | (stick_raw[2] << 4)); + stick = CenterSticks(stick_precal); + lock (buttons) + { + lock (down_) + { + for (int i = 0; i < buttons.Length; ++i) + { + down_[i] = buttons[i]; + } + } + buttons[(int)Button.DPAD_DOWN] = (report_buf[3 + (isLeft ? 2 : 0)] & (isLeft ? 0x01 : 0x04)) != 0; + buttons[(int)Button.DPAD_RIGHT] = (report_buf[3 + (isLeft ? 2 : 0)] & (isLeft ? 0x04 : 0x08)) != 0; + buttons[(int)Button.DPAD_UP] = (report_buf[3 + (isLeft ? 2 : 0)] & (isLeft ? 0x02 : 0x02)) != 0; + buttons[(int)Button.DPAD_LEFT] = (report_buf[3 + (isLeft ? 2 : 0)] & (isLeft ? 0x08 : 0x01)) != 0; + buttons[(int)Button.HOME] = ((report_buf[4] & 0x10) != 0); + buttons[(int)Button.MINUS] = ((report_buf[4] & 0x01) != 0); + buttons[(int)Button.PLUS] = ((report_buf[4] & 0x02) != 0); + buttons[(int)Button.STICK] = ((report_buf[4] & (isLeft ? 0x08 : 0x04)) != 0); + buttons[(int)Button.SHOULDER_1] = (report_buf[3 + (isLeft ? 2 : 0)] & 0x40) != 0; + buttons[(int)Button.SHOULDER_2] = (report_buf[3 + (isLeft ? 2 : 0)] & 0x80) != 0; + buttons[(int)Button.SR] = (report_buf[3 + (isLeft ? 2 : 0)] & 0x10) != 0; + buttons[(int)Button.SL] = (report_buf[3 + (isLeft ? 2 : 0)] & 0x20) != 0; + lock (buttons_up) + { + lock (buttons_down) + { + for (int i = 0; i < buttons.Length; ++i) + { + buttons_up[i] = (down_[i] & !buttons[i]); + buttons_down[i] = (!down_[i] & buttons[i]); + } + } + } + } + return 0; + } + private void ExtractIMUValues(byte[] report_buf, int n = 0) + { + gyr_r[0] = (Int16)(report_buf[19 + n * 12] | ((report_buf[20 + n * 12] << 8) & 0xff00)); + gyr_r[1] = (Int16)(report_buf[21 + n * 12] | ((report_buf[22 + n * 12] << 8) & 0xff00)); + gyr_r[2] = (Int16)(report_buf[23 + n * 12] | ((report_buf[24 + n * 12] << 8) & 0xff00)); + acc_r[0] = (Int16)(report_buf[13 + n * 12] | ((report_buf[14 + n * 12] << 8) & 0xff00)); + acc_r[1] = (Int16)(report_buf[15 + n * 12] | ((report_buf[16 + n * 12] << 8) & 0xff00)); + acc_r[2] = (Int16)(report_buf[17 + n * 12] | ((report_buf[18 + n * 12] << 8) & 0xff00)); + for (int i = 0; i < 3; ++i) + { + acc_g[i] = acc_r[i] * 0.00025f; + gyr_g[i] = (gyr_r[i] - gyr_neutral[i]) * 0.00122187695f; + if (Math.Abs(acc_g[i]) > Math.Abs(max[i])) + max[i] = acc_g[i]; + } + } + + private float err; + public Vector3 i_b, j_b, k_b, k_acc; + private Vector3 d_theta; + private Vector3 i_b_; + private Vector3 w_a, w_g; + private Quaternion vec; + + private int ProcessIMU(byte[] report_buf) + { + + // Direction Cosine Matrix method + // http://www.starlino.com/dcm_tutorial.html + + if (!imu_enabled | state < state_.IMU_DATA_OK) + return -1; + + if (report_buf[0] != 0x30) return -1; // no gyro data + + // read raw IMU values + int dt = (report_buf[1] - timestamp); + if (report_buf[1] < timestamp) dt += 0x100; + + for (int n = 0; n < 3; ++n) + { + ExtractIMUValues(report_buf, n); + + float dt_sec = 0.005f * dt; + sum[0] += gyr_g.x * dt_sec; + sum[1] += gyr_g.y * dt_sec; + sum[2] += gyr_g.z * dt_sec; + + if (isLeft) + { + gyr_g.y *= -1; + gyr_g.z *= -1; + acc_g.y *= -1; + acc_g.z *= -1; + } + + if (first_imu_packet) + { + i_b = new Vector3(1, 0, 0); + j_b = new Vector3(0, 1, 0); + k_b = new Vector3(0, 0, 1); + first_imu_packet = false; + } + else + { + k_acc = -Vector3.Normalize(acc_g); + w_a = Vector3.Cross(k_b, k_acc); + w_g = -gyr_g * dt_sec; + d_theta = (filterweight * w_a + w_g) / (1f + filterweight); + k_b += Vector3.Cross(d_theta, k_b); + i_b += Vector3.Cross(d_theta, i_b); + j_b += Vector3.Cross(d_theta, j_b); + //Correction, ensure new axes are orthogonal + err = Vector3.Dot(i_b, j_b) * 0.5f; + i_b_ = Vector3.Normalize(i_b - err * j_b); + j_b = Vector3.Normalize(j_b - err * i_b); + i_b = i_b_; + k_b = Vector3.Cross(i_b, j_b); + } + dt = 1; + } + timestamp = report_buf[1] + 2; + return 0; + } + public void Begin() + { + if (PollThreadObj == null) + { + PollThreadObj = new Thread(new ThreadStart(Poll)); + PollThreadObj.Start(); + } + } + public void Recenter() + { + first_imu_packet = true; + } + private float[] CenterSticks(UInt16[] vals) + { + + float[] s = { 0, 0 }; + for (uint i = 0; i < 2; ++i) + { + float diff = vals[i] - stick_cal[2 + i]; + if (Math.Abs(diff) < deadzone) vals[i] = 0; + else if (diff > 0) // if axis is above center + { + s[i] = diff / stick_cal[i]; + } + else + { + s[i] = diff / stick_cal[4 + i]; + } + } + return s; + } + public void SetRumble(float low_freq, float high_freq, float amp, int time = 0) + { + if (state <= Joycon.state_.ATTACHED) return; + if (rumble_obj.timed_rumble == false || rumble_obj.t < 0) + { + rumble_obj = new Rumble(low_freq, high_freq, amp, time); + } + } + private void SendRumble(byte[] buf) + { + byte[] buf_ = new byte[report_len]; + buf_[0] = 0x10; + buf_[1] = global_count; + if (global_count == 0xf) global_count = 0; + else ++global_count; + Array.Copy(buf, 0, buf_, 2, 8); + PrintArray(buf_, DebugType.RUMBLE, format: "Rumble data sent: {0:S}"); + HIDapi.hid_write(handle, buf_, new UIntPtr(report_len)); + } + private byte[] Subcommand(byte sc, byte[] buf, uint len, bool print = true) + { + byte[] buf_ = new byte[report_len]; + byte[] response = new byte[report_len]; + Array.Copy(default_buf, 0, buf_, 2, 8); + Array.Copy(buf, 0, buf_, 11, len); + buf_[10] = sc; + buf_[1] = global_count; + buf_[0] = 0x1; + if (global_count == 0xf) global_count = 0; + else ++global_count; + if (print) { PrintArray(buf_, DebugType.COMMS, len, 11, "Subcommand 0x" + string.Format("{0:X2}", sc) + " sent. Data: 0x{0:S}"); }; + HIDapi.hid_write(handle, buf_, new UIntPtr(len + 11)); + int res = HIDapi.hid_read_timeout(handle, response, new UIntPtr(report_len), 50); + if (res < 1) DebugPrint("No response.", DebugType.COMMS); + else if (print) { PrintArray(response, DebugType.COMMS, report_len - 1, 1, "Response ID 0x" + string.Format("{0:X2}", response[0]) + ". Data: 0x{0:S}"); } + return response; + } + private void dump_calibration_data() + { + byte[] buf_ = ReadSPI(0x80, (isLeft ? (byte)0x12 : (byte)0x1d), 9); // get user calibration data if possible + bool found = false; + for (int i = 0; i < 9; ++i) + { + if (buf_[i] != 0xff) + { + Debug.Log("Using user stick calibration data."); + found = true; + break; + } + } + if (!found) + { + Debug.Log("Using factory stick calibration data."); + buf_ = ReadSPI(0x60, (isLeft ? (byte)0x3d : (byte)0x46), 9); // get user calibration data if possible + } + stick_cal[isLeft ? 0 : 2] = (UInt16)((buf_[1] << 8) & 0xF00 | buf_[0]); // X Axis Max above center + stick_cal[isLeft ? 1 : 3] = (UInt16)((buf_[2] << 4) | (buf_[1] >> 4)); // Y Axis Max above center + stick_cal[isLeft ? 2 : 4] = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); // X Axis Center + stick_cal[isLeft ? 3 : 5] = (UInt16)((buf_[5] << 4) | (buf_[4] >> 4)); // Y Axis Center + stick_cal[isLeft ? 4 : 0] = (UInt16)((buf_[7] << 8) & 0xF00 | buf_[6]); // X Axis Min below center + stick_cal[isLeft ? 5 : 1] = (UInt16)((buf_[8] << 4) | (buf_[7] >> 4)); // Y Axis Min below center + + PrintArray(stick_cal, len: 6, start: 0, format: "Stick calibration data: {0:S}"); + + buf_ = ReadSPI(0x60, (isLeft ? (byte)0x86 : (byte)0x98), 16); + deadzone = (UInt16)((buf_[4] << 8) & 0xF00 | buf_[3]); + + buf_ = ReadSPI(0x80, 0x34, 10); + gyr_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00)); + gyr_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00)); + gyr_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00)); + PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "User gyro neutral position: {0:S}"); + + // This is an extremely messy way of checking to see whether there is user stick calibration data present, but I've seen conflicting user calibration data on blank Joy-Cons. Worth another look eventually. + if (gyr_neutral[0] + gyr_neutral[1] + gyr_neutral[2] == -3 || Math.Abs(gyr_neutral[0]) > 100 || Math.Abs(gyr_neutral[1]) > 100 || Math.Abs(gyr_neutral[2]) > 100) + { + buf_ = ReadSPI(0x60, 0x29, 10); + gyr_neutral[0] = (Int16)(buf_[3] | ((buf_[4] << 8) & 0xff00)); + gyr_neutral[1] = (Int16)(buf_[5] | ((buf_[6] << 8) & 0xff00)); + gyr_neutral[2] = (Int16)(buf_[7] | ((buf_[8] << 8) & 0xff00)); + PrintArray(gyr_neutral, len: 3, d: DebugType.IMU, format: "Factory gyro neutral position: {0:S}"); + } + } + private byte[] ReadSPI(byte addr1, byte addr2, uint len, bool print = false) + { + byte[] buf = { addr2, addr1, 0x00, 0x00, (byte)len }; + byte[] read_buf = new byte[len]; + byte[] buf_ = new byte[len + 20]; + + for (int i = 0; i < 100; ++i) + { + buf_ = Subcommand(0x10, buf, 5, false); + if (buf_[15] == addr2 && buf_[16] == addr1) + { + break; + } + } + Array.Copy(buf_, 20, read_buf, 0, len); + if (print) PrintArray(read_buf, DebugType.COMMS, len); + return read_buf; + } + private void PrintArray(T[] arr, DebugType d = DebugType.NONE, uint len = 0, uint start = 0, string format = "{0:S}") + { + if (d != debug_type && debug_type != DebugType.ALL) return; + if (len == 0) len = (uint)arr.Length; + string tostr = ""; + for (int i = 0; i < len; ++i) + { + tostr += string.Format((arr[0] is byte) ? "{0:X2} " : ((arr[0] is float) ? "{0:F} " : "{0:D} "), arr[i + start]); + } + DebugPrint(string.Format(format, tostr), d); + } +} diff --git a/Assets/Packages/JoyconLib_scripts/Joycon.cs.meta b/Assets/Packages/JoyconLib_scripts/Joycon.cs.meta new file mode 100644 index 0000000..ba50184 --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/Joycon.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7b0782e2174ef6146920e09c324cf2d1 +timeCreated: 1506536874 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Packages/JoyconLib_scripts/JoyconManager.cs b/Assets/Packages/JoyconLib_scripts/JoyconManager.cs new file mode 100644 index 0000000..b5bbefc --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/JoyconManager.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.Runtime.InteropServices; +using UnityEngine; +using System; + +public class JoyconManager: MonoBehaviour +{ + + // Settings accessible via Unity + public bool EnableIMU = true; + public bool EnableLocalize = true; + + // Different operating systems either do or don't like the trailing zero + // ReSharper disable InconsistentNaming + private const ushort vendor_id = 0x57e; + private const ushort vendor_id_ = 0x057e; + private const ushort product_l = 0x2006; + private const ushort product_r = 0x2007; + + public List ConnectedJoycons; // Array of all connected Joy-Cons + private static JoyconManager _instance; + + public static JoyconManager Instance => _instance; + + private void Awake() + { + if (_instance != null) Destroy(gameObject); + _instance = this; + + ConnectedJoycons = new List(); + bool isLeft = false; + HIDapi.hid_init(); + + IntPtr ptr = HIDapi.hid_enumerate(vendor_id, 0x0); + IntPtr topPtr = ptr; + + if (ptr == IntPtr.Zero) + { + ptr = HIDapi.hid_enumerate(vendor_id_, 0x0); + if (ptr == IntPtr.Zero) + { + HIDapi.hid_free_enumeration(ptr); + Debug.Log ("No Joy-Cons found!"); + } + } + hid_device_info enumerate; + while (ptr != IntPtr.Zero) { + enumerate = (hid_device_info)Marshal.PtrToStructure (ptr, typeof(hid_device_info)); + + Debug.Log (enumerate.product_id); + if (enumerate.product_id == product_l || enumerate.product_id == product_r) { + if (enumerate.product_id == product_l) { + isLeft = true; + Debug.Log ("Left Joy-Con connected."); + } else if (enumerate.product_id == product_r) { + isLeft = false; + Debug.Log ("Right Joy-Con connected."); + } else { + Debug.Log ("Non Joy-Con input device skipped."); + } + IntPtr handle = HIDapi.hid_open_path (enumerate.path); + HIDapi.hid_set_nonblocking (handle, 1); + ConnectedJoycons.Add (new Joycon (handle, EnableIMU, EnableLocalize & EnableIMU, 0.05f, isLeft)); + } + ptr = enumerate.next; + } + HIDapi.hid_free_enumeration (topPtr); + } + + private void Start() + { + for (int i = 0; i < ConnectedJoycons.Count; ++i) + { + Debug.Log (i); + Joycon jc = ConnectedJoycons [i]; + byte LEDs = 0x0; + LEDs |= (byte)(0x1 << i); + jc.Attach (LEDs); + jc.Begin (); + } + } + + private void Update() + { + foreach (Joycon joycon in ConnectedJoycons) + { + joycon.Update(); + } + } + + private void OnApplicationQuit() + { + foreach (Joycon joycon in ConnectedJoycons) + { + joycon.Detach (); + } + } + + public Joycon GetJoycon(int index) + { + return ConnectedJoycons[index]; + } +} diff --git a/Assets/Packages/JoyconLib_scripts/JoyconManager.cs.meta b/Assets/Packages/JoyconLib_scripts/JoyconManager.cs.meta new file mode 100644 index 0000000..1c3b23e --- /dev/null +++ b/Assets/Packages/JoyconLib_scripts/JoyconManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 405bad29b2b998c408059182c189d9e9 +timeCreated: 1506535713 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: