UNPKG

dualsense-ts

Version:

A natural interface for your DualSense controller, with Typescript

290 lines 15.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HIDProvider = exports.DefaultDualsenseHIDState = exports.mapGyroAccel = exports.mapTrigger = exports.mapAxis = void 0; __exportStar(require("../id"), exports); /** Maps a HID input of 0...n to -1...1 */ function mapAxis(value, max = 255) { return (2 / max) * Math.max(0, Math.min(max, value)) - 1; } exports.mapAxis = mapAxis; /** Maps a HID input of 0...255 to 0...1 */ function mapTrigger(value) { return (1 / 255) * Math.max(0, Math.min(255, value)); } exports.mapTrigger = mapTrigger; /** * Maps a HID input for either gyroscope or acceleration. * Adapted from https://github.com/nondebug/dualsense */ function mapGyroAccel(v0, v1) { let v = (v1 << 8) | v0; if (v > 0x7fff) v -= 0x10000; return mapAxis(v + 0x7fff, 0xffff); } exports.mapGyroAccel = mapGyroAccel; /** Default values for all inputs */ exports.DefaultDualsenseHIDState = { ["LX" /* InputId.LeftAnalogX */]: 0, ["LY" /* InputId.LeftAnalogY */]: 0, ["RX" /* InputId.RightAnalogX */]: 0, ["RY" /* InputId.RightAnalogY */]: 0, ["L2" /* InputId.LeftTrigger */]: 0, ["R2" /* InputId.RightTrigger */]: 0, ["Triangle" /* InputId.Triangle */]: false, ["Circle" /* InputId.Circle */]: false, ["Cross" /* InputId.Cross */]: false, ["Square" /* InputId.Square */]: false, ["Dpad" /* InputId.Dpad */]: 0, ["Up" /* InputId.Up */]: false, ["Down" /* InputId.Down */]: false, ["Left" /* InputId.Left */]: false, ["Right" /* InputId.Right */]: false, ["R3" /* InputId.RightAnalogButton */]: false, ["L3" /* InputId.LeftAnalogButton */]: false, ["Options" /* InputId.Options */]: false, ["Create" /* InputId.Create */]: false, ["R2Button" /* InputId.RightTriggerButton */]: false, ["L2Button" /* InputId.LeftTriggerButton */]: false, ["R1" /* InputId.RightBumper */]: false, ["L1" /* InputId.LeftBumper */]: false, ["Playstation" /* InputId.Playstation */]: false, ["TouchButton" /* InputId.TouchButton */]: false, ["Mute" /* InputId.Mute */]: false, ["Status" /* InputId.Status */]: false, ["TouchX0" /* InputId.TouchX0 */]: 0, ["TouchY0" /* InputId.TouchY0 */]: 0, ["TouchContact0" /* InputId.TouchContact0 */]: false, ["TouchId0" /* InputId.TouchId0 */]: 0, ["TouchX1" /* InputId.TouchX1 */]: 0, ["TouchY1" /* InputId.TouchY1 */]: 0, ["TouchContact1" /* InputId.TouchContact1 */]: false, ["TouchId1" /* InputId.TouchId1 */]: 0, ["GyroX" /* InputId.GyroX */]: 0, ["GyroY" /* InputId.GyroY */]: 0, ["GyroZ" /* InputId.GyroZ */]: 0, ["AccelX" /* InputId.AccelX */]: 0, ["AccelY" /* InputId.AccelY */]: 0, ["AccelZ" /* InputId.AccelZ */]: 0, }; /** Supports a connection to a physical or virtual Dualsense device */ class HIDProvider { constructor() { /** Callback to use for new input events */ this.onData = () => { }; /** Callback to use for Error events */ this.onError = () => { }; } /** * Sselects the correct method for reading the report. * @param buffer HID report buffer */ processReport(buffer) { const reportId = buffer.readUint8(0); switch (reportId) { case 0x01: return this.wireless ? this.processBluetoothInputReport01(buffer) : this.processUsbInputReport01(buffer); case 0x31: return this.processBluetoothInputReport31(buffer); default: this.onError(new Error(`Cannot process report, unexpected report id: ${reportId}`)); this.disconnect(); return { ...exports.DefaultDualsenseHIDState }; } } /** * Reset the HIDProvider state when the device is disconnected */ reset() { this.device = undefined; this.wireless = undefined; this.buffer = undefined; this.limited = undefined; this.onData(exports.DefaultDualsenseHIDState); } /** * Process a bluetooth input report of type 01. * @param buffer the report */ processBluetoothInputReport01(buffer) { this.limited = true; const buttonsAndDpad = buffer.readUint8(5); const buttons = buttonsAndDpad >> 4; const dpad = buttonsAndDpad & 0b1111; const miscButtons = buffer.readUint8(6); const lastButtons = buffer.readUint8(7); return { ...exports.DefaultDualsenseHIDState, ["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(1)), ["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(2)), ["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(3)), ["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(4)), ["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(8)), ["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(9)), ["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0, ["Circle" /* InputId.Circle */]: (buttons & 4) > 0, ["Cross" /* InputId.Cross */]: (buttons & 2) > 0, ["Square" /* InputId.Square */]: (buttons & 1) > 0, ["Dpad" /* InputId.Dpad */]: dpad, ["Up" /* InputId.Up */]: dpad < 2 || dpad === 7, ["Down" /* InputId.Down */]: dpad > 2 && dpad < 6, ["Left" /* InputId.Left */]: dpad > 4 && dpad < 8, ["Right" /* InputId.Right */]: dpad > 0 && dpad < 4, ["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0, ["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0, ["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0, ["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0, ["Create" /* InputId.Create */]: (miscButtons & 16) > 0, ["Options" /* InputId.Options */]: (miscButtons & 32) > 0, ["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0, ["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0, ["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0, ["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0, // See https://github.com/nondebug/dualsense/blob/main/dualsense-explorer.html#L338 // // "By default, bluetooth-connected DualSense only sends input report 0x01 which omits motion and touchpad data. // Reading feature report 0x05 causes it to start sending input report 0x31. // // Note: The Gamepad API will do this for us if it enumerates the gamepad. // Other applications like Steam may have also done this already." }; } /** Process bluetooth input report of type 31 */ processBluetoothInputReport31(buffer) { this.limited = false; const buttonsAndDpad = buffer.readUint8(9); const buttons = buttonsAndDpad >> 4; const dpad = buttonsAndDpad & 0b1111; const miscButtons = buffer.readUint8(10); const lastButtons = buffer.readUint8(11); return { ["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(2)), ["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(3)), ["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(4)), ["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(5)), ["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(6)), ["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(7)), ["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0, ["Circle" /* InputId.Circle */]: (buttons & 4) > 0, ["Cross" /* InputId.Cross */]: (buttons & 2) > 0, ["Square" /* InputId.Square */]: (buttons & 1) > 0, ["Dpad" /* InputId.Dpad */]: dpad, ["Up" /* InputId.Up */]: dpad < 2 || dpad === 7, ["Down" /* InputId.Down */]: dpad > 2 && dpad < 6, ["Left" /* InputId.Left */]: dpad > 4 && dpad < 8, ["Right" /* InputId.Right */]: dpad > 0 && dpad < 4, ["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0, ["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0, ["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0, ["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0, ["Create" /* InputId.Create */]: (miscButtons & 16) > 0, ["Options" /* InputId.Options */]: (miscButtons & 32) > 0, ["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0, ["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0, ["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0, ["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0, ["Mute" /* InputId.Mute */]: (lastButtons & 4) > 0, ["GyroX" /* InputId.GyroX */]: mapGyroAccel(buffer.readUint8(17), buffer.readUint8(18)), ["GyroY" /* InputId.GyroY */]: mapGyroAccel(buffer.readUint8(19), buffer.readUint8(20)), ["GyroZ" /* InputId.GyroZ */]: mapGyroAccel(buffer.readUint8(21), buffer.readUint8(22)), ["AccelX" /* InputId.AccelX */]: mapGyroAccel(buffer.readUint8(23), buffer.readUint8(24)), ["AccelY" /* InputId.AccelY */]: mapGyroAccel(buffer.readUint8(25), buffer.readUint8(26)), ["AccelZ" /* InputId.AccelZ */]: mapGyroAccel(buffer.readUint8(27), buffer.readUint8(28)), ["TouchId0" /* InputId.TouchId0 */]: buffer.readUint8(34) & 0x7f, ["TouchContact0" /* InputId.TouchContact0 */]: (buffer.readUint8(34) & 0x80) === 0, ["TouchX0" /* InputId.TouchX0 */]: mapAxis((buffer.readUint16LE(35) << 20) >> 20, 1920), ["TouchY0" /* InputId.TouchY0 */]: mapAxis(buffer.readUint16LE(36) >> 4, 1080), ["TouchId1" /* InputId.TouchId1 */]: buffer.readUint8(38) & 0x7f, ["TouchContact1" /* InputId.TouchContact1 */]: (buffer.readUint8(38) & 0x80) === 0, ["TouchX1" /* InputId.TouchX1 */]: mapAxis((buffer.readUint16LE(39) << 20) >> 20, 1920), ["TouchY1" /* InputId.TouchY1 */]: mapAxis(buffer.readUint16LE(40) >> 4, 1080), ["Status" /* InputId.Status */]: (buffer.readUint8(55) & 4) > 0, }; } /** * Process a USB input report of type 01. * @param buffer the report */ processUsbInputReport01(buffer) { this.limited = false; const buttonsAndDpad = buffer.readUint8(8); const buttons = buttonsAndDpad >> 4; const dpad = buttonsAndDpad & 0b1111; const miscButtons = buffer.readUint8(9); const lastButtons = buffer.readUint8(10); return { ["LX" /* InputId.LeftAnalogX */]: mapAxis(buffer.readUint8(1)), ["LY" /* InputId.LeftAnalogY */]: -mapAxis(buffer.readUint8(2)), ["RX" /* InputId.RightAnalogX */]: mapAxis(buffer.readUint8(3)), ["RY" /* InputId.RightAnalogY */]: -mapAxis(buffer.readUint8(4)), ["L2" /* InputId.LeftTrigger */]: mapTrigger(buffer.readUint8(5)), ["R2" /* InputId.RightTrigger */]: mapTrigger(buffer.readUint8(6)), ["Triangle" /* InputId.Triangle */]: (buttons & 8) > 0, ["Circle" /* InputId.Circle */]: (buttons & 4) > 0, ["Cross" /* InputId.Cross */]: (buttons & 2) > 0, ["Square" /* InputId.Square */]: (buttons & 1) > 0, ["Dpad" /* InputId.Dpad */]: dpad, ["Up" /* InputId.Up */]: dpad < 2 || dpad === 7, ["Down" /* InputId.Down */]: dpad > 2 && dpad < 6, ["Left" /* InputId.Left */]: dpad > 4 && dpad < 8, ["Right" /* InputId.Right */]: dpad > 0 && dpad < 4, ["L2Button" /* InputId.LeftTriggerButton */]: (miscButtons & 4) > 0, ["R2Button" /* InputId.RightTriggerButton */]: (miscButtons & 8) > 0, ["L1" /* InputId.LeftBumper */]: (miscButtons & 1) > 0, ["R1" /* InputId.RightBumper */]: (miscButtons & 2) > 0, ["Create" /* InputId.Create */]: (miscButtons & 16) > 0, ["Options" /* InputId.Options */]: (miscButtons & 32) > 0, ["L3" /* InputId.LeftAnalogButton */]: (miscButtons & 64) > 0, ["R3" /* InputId.RightAnalogButton */]: (miscButtons & 128) > 0, ["Playstation" /* InputId.Playstation */]: (lastButtons & 1) > 0, ["TouchButton" /* InputId.TouchButton */]: (lastButtons & 2) > 0, ["Mute" /* InputId.Mute */]: (lastButtons & 4) > 0, // The other 5 bits are unused // 5 reserved bytes ["GyroX" /* InputId.GyroX */]: mapGyroAccel(buffer.readUint8(16), buffer.readUint8(17)), ["GyroY" /* InputId.GyroY */]: mapGyroAccel(buffer.readUint8(18), buffer.readUint8(19)), ["GyroZ" /* InputId.GyroZ */]: mapGyroAccel(buffer.readUint8(20), buffer.readUint8(21)), ["AccelX" /* InputId.AccelX */]: mapGyroAccel(buffer.readUint8(22), buffer.readUint8(23)), ["AccelY" /* InputId.AccelY */]: mapGyroAccel(buffer.readUint8(24), buffer.readUint8(25)), ["AccelZ" /* InputId.AccelZ */]: mapGyroAccel(buffer.readUint8(26), buffer.readUint8(27)), // 4 bytes for sensor timestamp (32LE) // 1 reserved byte ["TouchId0" /* InputId.TouchId0 */]: buffer.readUint8(33) & 0x7f, ["TouchContact0" /* InputId.TouchContact0 */]: (buffer.readUint8(33) & 0x80) === 0, ["TouchX0" /* InputId.TouchX0 */]: mapAxis((buffer.readUint16LE(34) << 20) >> 20, 1920), ["TouchY0" /* InputId.TouchY0 */]: mapAxis(buffer.readUint16LE(35) >> 4, 1080), ["TouchId1" /* InputId.TouchId1 */]: buffer.readUint8(37) & 0x7f, ["TouchContact1" /* InputId.TouchContact1 */]: (buffer.readUint8(37) & 0x80) === 0, ["TouchX1" /* InputId.TouchX1 */]: mapAxis((buffer.readUint16LE(38) << 20) >> 20, 1920), ["TouchY1" /* InputId.TouchY1 */]: mapAxis(buffer.readUint16LE(39) >> 4, 1080), // 12 reserved bytes ["Status" /* InputId.Status */]: (buffer.readUint8(54) & 4) > 0, }; } } exports.HIDProvider = HIDProvider; /** HID vendorId for a Dualsense controller */ HIDProvider.vendorId = 1356; /** HID productId for a Dualsense controller */ HIDProvider.productId = 3302; /** HID usagePage for a Dualsense controller */ HIDProvider.usagePage = 0x0001; /** HID usage for a Dualsense controller */ HIDProvider.usage = 0x0005; //# sourceMappingURL=hid_provider.js.map