ScpControl.Shared/Core/ScpHidReport.cs (275 lines of code) (raw):

using System; using System.Linq; using System.Net.NetworkInformation; using System.Reflection; namespace ScpControl.Shared.Core { /// <summary> /// Represents an extended HID Input Report ready to be sent to the virtual bus device. /// </summary> public class ScpHidReport : EventArgs { /// <summary> /// Report bytes count. /// </summary> public static int Length { get { return 96; } } #region Private fields private static readonly PropertyInfo[] Ds3Buttons = typeof (Ds3Button).GetProperties(BindingFlags.Public | BindingFlags.Static); private static readonly PropertyInfo[] Ds3Axes = typeof (Ds3Axis).GetProperties(BindingFlags.Public | BindingFlags.Static); private static readonly PropertyInfo[] Ds4Buttons = typeof (Ds4Button).GetProperties(BindingFlags.Public | BindingFlags.Static); private static readonly PropertyInfo[] Ds4Axes = typeof (Ds4Axis).GetProperties(BindingFlags.Public | BindingFlags.Static); #endregion #region Public methods /// <summary> /// Sets the status of a digital (two-state) button. /// </summary> /// <param name="button">The button of the current report to manipulate.</param> /// <param name="value">True to set the button as pressed, false to set the button as released.</param> public void Set(IDsButton button, bool value = true) { button.ToggleBit(ref RawBytes[button.ArrayIndex], value); } /// <summary> /// Sets the status of a digital (two-state) button to 'released'. /// </summary> /// <param name="button">The button of the current report to unset.</param> public void Unset(IDsButton button) { Set(button, false); } /// <summary> /// Sets the value of an analog axis. /// </summary> /// <param name="axis">The axis of the current report to manipulate.</param> /// <param name="value">The value to set the axis to.</param> public void Set(IDsAxis axis, byte value) { RawBytes[axis.Offset] = value; } /// <summary> /// Sets the value of an analog axis to it's default value. /// </summary> /// <param name="axis">The axis of the current report to manipulate.</param> public void Unset(IDsAxis axis) { RawBytes[axis.Offset] = axis.DefaultValue; } public byte SetBatteryStatus(DsBattery battery) { return RawBytes[(int) DsOffset.Battery] = (byte) battery; } public void ZeroShoulderButtonsState() { RawBytes[11] = 0x00; } public void ZeroSelectStartButtonsState() { RawBytes[10] = 0x00; } public void ZeroPsButtonState() { RawBytes[12] = 0x00; } #endregion #region Ctors public ScpHidReport() { RawBytes = new byte[Length]; ReportId = 0x01; } public ScpHidReport(byte[] report) { RawBytes = report; } #endregion #region Public properties public byte[] RawBytes { get; private set; } public PhysicalAddress PadMacAddress { get { // last 6 bytes contain the PADs MAC address return new PhysicalAddress(RawBytes.Skip(Math.Max(0, RawBytes.Length - 6)).ToArray()); } set { if (value != null) Buffer.BlockCopy(value.GetAddressBytes(), 0, RawBytes, 90, 6); } } public uint PacketCounter { get { return (uint) ((RawBytes[7] << 24) | (RawBytes[6] << 16) | (RawBytes[5] << 8) | (RawBytes[4] << 0)); } set { RawBytes[4] = (byte) (value >> 0 & 0xFF); RawBytes[5] = (byte) (value >> 8 & 0xFF); RawBytes[6] = (byte) (value >> 16 & 0xFF); RawBytes[7] = (byte) (value >> 24 & 0xFF); } } public DsModel Model { get { return (DsModel) RawBytes[(int) DsOffset.Model]; } set { RawBytes[(int) DsOffset.Model] = (byte) value; } } public DsPadId PadId { get { return (DsPadId) RawBytes[(int) DsOffset.Pad]; } set { RawBytes[(int) DsOffset.Pad] = (byte) value; } } public DsState PadState { get { return (DsState) RawBytes[(int) DsOffset.State]; } set { RawBytes[(int)DsOffset.State] = (byte)value; } } public byte ReportId { get { return RawBytes[0]; } set { RawBytes[0] = value; } } public DsConnection ConnectionType { get { return (DsConnection) RawBytes[(int) DsOffset.Connection]; } set { RawBytes[(int) DsOffset.Connection] = (byte) value; } } public byte BatteryStatus { get { return RawBytes[(int)DsOffset.Battery]; } set { RawBytes[(int)DsOffset.Battery] = value; } } public bool IsPadActive { get { switch (Model) { case DsModel.DS3: if ( Ds3Buttons.Any( button => this[button.GetValue(typeof (Ds3Button), null) as IDsButton].IsPressed) || Ds3Axes.Any(axis => this[axis.GetValue(typeof (Ds3Axis), null) as IDsAxis].IsEngaged)) { return true; } break; case DsModel.DS4: if ( Ds4Buttons.Any( button => this[button.GetValue(typeof (Ds3Button), null) as IDsButton].IsPressed) || Ds4Axes.Any(axis => this[axis.GetValue(typeof (Ds4Axis), null) as IDsAxis].IsEngaged)) { return true; } break; default: return false; } return false; } } /// <summary> /// Gets the motion data from the DualShock accelerometer sensor. /// </summary> /// <remarks>https://github.com/ehd/node-ds4/blob/master/index.js</remarks> public DsAccelerometer Motion { get { switch (Model) { case DsModel.DS3: throw new NotImplementedException("DualShock 3 accelerometer readout not implemented yet."); case DsModel.DS4: return new DsAccelerometer { Y = (short) ((RawBytes[22] << 8) | RawBytes[21]), X = (short) -((RawBytes[24] << 8) | RawBytes[23]), Z = (short) -((RawBytes[26] << 8) | RawBytes[25]) }; } return new DsAccelerometer(); } } /// <summary> /// Gets the orientation data from the DualShock gyroscope sensor. /// </summary> /// <remarks>https://github.com/ehd/node-ds4/blob/master/index.js</remarks> public DsGyroscope Orientation { get { switch (Model) { case DsModel.DS3: throw new NotImplementedException("DualShock 3 gyroscope readout not implemented yet."); case DsModel.DS4: return new DsGyroscope { Roll = (short) -((RawBytes[28] << 8) | RawBytes[27]), Yaw = (short) ((RawBytes[30] << 8) | RawBytes[29]), Pitch = (short) ((RawBytes[32] << 8) | RawBytes[31]) }; } return new DsGyroscope(); } } #endregion #region DualShock 4 specific properties /// <summary> /// The first touch spot on the DualShock 4 track pad. /// </summary> /// <remarks>https://github.com/ehd/node-ds4</remarks> public DsTrackPadTouch TrackPadTouch0 { get { if (Model != DsModel.DS4) return null; return new DsTrackPadTouch { Id = RawBytes[43] & 0x7f, IsActive = RawBytes[43] >> 7 == 0, X = ((RawBytes[45] & 0x0f) << 8) | RawBytes[44], Y = RawBytes[46] << 4 | ((RawBytes[45] & 0xf0) >> 4) }; } } /// <summary> /// The second touch spot on the DualShock 4 track pad. /// </summary> /// <remarks>https://github.com/ehd/node-ds4</remarks> public DsTrackPadTouch TrackPadTouch1 { get { if (Model != DsModel.DS4) return null; return new DsTrackPadTouch { Id = RawBytes[47] & 0x7f, IsActive = RawBytes[47] >> 7 == 0, X = ((RawBytes[49] & 0x0f) << 8) | RawBytes[48], Y = RawBytes[50] << 4 | ((RawBytes[49] & 0xf0) >> 4) }; } } #endregion #region Indexers private readonly IDsButtonState _currentDsButtonState = new DsButtonState(); /// <summary> /// Checks if a given button state is engaged in the current packet. /// </summary> /// <param name="button">The DualShock button to question.</param> /// <returns>True if the button is pressed, false if the button is released.</returns> public IDsButtonState this[IDsButton button] { get { if (button is Ds3Button && Model == DsModel.DS3) { var buttons = (uint) ((RawBytes[10] << 0) | (RawBytes[11] << 8) | (RawBytes[12] << 16) | (RawBytes[13] << 24)); _currentDsButtonState.IsPressed = !button.Equals(Ds3Button.None) && (buttons & button.Offset) == button.Offset; _currentDsButtonState.Xbox360Button = _currentDsButtonState.IsPressed ? button.Xbox360Button : X360Button.None; return _currentDsButtonState; } if (button is Ds4Button && Model == DsModel.DS4) { var buttons = (uint) ((RawBytes[13] << 0) | (RawBytes[14] << 8) | (RawBytes[15] << 16)); _currentDsButtonState.IsPressed = !button.Equals(Ds4Button.None) && (buttons & button.Offset) == button.Offset; _currentDsButtonState.Xbox360Button = _currentDsButtonState.IsPressed ? button.Xbox360Button : X360Button.None; return _currentDsButtonState; } return _currentDsButtonState; } } private readonly IDsAxisState _currentDsAxisState = new DsAxisState(); /// <summary> /// Gets the axis state of the current packet. /// </summary> /// <param name="axis">The DualShock axis to question.</param> /// <returns>The value of the axis in question.</returns> public IDsAxisState this[IDsAxis axis] { get { if ((!(axis is Ds3Axis) || Model != DsModel.DS3) && (!(axis is Ds4Axis) || Model != DsModel.DS4)) throw new NotImplementedException(); if (axis.Equals(Ds3Axis.None) || axis.Equals(Ds4Axis.None)) { _currentDsAxisState.IsEngaged = false; return _currentDsAxisState; } _currentDsAxisState.Value = RawBytes[axis.Offset]; _currentDsAxisState.IsEngaged = axis.DefaultValue == 0x00 ? axis.DefaultValue != RawBytes[axis.Offset] /* * match a range for jitter compensation * if axis value is between 117 and 137 it's not reported as engaged * */ : (axis.DefaultValue - 10 > RawBytes[axis.Offset]) || (axis.DefaultValue + 10 < RawBytes[axis.Offset]); return _currentDsAxisState; } } #endregion } }