- Added Joycon Support

- Further gyro changes
- Updated hidapi
This commit is contained in:
David Khachaturov 2018-03-05 17:55:04 +00:00
parent 334b71d78e
commit e850051957
4 changed files with 136 additions and 61 deletions

View file

@ -10,10 +10,13 @@ using System.Threading.Tasks;
namespace BetterJoyForCemu { namespace BetterJoyForCemu {
public class Joycon { public class Joycon {
float timing = 60.0f; float timing = 120.0f;
bool isPro = false; public bool isPro = false;
bool isUSB = false; bool isUSB = false;
public Joycon other;
public bool send = true;
public enum DebugType : int { public enum DebugType : int {
NONE, NONE,
@ -210,7 +213,7 @@ namespace BetterJoyForCemu {
// For UdpServer // For UdpServer
public int PadId = 0; public int PadId = 0;
public int battery = 2; public int battery = 4;
public int model = 2; public int model = 2;
public int constate = 2; public int constate = 2;
public int connection = 3; public int connection = 3;
@ -261,9 +264,6 @@ namespace BetterJoyForCemu {
} }
public int Attach(byte leds_ = 0x0) { public int Attach(byte leds_ = 0x0) {
state = state_.ATTACHED; state = state_.ATTACHED;
// Make sure command is received
HIDapi.hid_set_nonblocking(handle, 0);
byte[] a = { 0x0 }; byte[] a = { 0x0 };
@ -315,8 +315,6 @@ namespace BetterJoyForCemu {
DebugPrint("Done with init.", DebugType.COMMS); DebugPrint("Done with init.", DebugType.COMMS);
HIDapi.hid_set_nonblocking(handle, 1);
return 0; return 0;
} }
@ -349,10 +347,10 @@ namespace BetterJoyForCemu {
private byte ts_en; private byte ts_en;
private int ReceiveRaw() { private int ReceiveRaw() {
if (handle == IntPtr.Zero) return -2; if (handle == IntPtr.Zero) return -2;
HIDapi.hid_set_nonblocking(handle, 0); HIDapi.hid_set_nonblocking(handle, 1);
byte[] raw_buf = new byte[report_len]; byte[] raw_buf = new byte[report_len];
int ret = HIDapi.hid_read(handle, raw_buf, new UIntPtr(report_len)); int ret = HIDapi.hid_read(handle, raw_buf, new UIntPtr(report_len));
if (ret > 0) { if (ret > 0 && Program.server != null) {
// Process packets as soon as they come // Process packets as soon as they come
for (int n = 0; n < 3; n++) { for (int n = 0; n < 3; n++) {
ExtractIMUValues(raw_buf, n); ExtractIMUValues(raw_buf, n);
@ -362,7 +360,7 @@ namespace BetterJoyForCemu {
Timestamp += (ulong)lag * 5000; // add lag once Timestamp += (ulong)lag * 5000; // add lag once
ProcessButtonsAndStick(raw_buf); ProcessButtonsAndStick(raw_buf);
} }
Timestamp += (ulong)(2 - n) * 5000; // 5ms difference Timestamp += 5000; // 5ms difference
packetCounter++; packetCounter++;
Program.server.NewReportIncoming(this); Program.server.NewReportIncoming(this);
@ -382,19 +380,19 @@ namespace BetterJoyForCemu {
private void Poll() { private void Poll() {
int attempts = 0; int attempts = 0;
while (!stop_polling & state > state_.NO_JOYCONS) { while (!stop_polling & state > state_.NO_JOYCONS) {
SendRumble(rumble_obj.GetData()); //SendRumble(rumble_obj.GetData());
int a = ReceiveRaw(); int a = ReceiveRaw();
//a = ReceiveRaw(); //a = ReceiveRaw();
if (a > 0) { if (a > 0) {
state = state_.IMU_DATA_OK; state = state_.IMU_DATA_OK;
attempts = 0; attempts = 0;
} else if (attempts > 1000) { } else if (attempts > 10000) {
state = state_.DROPPED; state = state_.DROPPED;
DebugPrint("Connection lost. Is the Joy-Con connected?", DebugType.ALL); DebugPrint("Connection lost. Is the Joy-Con connected?", DebugType.ALL);
break; break;
} else { } else {
DebugPrint("Pause 5ms", DebugType.THREADING); DebugPrint("Pause 5ms", DebugType.THREADING);
Thread.Sleep((Int32)5); Thread.Sleep((Int32)(!isPro ? 1 : 5));
} }
++attempts; ++attempts;
} }
@ -412,6 +410,9 @@ namespace BetterJoyForCemu {
} }
} }
} }
public float[] otherStick = { 0, 0 };
private int ProcessButtonsAndStick(byte[] report_buf) { private int ProcessButtonsAndStick(byte[] report_buf) {
if (report_buf[0] == 0x00) return -1; if (report_buf[0] == 0x00) return -1;
@ -427,14 +428,27 @@ namespace BetterJoyForCemu {
stick_precal[0] = (UInt16)(stick_raw[0] | ((stick_raw[1] & 0xf) << 8)); 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_precal[1] = (UInt16)((stick_raw[1] >> 4) | (stick_raw[2] << 4));
stick = CenterSticks(stick_precal); stick = CenterSticks(stick_precal, stick_cal, deadzone);
if (isPro) { if (isPro) {
stick2_precal[0] = (UInt16)(stick2_raw[0] | ((stick2_raw[1] & 0xf) << 8)); stick2_precal[0] = (UInt16)(stick2_raw[0] | ((stick2_raw[1] & 0xf) << 8));
stick2_precal[1] = (UInt16)((stick2_raw[1] >> 4) | (stick2_raw[2] << 4)); stick2_precal[1] = (UInt16)((stick2_raw[1] >> 4) | (stick2_raw[2] << 4));
stick2 = CenterSticks(stick2_precal, true); stick2 = CenterSticks(stick2_precal, stick2_cal, deadzone2);
} }
// Read other Joycon's sticks
if (isLeft && other != null) {
stick2 = otherStick;
other.otherStick = stick;
}
if (!isLeft && other != null) {
Array.Copy(stick, stick2, 2);
stick = otherStick;
other.otherStick = stick2;
}
//
lock (buttons) { lock (buttons) {
lock (down_) { lock (down_) {
for (int i = 0; i < buttons.Length; ++i) { for (int i = 0; i < buttons.Length; ++i) {
@ -465,6 +479,33 @@ namespace BetterJoyForCemu {
buttons[(int)Button.SHOULDER2_2] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x80) != 0; buttons[(int)Button.SHOULDER2_2] = (report_buf[3 + (!isLeft ? 2 : 0)] & 0x80) != 0;
} }
if (isLeft && other != null) {
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
buttons[(int)Button.Y] = other.buttons[(int)Button.DPAD_LEFT];
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
buttons[(int)Button.HOME] = other.buttons[(int)Button.HOME];
buttons[(int)Button.PLUS] = other.buttons[(int)Button.PLUS];
}
if (!isLeft && other != null) {
buttons[(int)Button.B] = other.buttons[(int)Button.DPAD_DOWN];
buttons[(int)Button.A] = other.buttons[(int)Button.DPAD_RIGHT];
buttons[(int)Button.X] = other.buttons[(int)Button.DPAD_UP];
buttons[(int)Button.Y] = other.buttons[(int)Button.DPAD_LEFT];
buttons[(int)Button.STICK2] = other.buttons[(int)Button.STICK];
buttons[(int)Button.SHOULDER2_1] = other.buttons[(int)Button.SHOULDER_1];
buttons[(int)Button.SHOULDER2_2] = other.buttons[(int)Button.SHOULDER_2];
buttons[(int)Button.MINUS] = other.buttons[(int)Button.MINUS];
}
lock (buttons_up) { lock (buttons_up) {
lock (buttons_down) { lock (buttons_down) {
for (int i = 0; i < buttons.Length; ++i) { for (int i = 0; i < buttons.Length; ++i) {
@ -500,13 +541,13 @@ namespace BetterJoyForCemu {
break; break;
case 1: case 1:
acc_g.Y = (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f; acc_g.Y = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Y = -(gyr_r[i] - gyr_neutral[i] * 0.5f) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i])); gyr_g.Y = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i] * 0.5f) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break; break;
case 2: case 2:
acc_g.Z = (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f; acc_g.Z = (!isLeft ? -1 : 1) * (acc_r[i] - offset[i]) * (1.0f / (acc_sensiti[i] - acc_neutral[i])) * 4.0f;
gyr_g.Z = -(gyr_r[i] - gyr_neutral[i]) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i])); gyr_g.Z = -(!isLeft ? -1 : 1) * (gyr_r[i] - gyr_neutral[i] * 0.5f) * (816.0f / (gyr_sensiti[i] - gyr_neutral[i]));
break; break;
} }
@ -526,16 +567,13 @@ namespace BetterJoyForCemu {
first_imu_packet = true; first_imu_packet = true;
} }
private float[] CenterSticks(UInt16[] vals, bool special = false) { private float[] CenterSticks(UInt16[] vals, ushort[] cal, ushort dz) {
ushort[] t = stick_cal; ushort[] t = cal;
if (special)
t = stick2_cal;
float[] s = { 0, 0 }; float[] s = { 0, 0 };
for (uint i = 0; i < 2; ++i) { for (uint i = 0; i < 2; ++i) {
float diff = vals[i] - t[2 + i]; float diff = vals[i] - t[2 + i];
if (Math.Abs(diff) < deadzone) vals[i] = 0; if (Math.Abs(diff) < dz) vals[i] = 0;
else if (diff > 0) // if axis is above center else if (diff > 0) // if axis is above center
{ {
s[i] = diff / t[i]; s[i] = diff / t[i];

View file

@ -18,9 +18,6 @@ namespace BetterJoyForCemu {
public bool EnableIMU = true; public bool EnableIMU = true;
public bool EnableLocalize = false; public bool EnableLocalize = false;
// Different operating systems either do or don't like the trailing zero
private const ushort vendor_id = 0x57e; private const ushort vendor_id = 0x57e;
private const ushort vendor_id_ = 0x057e; private const ushort vendor_id_ = 0x057e;
private const ushort product_l = 0x2006; private const ushort product_l = 0x2006;
@ -53,7 +50,6 @@ namespace BetterJoyForCemu {
} }
} }
string path = "";
hid_device_info enumerate; hid_device_info enumerate;
while (ptr != IntPtr.Zero) { while (ptr != IntPtr.Zero) {
enumerate = (hid_device_info)Marshal.PtrToStructure(ptr, typeof(hid_device_info)); enumerate = (hid_device_info)Marshal.PtrToStructure(ptr, typeof(hid_device_info));
@ -73,7 +69,12 @@ namespace BetterJoyForCemu {
} }
IntPtr handle = HIDapi.hid_open_path(enumerate.path); IntPtr handle = HIDapi.hid_open_path(enumerate.path);
HIDapi.hid_set_nonblocking(handle, 1); try {
HIDapi.hid_set_nonblocking(handle, 1);
} catch (Exception e) {
Console.WriteLine("Unable to open path to device - are you using the correct (64 vs 32-bit) version for your PC?");
break;
}
j.Add(new Joycon(handle, EnableIMU, EnableLocalize & EnableIMU, 0.05f, isLeft, j.Count, enumerate.product_id == product_pro, enumerate.serial_number == "000000000001")); j.Add(new Joycon(handle, EnableIMU, EnableLocalize & EnableIMU, 0.05f, isLeft, j.Count, enumerate.product_id == product_pro, enumerate.serial_number == "000000000001"));
@ -87,6 +88,39 @@ namespace BetterJoyForCemu {
ptr = enumerate.next; ptr = enumerate.next;
} }
int found = 0;
foreach (Joycon v in j) {
if (v.isLeft && !v.isPro)
found++;
if (!v.isLeft && !v.isPro)
found++;
}
if (found == 2) {
Console.WriteLine("Both joycons successfully found.");
Joycon temp = null;
foreach (Joycon v in j) {
if (v.isLeft && !v.isPro) {
if (temp == null)
temp = v;
else {
temp.other = v;
v.other = temp;
}
}
if (!v.isLeft && !v.isPro) {
if (temp == null)
temp = v;
else {
temp.other = v;
v.other = temp;
}
}
} // Join up the two joycons
} else if (found != 0)
Console.WriteLine("Only one joycon found. Please connect both and then restart the program.");
HIDapi.hid_free_enumeration(top_ptr); HIDapi.hid_free_enumeration(top_ptr);
} }

View file

@ -292,7 +292,8 @@ namespace BetterJoyForCemu {
udpSock.Close(); udpSock.Close();
udpSock = null; udpSock = null;
throw ex; Console.WriteLine("Could not start server. Make sure that only one instance of the program is running at a time and no other CemuHook applications are running.");
return;
} }
byte[] randomBuf = new byte[4]; byte[] randomBuf = new byte[4];
@ -315,27 +316,29 @@ namespace BetterJoyForCemu {
private bool ReportToBuffer(Joycon hidReport, byte[] outputData, ref int outIdx) { private bool ReportToBuffer(Joycon hidReport, byte[] outputData, ref int outIdx) {
outputData[outIdx] = 0; outputData[outIdx] = 0;
if (hidReport.GetButton(Joycon.Button.DPAD_LEFT)) outputData[outIdx] |= 0x80; bool isLeft = hidReport.isLeft;
if (hidReport.GetButton(Joycon.Button.DPAD_DOWN)) outputData[outIdx] |= 0x40;
if (hidReport.GetButton(Joycon.Button.DPAD_RIGHT)) outputData[outIdx] |= 0x20;
if (hidReport.GetButton(Joycon.Button.DPAD_UP)) outputData[outIdx] |= 0x10;
if (hidReport.GetButton(Joycon.Button.PLUS)) outputData[outIdx] |= 0x08; if (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_LEFT : Joycon.Button.A)) outputData[outIdx] |= 0x80;
if (hidReport.GetButton(Joycon.Button.STICK2)) outputData[outIdx] |= 0x04; if (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_DOWN : Joycon.Button.B)) outputData[outIdx] |= 0x40;
if (hidReport.GetButton(Joycon.Button.STICK)) outputData[outIdx] |= 0x02; if (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_RIGHT : Joycon.Button.Y)) outputData[outIdx] |= 0x20;
if (hidReport.GetButton(Joycon.Button.MINUS)) outputData[outIdx] |= 0x01; if (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_UP : Joycon.Button.X)) outputData[outIdx] |= 0x10;
if (hidReport.GetButton(Joycon.Button.PLUS)) outputData[outIdx] |= 0x08;
if (hidReport.GetButton(isLeft ? Joycon.Button.STICK2 : Joycon.Button.STICK)) outputData[outIdx] |= 0x04;
if (hidReport.GetButton(isLeft ? Joycon.Button.STICK : Joycon.Button.STICK2)) outputData[outIdx] |= 0x02;
if (hidReport.GetButton(Joycon.Button.MINUS)) outputData[outIdx] |= 0x01;
outputData[++outIdx] = 0; outputData[++outIdx] = 0;
if (hidReport.GetButton(Joycon.Button.Y)) outputData[outIdx] |= 0x80; if (hidReport.GetButton(isLeft ? Joycon.Button.Y : Joycon.Button.DPAD_LEFT)) outputData[outIdx] |= 0x80;
if (hidReport.GetButton(Joycon.Button.B)) outputData[outIdx] |= 0x40; if (hidReport.GetButton(isLeft ? Joycon.Button.B : Joycon.Button.DPAD_DOWN)) outputData[outIdx] |= 0x40;
if (hidReport.GetButton(Joycon.Button.A)) outputData[outIdx] |= 0x20; if (hidReport.GetButton(isLeft ? Joycon.Button.A : Joycon.Button.DPAD_RIGHT)) outputData[outIdx] |= 0x20;
if (hidReport.GetButton(Joycon.Button.X)) outputData[outIdx] |= 0x10; if (hidReport.GetButton(isLeft ? Joycon.Button.X : Joycon.Button.DPAD_UP)) outputData[outIdx] |= 0x10;
if (hidReport.GetButton(Joycon.Button.SHOULDER2_1)) outputData[outIdx] |= 0x08; if (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER2_1 : Joycon.Button.SHOULDER_1)) outputData[outIdx] |= 0x08;
if (hidReport.GetButton(Joycon.Button.SHOULDER_1)) outputData[outIdx] |= 0x04; if (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER_1 : Joycon.Button.SHOULDER2_1)) outputData[outIdx] |= 0x04;
if (hidReport.GetButton(Joycon.Button.SHOULDER2_2)) outputData[outIdx] |= 0x02; if (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER2_2 : Joycon.Button.SHOULDER_2)) outputData[outIdx] |= 0x02;
if (hidReport.GetButton(Joycon.Button.SHOULDER_2)) outputData[outIdx] |= 0x01; if (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER_2 : Joycon.Button.SHOULDER2_2)) outputData[outIdx] |= 0x01;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.HOME)) ? (byte)1 : (byte)0; outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.HOME)) ? (byte)1 : (byte)0;
outputData[++outIdx] = 0; // no touch pad outputData[++outIdx] = 0; // no touch pad
@ -349,21 +352,21 @@ namespace BetterJoyForCemu {
outputData[++outIdx] = (byte)(Math.Max(0, Math.Min(255, 127 + rightStick[1] * 127))); outputData[++outIdx] = (byte)(Math.Max(0, Math.Min(255, 127 + rightStick[1] * 127)));
//we don't have analog buttons so just use the Button enums (which give either 0 or 0xFF) //we don't have analog buttons so just use the Button enums (which give either 0 or 0xFF)
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.DPAD_LEFT)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_LEFT : Joycon.Button.A)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.DPAD_DOWN)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_DOWN : Joycon.Button.B)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.DPAD_RIGHT)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_RIGHT : Joycon.Button.Y)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.DPAD_UP)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.DPAD_UP : Joycon.Button.X)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.Y)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.Y : Joycon.Button.DPAD_LEFT)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.B)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.B : Joycon.Button.DPAD_DOWN)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.A)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.A : Joycon.Button.DPAD_RIGHT)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.X)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.X : Joycon.Button.DPAD_UP)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.SHOULDER2_1)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER2_1 : Joycon.Button.SHOULDER_1)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.SHOULDER_1)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER_1 : Joycon.Button.SHOULDER2_1)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.SHOULDER2_2)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER2_2 : Joycon.Button.SHOULDER_2)) ? (byte)0xFF : (byte)0;
outputData[++outIdx] = (hidReport.GetButton(Joycon.Button.SHOULDER_2)) ? (byte)0xFF : (byte)0; outputData[++outIdx] = (hidReport.GetButton(isLeft ? Joycon.Button.SHOULDER_2 : Joycon.Button.SHOULDER2_2)) ? (byte)0xFF : (byte)0;
outIdx++; outIdx++;
@ -384,7 +387,7 @@ namespace BetterJoyForCemu {
//Array.Copy(BitConverter.GetBytes((float)0), 0, outputData, outIdx, 4); //Array.Copy(BitConverter.GetBytes((float)0), 0, outputData, outIdx, 4);
outIdx += 4; outIdx += 4;
Array.Copy(BitConverter.GetBytes(-accel.Z), 0, outputData, outIdx, 4); Array.Copy(BitConverter.GetBytes(-accel.Z), 0, outputData, outIdx, 4);
//Array.Copy(BitConverter.GetBytes((float)-1), 0, outputData, outIdx, 4); //Array.Copy(BitConverter.GetBytes((float)0), 0, outputData, outIdx, 4);
outIdx += 4; outIdx += 4;
Array.Copy(BitConverter.GetBytes(accel.X), 0, outputData, outIdx, 4); Array.Copy(BitConverter.GetBytes(accel.X), 0, outputData, outIdx, 4);
//Array.Copy(BitConverter.GetBytes((float)0), 0, outputData, outIdx, 4); //Array.Copy(BitConverter.GetBytes((float)0), 0, outputData, outIdx, 4);

Binary file not shown.