UNPKG

dualsense-ts

Version:

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

166 lines 6.52 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.NodeHIDProvider = void 0; const hid_provider_1 = require("./hid_provider"); const bt_checksum_1 = require("./bt_checksum"); const calibration_1 = require("./calibration"); class NodeHIDProvider extends hid_provider_1.HIDProvider { constructor(options = {}) { super(); this.connecting = false; this.targetPath = options.devicePath; this.targetSerial = options.serialNumber; } /** List all available Dualsense controllers */ static async enumerate() { let nodeHid; try { nodeHid = await import("node-hid"); } catch { return []; } const controllers = nodeHid.devices(hid_provider_1.HIDProvider.vendorId, hid_provider_1.HIDProvider.productId); return controllers .filter((d) => Boolean(d.path)) .map((d) => ({ path: d.path, serialNumber: d.serialNumber ?? undefined, wireless: d.interface === -1, })); } async connect() { if (this.connecting) return; if (typeof window !== "undefined") return this.onError(new Error("Attempted to use node-hid in browser environment")); this.connecting = true; let nodeHid; try { nodeHid = await import("node-hid"); } catch (err) { this.connecting = false; return this.onError(new Error(`Could not import 'node-hid'. Did you add it?\nError: ${err instanceof Error ? err.message : "???"}`)); } try { this.disconnect(); const { HID, devices } = nodeHid; const controllers = devices(hid_provider_1.HIDProvider.vendorId, hid_provider_1.HIDProvider.productId); // Find a suitable controller: targeted path, then serial match, then first unclaimed let target = this.targetPath ? controllers.find((d) => d.path === this.targetPath) : undefined; // Fall back to serial match (handles USB/BT switches where path changes) if (!target && this.targetSerial) { target = controllers.find((d) => d.serialNumber === this.targetSerial && d.path && !hid_provider_1.HIDProvider.claimedDevices.has(d.path)); if (target?.path != null) { this.targetPath = target.path; } } // No specific target: grab first unclaimed device if (!target && !this.targetPath && !this.targetSerial) { target = controllers.find((d) => d.path && !hid_provider_1.HIDProvider.claimedDevices.has(d.path)); } if (!target?.path) { return this.onError(new Error(`No controllers (${devices().length} other devices)`)); } // Detect connection type this.wireless = target.interface === -1; const device = new HID(target.path); // Claim this device this.deviceId = target.path; this.serialNumber = target.serialNumber ?? undefined; hid_provider_1.HIDProvider.claimedDevices.add(target.path); // Enable accelerometer, gyro, touchpad — and capture IMU calibration const calBuf = device.getFeatureReport(0x05, 41); try { this.calibration = (0, calibration_1.resolveCalibration)((0, calibration_1.parseIMUCalibration)(new Uint8Array(calBuf))); } catch { /* use default calibration */ } device.on("data", (arg) => { this.buffer = arg; this.onData(this.process(arg)); }); device.on("error", (err) => { this.disconnect(); this.onError(err); }); this.device = device; this.onConnect(); } catch (err) { this.onError(err instanceof Error ? err : new Error(String(err))); } finally { this.connecting = false; } } write(data) { if (!this.device) return Promise.resolve(); this.device.write(Array.from(data)); return Promise.resolve(); } readFeatureReport(reportId, length) { if (!this.device) return Promise.reject(new Error("No device connected")); const buf = this.device.getFeatureReport(reportId, length); return Promise.resolve(new Uint8Array(buf)); } sendFeatureReport(_reportId, data) { if (!this.device) return Promise.resolve(); // node-hid sendFeatureReport expects the report ID as the first byte of the buffer. // For Bluetooth, pad to 64 bytes and append CRC-32 in the last 4 bytes. if (this.wireless) { const reportId = data[0]; const payload = new Uint8Array(64); payload.set(data.slice(1)); const crc = (0, bt_checksum_1.computeFeatureReportChecksum)(reportId, payload); const off = payload.length - 4; payload[off] = crc & 0xff; payload[off + 1] = (crc >>> 8) & 0xff; payload[off + 2] = (crc >>> 16) & 0xff; payload[off + 3] = (crc >>> 24) & 0xff; // Prepend report ID for node-hid const buf = new Uint8Array(1 + payload.length); buf[0] = reportId; buf.set(payload, 1); this.device.sendFeatureReport(Array.from(buf)); } else { this.device.sendFeatureReport(Array.from(data)); } return Promise.resolve(); } get connected() { return this.device !== undefined; } disconnect() { if (this.device) { this.device.removeAllListeners(); this.device.close(); } this.reset(); } process(buffer) { const report = { length: buffer.length, readUint8(offset) { return buffer.readUint8(offset); }, readUint16LE(offset) { return buffer.readUint16LE(offset); }, readUint32LE(offset) { return buffer.readUint32LE(offset); }, }; return this.processReport(report); } } exports.NodeHIDProvider = NodeHIDProvider; //# sourceMappingURL=node_hid_provider.js.map