--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+ <title>DualSense rumble test</title>
+ <style>
+ body { font-family: sans-serif; padding: 20px; background: #1a1a1a; color: #fff; }
+ button { padding: 15px 30px; font-size: 16px; margin: 10px; cursor: pointer; }
+ #status { margin: 20px 0; padding: 10px; background: #333; }
+ </style>
+</head>
+<body>
+ <h1>DualSense rumble test</h1>
+ <div id="status">Not connected</div>
+ <button onclick="connect()">connect controller</button>
+ <button onclick="rumble()" id="rumbleBtn" disabled>test rumble</button>
+
+ <script>
+ const SONY_VENDOR_ID = 0x054c;
+ const DUALSENSE_PRODUCT_ID = 0x0ce6;
+ let device = null;
+
+ async function connect() {
+ const devices = await navigator.hid.requestDevice({
+ filters: [{ vendorId: SONY_VENDOR_ID, productId: DUALSENSE_PRODUCT_ID }]
+ });
+ if (devices.length === 0) return;
+
+ device = devices[0];
+ await device.open();
+ document.getElementById('status').textContent = 'connected: ' + device.productName;
+ document.getElementById('rumbleBtn').disabled = false;
+ }
+
+ async function sendRumble(left, right) {
+ if (!device) return;
+
+ // get report size from device
+ let size = 47;
+ if (device.collections?.[0]?.outputReports?.[0]?.items?.[0]) {
+ const item = device.collections[0].outputReports[0].items[0];
+ size = (item.reportSize * item.reportCount) / 8;
+ }
+
+ const report = new Uint8Array(size);
+ report[0] = 0x03; // enable rumble
+ report[1] = 0xF7;
+ report[2] = right;
+ report[3] = left;
+
+ await device.sendReport(0x02, report);
+ }
+
+ async function rumble() {
+ await sendRumble(255, 255);
+ setTimeout(() => sendRumble(0, 0), 500);
+ }
+
+ // auto-connect if already paired
+ navigator.hid.getDevices().then(async devices => {
+ const ds = devices.find(d => d.vendorId === SONY_VENDOR_ID && d.productId === DUALSENSE_PRODUCT_ID);
+ if (ds) {
+ device = ds;
+ await device.open();
+ document.getElementById('status').textContent = 'connected: ' + device.productName;
+ document.getElementById('rumbleBtn').disabled = false;
+ }
+ });
+ </script>
+</body>
+</html>