WebHID로 레거시 하드웨어 되살리기

27032 단어 hardwarewebwebhidfugu

금을 찾아서!



수년 동안 나는 SomeDay(TM)에 사용할 수 있는 다른 이상한 하드웨어 장치를 저장해 왔습니다.

사실 그 날은 좀처럼 오지 않기 때문에 몇 달 전에 사용하지 않는 물건을 모두 없애기로 했습니다.

내가 처음에 '외부' 파일에 넣은 항목 중 하나는 3Dconnexion Spaceball 5000이었습니다. 어떤 OS에서도 지원이 거의 또는 전혀 없었기 때문입니다.

그러나 무슨 일이 일어날지 알아보기 위해 우분투 컴퓨터에 연결하려고 했습니다.



실행lsusb은 다음을 제공합니다.

ID 046d:c621 Logitech, Inc. 3Dconnexion Spaceball 5000


... -v 스위치를 추가하면 노출된 HID 인터페이스가 하나 있음을 알 수 있습니다.

...
        bInterfaceClass         3 Human Interface Device
...


참고: (Ubuntu) Linux를 사용하여 사용자 공간에서 액세스하려면 lsusb에서 udev 규칙 파일(예: /etc/udev/rules/50-webhid.rules )로 반환된 USB 공급업체 ID를 사용하여 다음을 추가하십시오.

SUBSYSTEM=="hidraw", ATTRS{idVendor}=="046d", MODE:="0666", GROUP="input"


그리고 달린다sudo udevadm control --reload-rules && sudo udevadm trigger
다른 시스템에서는 필요하지 않습니다.

웹HID



the Fugu API tracker 을 보면 크롬 M89에서 WebHID이 출시된 것을 볼 수 있으므로 연결을 시도하고 스페이스볼이 어떤 종류의 데이터를 보내는지 봅시다.

장치에서 오는 모든 데이터를 읽는 아주 간단한 것부터 시작하겠습니다.

<button id="scan">SCAN</button>
<script>
function scan() {
    navigator.hid.requestDevice({filters: [{ vendorId: 0x046d }]}).then(devices => {
        if (!devices.length) return;

        const device = devices[0];

        device.open().then(() => {
            console.log('Opened device: ' + device.productName);
            device.addEventListener('inputreport', e => {
                console.log('Report ID', e.reportId);
                console.log('Data', new Int8Array(e.data.buffer));
            });
        });
    });
}

document.querySelector('#scan').addEventListener('click', scan);
</script>


Chrome에서 이것을 실행하고 장치가 연결된 상태에서 SCAN 버튼을 클릭하면 이제 Spaceball 5000을 선택할 수 있는 대화 상자가 표시되어야 합니다.



성공적으로 연결되면 콘솔에 다음이 표시되어야 합니다.
Opened device: 3Dconnexion SpaceBall 5000 USB
예!

모든 데이터!



Spaceball을 조작하면 보고서 ID 1 및 2와 쌍을 이루는 것처럼 보이는 6바이트의 많은 데이터 패킷을 콘솔로 보냅니다.

Report ID 1
Data Int8Array(6) [101, -1, -2, -1, 95, 0]
Report ID 2
Data Int8Array(6) [-27, 0, 35, 1, -126, -1]


프로토콜 설명을 조금 검색한 후 2006년의 작은 값post을 읽었습니다. 값은 16비트 부호 있는 X, Y, Z 변환 값이고 회전에 대해서도 동일합니다.

코드를 약간 변경해 보겠습니다.

...
device.addEventListener('inputreport', e => {
    // First attempt: Print all the data
    // console.log('Report ID', e.reportId);
    // console.log('Data', new Int8Array(e.data.buffer));

    // Second attempt: extract translation and rotation:
    if (e.reportId === 1) console.log('T', new Int16Array(e.data.buffer));
    if (e.reportId === 2) console.log('R', new Int16Array(e.data.buffer));
});
...


이제 데이터가 좀 더 이해되기 시작합니다.

T Int16Array(3) [33, -18, 132]
R Int16Array(3) [-230, 67, 0]
T Int16Array(3) [39, -15, 124]
R Int16Array(3) [-208, 67, 0]


Spaceball을 놓으면 0이 되는 것 같습니다.

T Int16Array(3) [0, 0, 0]
R Int16Array(3) [0, 0, 0]


간단한 드라이버 만들기



이제 모든 기본 사항이 준비되었으므로 장치용 작은 웹 드라이버를 작성해 보겠습니다.

