UNPKG

dualsense-ts

Version:

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

164 lines 6.45 kB
"use strict"; /** * IMU calibration data from Feature Report 0x05. * * The DualSense stores per-unit factory calibration for the gyroscope and * accelerometer. Reading and applying these offsets and scale factors * removes bias drift and normalises per-axis sensitivity, giving more * accurate motion data than the hardcoded mapping. * * Layout mirrors the Linux kernel hid-playstation.c interpretation of * Feature Report 0x05 (41 bytes including the report-ID prefix). */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultResolvedCalibration = void 0; exports.parseIMUCalibration = parseIMUCalibration; exports.resolveCalibration = resolveCalibration; exports.readIMUCalibration = readIMUCalibration; exports.rawInt16 = rawInt16; exports.applyCal = applyCal; // --------------------------------------------------------------------------- // Defaults // --------------------------------------------------------------------------- /** Identity calibration: zero bias, uniform 1/32767 scale */ exports.DefaultResolvedCalibration = { gyroPitch: { bias: 0, scale: 1 / 32767 }, gyroYaw: { bias: 0, scale: 1 / 32767 }, gyroRoll: { bias: 0, scale: 1 / 32767 }, accelX: { bias: 0, scale: 1 / 32767 }, accelY: { bias: 0, scale: 1 / 32767 }, accelZ: { bias: 0, scale: 1 / 32767 }, }; // --------------------------------------------------------------------------- // Parsing // --------------------------------------------------------------------------- const REPORT_ID = 0x05; const REPORT_LENGTH = 41; function readInt16LE(buf, offset) { let v = buf[offset] | (buf[offset + 1] << 8); if (v > 0x7fff) v -= 0x10000; return v; } /** * Parse Feature Report 0x05 into an {@link IMUCalibration}. * * The report may or may not include the report-ID byte as the first * element depending on platform — the parser auto-detects. */ function parseIMUCalibration(buf) { const off = buf[0] === REPORT_ID ? 1 : 0; return { gyro: { pitch: { bias: readInt16LE(buf, off + 0), plus: readInt16LE(buf, off + 6), minus: readInt16LE(buf, off + 8), }, yaw: { bias: readInt16LE(buf, off + 2), plus: readInt16LE(buf, off + 10), minus: readInt16LE(buf, off + 12), }, roll: { bias: readInt16LE(buf, off + 4), plus: readInt16LE(buf, off + 14), minus: readInt16LE(buf, off + 16), }, speedPlus: readInt16LE(buf, off + 18), speedMinus: readInt16LE(buf, off + 20), }, accel: { x: { plus: readInt16LE(buf, off + 22), minus: readInt16LE(buf, off + 24) }, y: { plus: readInt16LE(buf, off + 26), minus: readInt16LE(buf, off + 28) }, z: { plus: readInt16LE(buf, off + 30), minus: readInt16LE(buf, off + 32) }, }, }; } // --------------------------------------------------------------------------- // Resolution: raw calibration → precomputed factors // --------------------------------------------------------------------------- /** * Precompute per-axis bias and scale from raw calibration data. * * **Gyroscope:** Removes resting-state bias and normalises per-axis * sensitivity so that the same physical rotation rate produces the same * numeric value on all three axes. The most-sensitive axis (largest * raw range) maps exactly to [-1, 1] at full scale; less-sensitive * axes are boosted to match, with ≤ 4% clipping at extreme values. * * **Accelerometer:** Removes the zero-point offset (manufacturing * tolerance) and normalises per-axis sensitivity the same way. */ function resolveCalibration(cal) { const pitchRange = cal.gyro.pitch.plus - cal.gyro.pitch.minus; const yawRange = cal.gyro.yaw.plus - cal.gyro.yaw.minus; const rollRange = cal.gyro.roll.plus - cal.gyro.roll.minus; const maxGyroRange = Math.max(Math.abs(pitchRange), Math.abs(yawRange), Math.abs(rollRange)); const xRange = cal.accel.x.plus - cal.accel.x.minus; const yRange = cal.accel.y.plus - cal.accel.y.minus; const zRange = cal.accel.z.plus - cal.accel.z.minus; const maxAccelRange = Math.max(Math.abs(xRange), Math.abs(yRange), Math.abs(zRange)); function gyroScale(range) { return range !== 0 ? maxGyroRange / range / 32767 : 1 / 32767; } function accelScale(range) { return range !== 0 ? maxAccelRange / range / 32767 : 1 / 32767; } return { gyroPitch: { bias: cal.gyro.pitch.bias, scale: gyroScale(pitchRange) }, gyroYaw: { bias: cal.gyro.yaw.bias, scale: gyroScale(yawRange) }, gyroRoll: { bias: cal.gyro.roll.bias, scale: gyroScale(rollRange) }, accelX: { bias: (cal.accel.x.plus + cal.accel.x.minus) / 2, scale: accelScale(xRange), }, accelY: { bias: (cal.accel.y.plus + cal.accel.y.minus) / 2, scale: accelScale(yRange), }, accelZ: { bias: (cal.accel.z.plus + cal.accel.z.minus) / 2, scale: accelScale(zRange), }, }; } // --------------------------------------------------------------------------- // Reading from device // --------------------------------------------------------------------------- /** * Read and parse IMU calibration from a connected controller. * Returns undefined if the report cannot be read. */ async function readIMUCalibration(provider) { try { const buf = await provider.readFeatureReport(REPORT_ID, REPORT_LENGTH); if (!buf || buf.length < 35) return undefined; return parseIMUCalibration(buf); } catch { return undefined; } } // --------------------------------------------------------------------------- // Per-sample helpers // --------------------------------------------------------------------------- /** * Assemble two raw report bytes into a signed int16. */ function rawInt16(lo, hi) { let v = (hi << 8) | lo; if (v > 0x7fff) v -= 0x10000; return v; } /** * Apply precomputed calibration to a single axis sample. * `calibrated = clamp((raw − bias) × scale, −1, 1)` */ function applyCal(raw, factors) { const v = (raw - factors.bias) * factors.scale; return v > 1 ? 1 : v < -1 ? -1 : v; } //# sourceMappingURL=calibration.js.map