public Device(HidDevice hidDevice) { HidDevice = hidDevice; ConnectionType = HidConnectionType(HidDevice); MacAddress = HidDevice.readSerial(); if (ConnectionType == ConnectionType.USB) { _inputReport = new byte[64]; _outputReport = new byte[HidDevice.Capabilities.OutputReportByteLength]; _outputReportBuffer = new byte[HidDevice.Capabilities.OutputReportByteLength]; } else { _btInputReport = new byte[BtInputReportLength]; _inputReport = new byte[_btInputReport.Length - 2]; _outputReport = new byte[BtOutputReportLength]; _outputReportBuffer = new byte[BtOutputReportLength]; } Touchpad = new Touchpad(); SixAxis = new SixAxis(); }
private void PerformDs4Input() { FirstActive = DateTime.UtcNow; var readTimeout = new System.Timers.Timer(); // Await 30 seconds for the initial packet, then 3 seconds thereafter. readTimeout.Elapsed += (sender, args) => HidDevice.CancelIO(); var latencies = new List <long>(); long oldtime = 0; var sw = Stopwatch.StartNew(); while (true) { var currentError = string.Empty; latencies.Add(sw.ElapsedMilliseconds - oldtime); oldtime = sw.ElapsedMilliseconds; if (latencies.Count > 100) { latencies.RemoveAt(0); } Latency = latencies.Average(); if (Latency > 10 && !warn && sw.ElapsedMilliseconds > 4000) { warn = true; } else if (Latency <= 10 && warn) { warn = false; } if (readTimeout.Interval != 3000.0) { if (readTimeout.Interval != 30000.0) { readTimeout.Interval = 30000.0; } else { readTimeout.Interval = 3000.0; } } readTimeout.Enabled = true; if (ConnectionType != ConnectionType.USB) { var res = HidDevice.ReadFile(_btInputReport); readTimeout.Enabled = false; if (res == HidDevice.ReadStatus.Success) { Array.Copy(_btInputReport, 2, _inputReport, 0, _inputReport.Length); } else { Console.WriteLine(MacAddress + " " + DateTime.UtcNow.ToString("o") + "> disconnect due to read failure: " + Marshal.GetLastWin32Error()); SendOutputReport(true); // Kick Windows into noticing the disconnection. StopOutputUpdate(); IsDisconnecting = true; Removal?.Invoke(this, EventArgs.Empty); return; } } else { var res = HidDevice.ReadFile(_inputReport); readTimeout.Enabled = false; if (res != HidDevice.ReadStatus.Success) { Console.WriteLine(MacAddress + " " + DateTime.UtcNow.ToString("o") + "> disconnect due to read failure: " + Marshal.GetLastWin32Error()); StopOutputUpdate(); IsDisconnecting = true; Removal?.Invoke(this, EventArgs.Empty); return; } } if (ConnectionType == ConnectionType.BT && _btInputReport[0] != 0x11) { //Received incorrect report, skip it continue; } var utcNow = DateTime.UtcNow; // timestamp with UTC in case system time zone changes resetHapticState(); _cState.ReportTimeStamp = utcNow; _cState.LX = _inputReport[1]; _cState.LY = _inputReport[2]; _cState.RX = _inputReport[3]; _cState.RY = _inputReport[4]; _cState.L2 = _inputReport[8]; _cState.R2 = _inputReport[9]; _cState.Triangle = (_inputReport[5] & (1 << 7)) != 0; _cState.Circle = (_inputReport[5] & (1 << 6)) != 0; _cState.Cross = (_inputReport[5] & (1 << 5)) != 0; _cState.Square = (_inputReport[5] & (1 << 4)) != 0; _cState.DpadUp = (_inputReport[5] & (1 << 3)) != 0; _cState.DpadDown = (_inputReport[5] & (1 << 2)) != 0; _cState.DpadLeft = (_inputReport[5] & (1 << 1)) != 0; _cState.DpadRight = (_inputReport[5] & (1 << 0)) != 0; //Convert dpad into individual On/Off bits instead of a clock representation var dpadState = (byte)( ((_cState.DpadRight ? 1 : 0) << 0) | ((_cState.DpadLeft ? 1 : 0) << 1) | ((_cState.DpadDown ? 1 : 0) << 2) | ((_cState.DpadUp ? 1 : 0) << 3)); switch (dpadState) { case 0: _cState.DpadUp = true; _cState.DpadDown = false; _cState.DpadLeft = false; _cState.DpadRight = false; break; case 1: _cState.DpadUp = true; _cState.DpadDown = false; _cState.DpadLeft = false; _cState.DpadRight = true; break; case 2: _cState.DpadUp = false; _cState.DpadDown = false; _cState.DpadLeft = false; _cState.DpadRight = true; break; case 3: _cState.DpadUp = false; _cState.DpadDown = true; _cState.DpadLeft = false; _cState.DpadRight = true; break; case 4: _cState.DpadUp = false; _cState.DpadDown = true; _cState.DpadLeft = false; _cState.DpadRight = false; break; case 5: _cState.DpadUp = false; _cState.DpadDown = true; _cState.DpadLeft = true; _cState.DpadRight = false; break; case 6: _cState.DpadUp = false; _cState.DpadDown = false; _cState.DpadLeft = true; _cState.DpadRight = false; break; case 7: _cState.DpadUp = true; _cState.DpadDown = false; _cState.DpadLeft = true; _cState.DpadRight = false; break; case 8: _cState.DpadUp = false; _cState.DpadDown = false; _cState.DpadLeft = false; _cState.DpadRight = false; break; } _cState.R3 = (_inputReport[6] & (1 << 7)) != 0; _cState.L3 = (_inputReport[6] & (1 << 6)) != 0; _cState.Options = (_inputReport[6] & (1 << 5)) != 0; _cState.Share = (_inputReport[6] & (1 << 4)) != 0; _cState.R1 = (_inputReport[6] & (1 << 1)) != 0; _cState.L1 = (_inputReport[6] & (1 << 0)) != 0; _cState.PS = (_inputReport[7] & (1 << 0)) != 0; _cState.TouchButton = (_inputReport[7] & (1 << 2 - 1)) != 0; _cState.FrameCounter = (byte)(_inputReport[7] >> 2); // Store Gyro and Accel values Array.Copy(_inputReport, 14, _accel, 0, 6); Array.Copy(_inputReport, 20, _gyro, 0, 6); SixAxis.handleSixaxis(_gyro, _accel, _cState); try { Charging = (_inputReport[30] & 0x10) != 0; Battery = (_inputReport[30] & 0x0f) * 10; _cState.Battery = (byte)Battery; if (_inputReport[30] != priorInputReport30) { priorInputReport30 = _inputReport[30]; Console.WriteLine(MacAddress + " " + DateTime.UtcNow.ToString("o") + "> power subsystem octet: 0x" + _inputReport[30].ToString("x02")); } } catch { currentError = "Index out of bounds: battery"; } // XXX DS4State mapping needs fixup, turn touches into an array[4] of structs. And include the touchpad details there instead. try { for (int touches = _inputReport[-1 + Touchpad.TOUCHPAD_DATA_OFFSET - 1], touchOffset = 0; touches > 0; touches--, touchOffset += 9) { _cState.TouchPacketCounter = _inputReport[-1 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset]; _cState.Touch1 = _inputReport[0 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] >> 7 == 0; // >= 1 touch detected _cState.Touch1Identifier = (byte)(_inputReport[0 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7f); _cState.Touch2 = _inputReport[4 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] >> 7 == 0; // 2 touches detected _cState.Touch2Identifier = (byte)(_inputReport[4 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0x7f); _cState.TouchLeft = _inputReport[1 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] + (_inputReport[2 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF) * 255 < 1920 * 2 / 5; _cState.TouchRight = _inputReport[1 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] + (_inputReport[2 + Touchpad.TOUCHPAD_DATA_OFFSET + touchOffset] & 0xF) * 255 >= 1920 * 2 / 5; // Even when idling there is still a touch packet indicating no touch 1 or 2 Touchpad.handleTouchpad(_inputReport, _cState, touchOffset); } } catch { currentError = "Index out of bounds: touchpad"; } /* Debug output of incoming HID data: * if (cState.L2 == 0xff && cState.R2 == 0xff) * { * Console.Write(MacAddress.ToString() + " " + System.DateTime.UtcNow.ToString("o") + ">"); * for (int i = 0; i < inputReport.Length; i++) * Console.Write(" " + inputReport[i].ToString("x2")); * Console.WriteLine(); * } */ if (!IsIdle()) { LastActive = utcNow; } if (ConnectionType == ConnectionType.BT) { var shouldDisconnect = false; if (IdleTimeout > 0) { if (IsIdle()) { var timeout = LastActive + TimeSpan.FromSeconds(IdleTimeout); if (!Charging) { shouldDisconnect = utcNow >= timeout; } } } if (shouldDisconnect && DisconnectBT()) { return; // all done } } // XXX fix initialization ordering so the null checks all go away Report?.Invoke(this, EventArgs.Empty); SendOutputReport(false); if (!string.IsNullOrEmpty(error)) { error = string.Empty; } if (!string.IsNullOrEmpty(currentError)) { error = currentError; } _cState.CopyTo(_pState); } }