UNPKG

dualsense-ts

Version:

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

292 lines 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DualsenseAccess = void 0; const elements_1 = require("./elements"); const access_profile_leds_1 = require("./elements/access_profile_leds"); const access_status_led_1 = require("./elements/access_status_led"); const access_player_indicator_1 = require("./elements/access_player_indicator"); const input_1 = require("./input"); const access_hid_state_1 = require("./hid/access/access_hid_state"); const access_hid_1 = require("./hid/access/access_hid"); const access_null_hid_provider_1 = require("./hid/access/access_null_hid_provider"); const access_platform_hid_provider_1 = require("./hid/access/access_platform_hid_provider"); const battery_state_1 = require("./hid/battery_state"); /** Tracks the active profile ID (1–3) */ class ProfileId extends input_1.Input { constructor() { super(...arguments); this.state = 1; } get active() { return false; } } /** Represents a DualSense Access controller */ class DualsenseAccess extends input_1.Input { /** Firmware and hardware information */ get firmwareInfo() { return this.hid.firmwareInfo; } /** Factory information (serial number, body color, board revision) */ get factoryInfo() { return this.hid.factoryInfo; } /** True if any input is active */ get active() { return Object.values(this).some((input) => input !== this && input instanceof input_1.Input && input.active); } /** Returns true if connected via Bluetooth */ get wireless() { return this.hid.wireless; } /** Factory-stamped serial number */ get serialNumber() { return this.hid.factoryInfo.serialNumber; } constructor(params = {}) { super(params); this.state = this; /** RGB light bar */ this.lightbar = new elements_1.Lightbar(); /** Profile indicator LEDs (3 LEDs, animation modes) */ this.profileLeds = new access_profile_leds_1.AccessProfileLeds(); /** Player indicator LED pattern (6-segment cross) */ this.playerIndicator = new access_player_indicator_1.AccessPlayerIndicatorLed(); /** White status LED */ this.statusLed = new access_status_led_1.AccessStatusLed(); /** Active interval timers, cleared on dispose */ this.timers = []; /** Buffered battery reading */ this.pendingBattery = { peakLevel: 0, status: battery_state_1.ChargeStatus.Discharging, }; this.b1 = new elements_1.Momentary({ icon: "1", name: "B1", ...(params.b1 ?? {}) }); this.b2 = new elements_1.Momentary({ icon: "2", name: "B2", ...(params.b2 ?? {}) }); this.b3 = new elements_1.Momentary({ icon: "3", name: "B3", ...(params.b3 ?? {}) }); this.b4 = new elements_1.Momentary({ icon: "4", name: "B4", ...(params.b4 ?? {}) }); this.b5 = new elements_1.Momentary({ icon: "5", name: "B5", ...(params.b5 ?? {}) }); this.b6 = new elements_1.Momentary({ icon: "6", name: "B6", ...(params.b6 ?? {}) }); this.b7 = new elements_1.Momentary({ icon: "7", name: "B7", ...(params.b7 ?? {}) }); this.b8 = new elements_1.Momentary({ icon: "8", name: "B8", ...(params.b8 ?? {}) }); this.center = new elements_1.Momentary({ icon: "C", name: "Center", ...(params.center ?? {}), }); this.ps = new elements_1.Momentary({ icon: "㎰", name: "PS", ...(params.ps ?? {}), }); this.profile = new elements_1.Momentary({ icon: "P", name: "Profile", ...(params.profile ?? {}), }); this.stick = new elements_1.Analog({ icon: "⊕", name: "Stick", ...(params.stick ?? {}), }); // Profile-mapped inputs (DualSense-compatible) this.left = new elements_1.Unisense({ icon: "L", name: "Left", ...(params.left ?? {}), }); this.right = new elements_1.Unisense({ icon: "R", name: "Right", ...(params.right ?? {}), }); this.dpad = new elements_1.Dpad({ icon: "D", name: "D-pad", ...(params.dpad ?? {}), }); this.cross = new elements_1.Momentary({ icon: "⮿", name: "Cross", ...(params.cross ?? {}), }); this.circle = new elements_1.Momentary({ icon: "⊚", name: "Circle", ...(params.circle ?? {}), }); this.square = new elements_1.Momentary({ icon: "🟗", name: "Square", ...(params.square ?? {}), }); this.triangle = new elements_1.Momentary({ icon: "🟕", name: "Triangle", ...(params.triangle ?? {}), }); this.touchpad = new elements_1.Touchpad({ icon: "⎚", name: "Touchpad", ...(params.touchpad ?? {}), }); this.options = new elements_1.Momentary({ icon: "⋯", name: "Options", ...(params.options ?? {}), }); this.create = new elements_1.Momentary({ icon: "🖉", name: "Create", ...(params.create ?? {}), }); this.mute = new elements_1.Mute({ icon: "🕩", name: "Mute", ...(params.mute ?? {}), }); this.battery = new elements_1.Battery({ icon: "🔋", name: "Battery", ...(params.battery ?? {}), }); this.profileId = new ProfileId({ icon: "#", name: "ProfileId" }); this.connection = new elements_1.Momentary({ icon: "🔗", name: "Connected", ...(params.connection ?? {}), }); this.connection[input_1.InputSet](false); const externallyManaged = params.hid !== undefined; this.hid = params.hid === null ? new access_hid_1.AccessHID(new access_null_hid_provider_1.AccessNullHIDProvider()) : (params.hid ?? new access_hid_1.AccessHID(new access_platform_hid_provider_1.AccessPlatformHIDProvider())); this.hid.register((state) => { this.processHID(state); }); const lightbarMemo = { key: "" }; const profileLedsMemo = { key: "" }; const playerIndicatorMemo = { key: "" }; const statusLedMemo = { key: "" }; const invalidateMemos = () => { lightbarMemo.key = ""; profileLedsMemo.key = ""; playerIndicatorMemo.key = ""; statusLedMemo.key = ""; }; this.hid.onConnectionChange((connected) => { this.connection[input_1.InputSet](connected); if (connected) { invalidateMemos(); // BT full-mode switch (triggered by Feature Report 0x05) can take // up to ~1s. Output reports sent during this window are silently // dropped, so re-invalidate memos to force re-sends. this.timers.push(setTimeout(() => invalidateMemos(), 500)); this.timers.push(setTimeout(() => invalidateMemos(), 1500)); } }); this.connection[input_1.InputSet](this.hid.provider.connected); // Standalone mode: poll for devices if (!externallyManaged) { this.timers.push(setInterval(() => { if (!this.hid.provider.connected) { void Promise.resolve(this.hid.provider.connect()).catch(() => { }); } }, 200)); } // Battery: 1s buffered cadence with peak-filtering this.timers.push(setInterval(() => { if (!this.connection.active) return; this.battery.level[input_1.InputSet](this.pendingBattery.peakLevel); this.battery.status[input_1.InputSet](this.pendingBattery.status); this.pendingBattery.peakLevel = 0; }, 1000)); // Output loop (30Hz) // When any subsystem changes, send all 4 in one report — the Access // controller over BT requires combined mutator + LED_FLAGS_1. this.timers.push(setInterval(() => { if (!this.connection.active) return; const lightbarKey = this.lightbar.toKey(); const profileLedsKey = this.profileLeds.toKey(); const playerIndicatorKey = this.playerIndicator.toKey(); const statusLedKey = this.statusLed.toKey(); const anyChanged = lightbarKey !== lightbarMemo.key || profileLedsKey !== profileLedsMemo.key || playerIndicatorKey !== playerIndicatorMemo.key || statusLedKey !== statusLedMemo.key; if (anyChanged) { const { r, g, b } = this.lightbar.color; this.hid.setLightbar(r, g, b); this.hid.setProfileLeds(this.profileLeds.mode); this.hid.setPlayerIndicator(this.playerIndicator.pattern); this.hid.setStatusLed(this.statusLed.on); lightbarMemo.key = lightbarKey; profileLedsMemo.key = profileLedsKey; playerIndicatorMemo.key = playerIndicatorKey; statusLedMemo.key = statusLedKey; } }, 1000 / 30)); } /** Stop all internal timers and release resources */ dispose() { this.timers.forEach((timer) => { clearInterval(timer); }); this.timers.length = 0; this.hid.dispose(); } /** Distributes HID state to inputs */ processHID(state) { // Raw hardware inputs this.b1[input_1.InputSet](state[access_hid_state_1.AccessInputId.B1]); this.b2[input_1.InputSet](state[access_hid_state_1.AccessInputId.B2]); this.b3[input_1.InputSet](state[access_hid_state_1.AccessInputId.B3]); this.b4[input_1.InputSet](state[access_hid_state_1.AccessInputId.B4]); this.b5[input_1.InputSet](state[access_hid_state_1.AccessInputId.B5]); this.b6[input_1.InputSet](state[access_hid_state_1.AccessInputId.B6]); this.b7[input_1.InputSet](state[access_hid_state_1.AccessInputId.B7]); this.b8[input_1.InputSet](state[access_hid_state_1.AccessInputId.B8]); this.center[input_1.InputSet](state[access_hid_state_1.AccessInputId.Center]); this.ps[input_1.InputSet](state[access_hid_state_1.AccessInputId.PS]); this.profile[input_1.InputSet](state[access_hid_state_1.AccessInputId.Profile]); this.stick.x[input_1.InputSet](state[access_hid_state_1.AccessInputId.StickX]); this.stick.y[input_1.InputSet](state[access_hid_state_1.AccessInputId.StickY]); this.stick.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.StickClick]); // Profile-mapped inputs this.left.analog.x[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedLeftStickX]); this.left.analog.y[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedLeftStickY]); this.left.analog.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.L3]); this.left.trigger[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedL2]); this.left.trigger.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.L2Button]); this.left.bumper[input_1.InputSet](state[access_hid_state_1.AccessInputId.L1]); this.right.analog.x[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedRightStickX]); this.right.analog.y[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedRightStickY]); this.right.analog.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.R3]); this.right.trigger[input_1.InputSet](state[access_hid_state_1.AccessInputId.MappedR2]); this.right.trigger.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.R2Button]); this.right.bumper[input_1.InputSet](state[access_hid_state_1.AccessInputId.R1]); this.dpad.up[input_1.InputSet](state[access_hid_state_1.AccessInputId.DpadUp]); this.dpad.down[input_1.InputSet](state[access_hid_state_1.AccessInputId.DpadDown]); this.dpad.left[input_1.InputSet](state[access_hid_state_1.AccessInputId.DpadLeft]); this.dpad.right[input_1.InputSet](state[access_hid_state_1.AccessInputId.DpadRight]); this.cross[input_1.InputSet](state[access_hid_state_1.AccessInputId.Cross]); this.circle[input_1.InputSet](state[access_hid_state_1.AccessInputId.Circle]); this.square[input_1.InputSet](state[access_hid_state_1.AccessInputId.Square]); this.triangle[input_1.InputSet](state[access_hid_state_1.AccessInputId.Triangle]); this.touchpad.button[input_1.InputSet](state[access_hid_state_1.AccessInputId.TouchButton]); this.options[input_1.InputSet](state[access_hid_state_1.AccessInputId.Options]); this.create[input_1.InputSet](state[access_hid_state_1.AccessInputId.Create]); this.mute[input_1.InputSet](state[access_hid_state_1.AccessInputId.MuteButton]); this.profileId[input_1.InputSet](state[access_hid_state_1.AccessInputId.ProfileId]); const level = state[access_hid_state_1.AccessInputId.BatteryLevel]; if (level > this.pendingBattery.peakLevel) { this.pendingBattery.peakLevel = level; } this.pendingBattery.status = state[access_hid_state_1.AccessInputId.BatteryStatus]; } } exports.DualsenseAccess = DualsenseAccess; //# sourceMappingURL=dualsense_access.js.map