UNPKG

dualsense-ts

Version:

The natural interface for your DualSense and DualSense Access controllers, with Typescript

332 lines 17.1 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 = void 0; exports.mapAxis = mapAxis; exports.mapTrigger = mapTrigger; exports.mapBatteryLevel = mapBatteryLevel; exports.mapGyroAccel = mapGyroAccel; const id_1 = require("../id"); const battery_state_1 = require("./battery_state"); const calibration_1 = require("./calibration"); __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; } /** Maps a HID input of 0...255 to 0...1 */ function mapTrigger(value) { return (1 / 255) * Math.max(0, Math.min(255, value)); } /** Maps a battery level nibble (0–10) to a 0–1 intensity, clamped */ function mapBatteryLevel(value) { return Math.min(1, Math.max(0, (value & 0xf) / 10)); } /** * 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); } /** Default values for all inputs */ exports.DefaultDualsenseHIDState = { [id_1.InputId.LeftAnalogX]: 0, [id_1.InputId.LeftAnalogY]: 0, [id_1.InputId.RightAnalogX]: 0, [id_1.InputId.RightAnalogY]: 0, [id_1.InputId.LeftTrigger]: 0, [id_1.InputId.RightTrigger]: 0, [id_1.InputId.Triangle]: false, [id_1.InputId.Circle]: false, [id_1.InputId.Cross]: false, [id_1.InputId.Square]: false, [id_1.InputId.Dpad]: 0, [id_1.InputId.Up]: false, [id_1.InputId.Down]: false, [id_1.InputId.Left]: false, [id_1.InputId.Right]: false, [id_1.InputId.RightAnalogButton]: false, [id_1.InputId.LeftAnalogButton]: false, [id_1.InputId.Options]: false, [id_1.InputId.Create]: false, [id_1.InputId.RightTriggerButton]: false, [id_1.InputId.LeftTriggerButton]: false, [id_1.InputId.RightBumper]: false, [id_1.InputId.LeftBumper]: false, [id_1.InputId.Playstation]: false, [id_1.InputId.TouchButton]: false, [id_1.InputId.Mute]: false, [id_1.InputId.Status]: false, [id_1.InputId.TouchX0]: 0, [id_1.InputId.TouchY0]: 0, [id_1.InputId.TouchContact0]: false, [id_1.InputId.TouchId0]: 0, [id_1.InputId.TouchX1]: 0, [id_1.InputId.TouchY1]: 0, [id_1.InputId.TouchContact1]: false, [id_1.InputId.TouchId1]: 0, [id_1.InputId.GyroX]: 0, [id_1.InputId.GyroY]: 0, [id_1.InputId.GyroZ]: 0, [id_1.InputId.AccelX]: 0, [id_1.InputId.AccelY]: 0, [id_1.InputId.AccelZ]: 0, [id_1.InputId.SensorTimestamp]: 0, [id_1.InputId.BatteryLevel]: 0, [id_1.InputId.BatteryStatus]: battery_state_1.ChargeStatus.Discharging, [id_1.InputId.MuteLed]: false, [id_1.InputId.Microphone]: false, [id_1.InputId.Headphone]: false, }; /** 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 = () => { }; /** Callback fired the moment a device is fully attached and ready for I/O */ this.onConnect = () => { }; /** Callback fired the moment a device detaches (cleanly or via error) */ this.onDisconnect = () => { }; /** Precomputed IMU calibration factors, applied during report processing */ this.calibration = calibration_1.DefaultResolvedCalibration; } /** * 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() { const wasAttached = this.device !== undefined; if (this.deviceId) { HIDProvider.claimedDevices.delete(this.deviceId); } this.device = undefined; this.wireless = undefined; this.buffer = undefined; this.limited = undefined; this.calibration = calibration_1.DefaultResolvedCalibration; this.deviceId = undefined; this.serialNumber = undefined; this.onData(exports.DefaultDualsenseHIDState); if (wasAttached) this.onDisconnect(); } /** * 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, [id_1.InputId.LeftAnalogX]: mapAxis(buffer.readUint8(1)), [id_1.InputId.LeftAnalogY]: -mapAxis(buffer.readUint8(2)), [id_1.InputId.RightAnalogX]: mapAxis(buffer.readUint8(3)), [id_1.InputId.RightAnalogY]: -mapAxis(buffer.readUint8(4)), [id_1.InputId.LeftTrigger]: mapTrigger(buffer.readUint8(8)), [id_1.InputId.RightTrigger]: mapTrigger(buffer.readUint8(9)), [id_1.InputId.Triangle]: (buttons & 8) > 0, [id_1.InputId.Circle]: (buttons & 4) > 0, [id_1.InputId.Cross]: (buttons & 2) > 0, [id_1.InputId.Square]: (buttons & 1) > 0, [id_1.InputId.Dpad]: dpad, [id_1.InputId.Up]: dpad < 2 || dpad === 7, [id_1.InputId.Down]: dpad > 2 && dpad < 6, [id_1.InputId.Left]: dpad > 4 && dpad < 8, [id_1.InputId.Right]: dpad > 0 && dpad < 4, [id_1.InputId.LeftTriggerButton]: (miscButtons & 4) > 0, [id_1.InputId.RightTriggerButton]: (miscButtons & 8) > 0, [id_1.InputId.LeftBumper]: (miscButtons & 1) > 0, [id_1.InputId.RightBumper]: (miscButtons & 2) > 0, [id_1.InputId.Create]: (miscButtons & 16) > 0, [id_1.InputId.Options]: (miscButtons & 32) > 0, [id_1.InputId.LeftAnalogButton]: (miscButtons & 64) > 0, [id_1.InputId.RightAnalogButton]: (miscButtons & 128) > 0, [id_1.InputId.Playstation]: (lastButtons & 1) > 0, [id_1.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 { [id_1.InputId.LeftAnalogX]: mapAxis(buffer.readUint8(2)), [id_1.InputId.LeftAnalogY]: -mapAxis(buffer.readUint8(3)), [id_1.InputId.RightAnalogX]: mapAxis(buffer.readUint8(4)), [id_1.InputId.RightAnalogY]: -mapAxis(buffer.readUint8(5)), [id_1.InputId.LeftTrigger]: mapTrigger(buffer.readUint8(6)), [id_1.InputId.RightTrigger]: mapTrigger(buffer.readUint8(7)), [id_1.InputId.Triangle]: (buttons & 8) > 0, [id_1.InputId.Circle]: (buttons & 4) > 0, [id_1.InputId.Cross]: (buttons & 2) > 0, [id_1.InputId.Square]: (buttons & 1) > 0, [id_1.InputId.Dpad]: dpad, [id_1.InputId.Up]: dpad < 2 || dpad === 7, [id_1.InputId.Down]: dpad > 2 && dpad < 6, [id_1.InputId.Left]: dpad > 4 && dpad < 8, [id_1.InputId.Right]: dpad > 0 && dpad < 4, [id_1.InputId.LeftTriggerButton]: (miscButtons & 4) > 0, [id_1.InputId.RightTriggerButton]: (miscButtons & 8) > 0, [id_1.InputId.LeftBumper]: (miscButtons & 1) > 0, [id_1.InputId.RightBumper]: (miscButtons & 2) > 0, [id_1.InputId.Create]: (miscButtons & 16) > 0, [id_1.InputId.Options]: (miscButtons & 32) > 0, [id_1.InputId.LeftAnalogButton]: (miscButtons & 64) > 0, [id_1.InputId.RightAnalogButton]: (miscButtons & 128) > 0, [id_1.InputId.Playstation]: (lastButtons & 1) > 0, [id_1.InputId.TouchButton]: (lastButtons & 2) > 0, [id_1.InputId.Mute]: (lastButtons & 4) > 0, [id_1.InputId.GyroX]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(17), buffer.readUint8(18)), this.calibration.gyroPitch), [id_1.InputId.GyroY]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(19), buffer.readUint8(20)), this.calibration.gyroYaw), [id_1.InputId.GyroZ]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(21), buffer.readUint8(22)), this.calibration.gyroRoll), [id_1.InputId.AccelX]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(23), buffer.readUint8(24)), this.calibration.accelX), [id_1.InputId.AccelY]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(25), buffer.readUint8(26)), this.calibration.accelY), [id_1.InputId.AccelZ]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(27), buffer.readUint8(28)), this.calibration.accelZ), [id_1.InputId.SensorTimestamp]: buffer.readUint32LE(29), [id_1.InputId.TouchId0]: buffer.readUint8(34) & 0x7f, [id_1.InputId.TouchContact0]: (buffer.readUint8(34) & 0x80) === 0, [id_1.InputId.TouchX0]: mapAxis((buffer.readUint16LE(35) << 20) >> 20, 1920), [id_1.InputId.TouchY0]: mapAxis(buffer.readUint16LE(36) >> 4, 1080), [id_1.InputId.TouchId1]: buffer.readUint8(38) & 0x7f, [id_1.InputId.TouchContact1]: (buffer.readUint8(38) & 0x80) === 0, [id_1.InputId.TouchX1]: mapAxis((buffer.readUint16LE(39) << 20) >> 20, 1920), [id_1.InputId.TouchY1]: mapAxis(buffer.readUint16LE(40) >> 4, 1080), [id_1.InputId.Status]: (buffer.readUint8(55) & 8) > 0, [id_1.InputId.MuteLed]: (buffer.readUint8(55) & 4) > 0, [id_1.InputId.Microphone]: (buffer.readUint8(55) & 2) > 0, [id_1.InputId.Headphone]: (buffer.readUint8(55) & 1) > 0, [id_1.InputId.BatteryLevel]: mapBatteryLevel(buffer.readUint8(54)), [id_1.InputId.BatteryStatus]: (buffer.readUint8(54) >> 4), }; } /** * 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 { [id_1.InputId.LeftAnalogX]: mapAxis(buffer.readUint8(1)), [id_1.InputId.LeftAnalogY]: -mapAxis(buffer.readUint8(2)), [id_1.InputId.RightAnalogX]: mapAxis(buffer.readUint8(3)), [id_1.InputId.RightAnalogY]: -mapAxis(buffer.readUint8(4)), [id_1.InputId.LeftTrigger]: mapTrigger(buffer.readUint8(5)), [id_1.InputId.RightTrigger]: mapTrigger(buffer.readUint8(6)), [id_1.InputId.Triangle]: (buttons & 8) > 0, [id_1.InputId.Circle]: (buttons & 4) > 0, [id_1.InputId.Cross]: (buttons & 2) > 0, [id_1.InputId.Square]: (buttons & 1) > 0, [id_1.InputId.Dpad]: dpad, [id_1.InputId.Up]: dpad < 2 || dpad === 7, [id_1.InputId.Down]: dpad > 2 && dpad < 6, [id_1.InputId.Left]: dpad > 4 && dpad < 8, [id_1.InputId.Right]: dpad > 0 && dpad < 4, [id_1.InputId.LeftTriggerButton]: (miscButtons & 4) > 0, [id_1.InputId.RightTriggerButton]: (miscButtons & 8) > 0, [id_1.InputId.LeftBumper]: (miscButtons & 1) > 0, [id_1.InputId.RightBumper]: (miscButtons & 2) > 0, [id_1.InputId.Create]: (miscButtons & 16) > 0, [id_1.InputId.Options]: (miscButtons & 32) > 0, [id_1.InputId.LeftAnalogButton]: (miscButtons & 64) > 0, [id_1.InputId.RightAnalogButton]: (miscButtons & 128) > 0, [id_1.InputId.Playstation]: (lastButtons & 1) > 0, [id_1.InputId.TouchButton]: (lastButtons & 2) > 0, [id_1.InputId.Mute]: (lastButtons & 4) > 0, // The other 5 bits are unused // 5 reserved bytes [id_1.InputId.GyroX]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(16), buffer.readUint8(17)), this.calibration.gyroPitch), [id_1.InputId.GyroY]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(18), buffer.readUint8(19)), this.calibration.gyroYaw), [id_1.InputId.GyroZ]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(20), buffer.readUint8(21)), this.calibration.gyroRoll), [id_1.InputId.AccelX]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(22), buffer.readUint8(23)), this.calibration.accelX), [id_1.InputId.AccelY]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(24), buffer.readUint8(25)), this.calibration.accelY), [id_1.InputId.AccelZ]: (0, calibration_1.applyCal)((0, calibration_1.rawInt16)(buffer.readUint8(26), buffer.readUint8(27)), this.calibration.accelZ), [id_1.InputId.SensorTimestamp]: buffer.readUint32LE(28), // 1 reserved byte [id_1.InputId.TouchId0]: buffer.readUint8(33) & 0x7f, [id_1.InputId.TouchContact0]: (buffer.readUint8(33) & 0x80) === 0, [id_1.InputId.TouchX0]: mapAxis((buffer.readUint16LE(34) << 20) >> 20, 1920), [id_1.InputId.TouchY0]: mapAxis(buffer.readUint16LE(35) >> 4, 1080), [id_1.InputId.TouchId1]: buffer.readUint8(37) & 0x7f, [id_1.InputId.TouchContact1]: (buffer.readUint8(37) & 0x80) === 0, [id_1.InputId.TouchX1]: mapAxis((buffer.readUint16LE(38) << 20) >> 20, 1920), [id_1.InputId.TouchY1]: mapAxis(buffer.readUint16LE(39) >> 4, 1080), // 12 reserved bytes [id_1.InputId.Status]: (buffer.readUint8(54) & 8) > 0, [id_1.InputId.MuteLed]: (buffer.readUint8(54) & 4) > 0, [id_1.InputId.Microphone]: (buffer.readUint8(54) & 2) > 0, [id_1.InputId.Headphone]: (buffer.readUint8(54) & 1) > 0, [id_1.InputId.BatteryLevel]: mapBatteryLevel(buffer.readUint8(53)), [id_1.InputId.BatteryStatus]: (buffer.readUint8(53) >> 4), }; } } 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; /** Global set of device paths currently claimed by a provider instance */ HIDProvider.claimedDevices = new Set(); //# sourceMappingURL=hid_provider.js.map