확장EventTarget은 드라이버가 구문 분석된 패킷을 적절한 이벤트로 수신기에 보낼 수 있는 기능을 제공합니다.

export const SpaceDriver = new class extends EventTarget {
    #device // Just allow one device, for now

    constructor() {
        super();

        this.handleInputReport = this.handleInputReport.bind(this);

        // See if a paired device is already connected
        navigator.hid.getDevices().then((devices) => {
            devices.filter(d => d.vendorId === deviceFilter.vendorId).forEach(this.openDevice.bind(this));
        });

        navigator.hid.addEventListener('disconnect', evt => {
            const device = evt.device;
            console.log('disconnected', device);
            if (device === this.#device) {
                this.disconnect();
            }
        });

    }

    openDevice(device) {
        this.disconnect(); // If another device is connected - close it

        device.open().then(() => {
            console.log('Opened device: ' + device.productName);
            device.addEventListener('inputreport', this.handleInputReport);
            this.#device = device;
            this.dispatchEvent(new CustomEvent('connect', {detail: { device }}));
        });
    }

    disconnect() {
        this.#device?.close();
        this.#device = undefined;
        this.dispatchEvent(new Event('disconnect'));
    }

    scan() {
        navigator.hid.requestDevice(requestParams).then(devices => {
            if (devices.length == 0) return;
            this.openDevice(devices[0]);
        });
    }

    handleInputReport(e) {
        switch(e.reportId) {
            case 1: // x, y, z
            this.handleTranslation(new Int16Array(e.data.buffer));
            break;
            case 2: // yaw, pitch, roll
            this.handleRotation(new Int16Array(e.data.buffer));
            break;
        }
    }

    handleTranslation(val) {
        this.dispatchEvent(new CustomEvent('translate', {
            detail: {
                x: val[0],
                y: val[1],
                z: val[2]
            }
        }));
    }

    handleRotation(val) {
        this.dispatchEvent(new CustomEvent('rotate', {
            detail: {
                rx: -val[0],
                ry: -val[1],
                rz: val[2]
            }
        }));
    }
}



웹 구성 요소 및 CSS3D로 시각화



콘솔에 숫자를 쓰는 것은 재미가 없으며 기본 CSS3D 및 웹 구성 요소를 지원하는 최신 브라우저를 사용하고 있으므로 구성 요소에서 어리석은 3D 개체를 만들어 Spaceball로 조작할 수 있습니다.

export class Demo3DObj extends HTMLElement {
    #objtranslate
    #objrotate
    #obj

    constructor() {
        super();
        this.#objtranslate = '';
        this.#objrotate = '';
    }

    connectedCallback() {
        this.innerHTML = `
        <style>
        .scene {
            width: 200px;
            height: 200px;
            margin: 200px;
            perspective: 500px;
        }

        .obj {
            width: 200px;
            height: 200px;
            position: relative;
            transform-style: preserve-3d;
            transform: translateZ(-1000px);
            transition: transform 100ms;
        }

        .plane {
            position: absolute;
            width: 200px;
            height: 200px;
            border: 5px solid black;
            border-radius: 50%;
        }

        .red {
            background: rgba(255,0,0,0.5);
            transform: rotateY(-90deg)
        }

        .green {
            background: rgba(0,255,0,0.5);
            transform: rotateX( 90deg)
        }

        .blue {
            background: rgba(0,0,255,0.5);
        }
        </style>

        <div class="scene">
            <div class="obj">
                <div class="plane red"></div>
                <div class="plane green"></div>
                <div class="plane blue"></div>
            </div>
        </div>
        `;

        this.#obj = this.querySelector('.obj');
    }

    setTranslation(x, y, z) {
        this.#objtranslate = `translateX(${x}px) translateY(${y}px) translateZ(${z}px) `;
        this.#obj.style.transform = this.#objtranslate + this.#objrotate;
    }

    setRotation(rx, ry, rz) {
        this.#objrotate = `rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg) `;
        this.#obj.style.transform = this.#objtranslate + this.#objrotate;
    }
}
customElements.define('demo-3dobj', Demo3DObj);


남은 것은 SpaceDriverdemo-3dobj를 결합하여 Spaceball 5000에 연결된 WebHID를 사용하여 3D 개체를 직접 조작하는 것을 보는 것입니다.



GitHub의 라이브 데모 및 소스 코드



Try it out here!

source

즐겨!

좋은 웹페이지 즐겨찾기