From a028b7843a2f5e5dbe4c0e3ee077afe8a20809b9 Mon Sep 17 00:00:00 2001 From: Lars Knudsen Date: Mon, 18 Sep 2017 21:15:35 +0200 Subject: [PATCH 1/2] Add definitions for W3C Web USB API --- types/w3c-web-usb/index.d.ts | 153 +++++++++++++++++++++++++ types/w3c-web-usb/tsconfig.json | 23 ++++ types/w3c-web-usb/tslint.json | 1 + types/w3c-web-usb/w3c-web-usb-tests.ts | 72 ++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 types/w3c-web-usb/index.d.ts create mode 100644 types/w3c-web-usb/tsconfig.json create mode 100644 types/w3c-web-usb/tslint.json create mode 100644 types/w3c-web-usb/w3c-web-usb-tests.ts diff --git a/types/w3c-web-usb/index.d.ts b/types/w3c-web-usb/index.d.ts new file mode 100644 index 0000000000..d481245e14 --- /dev/null +++ b/types/w3c-web-usb/index.d.ts @@ -0,0 +1,153 @@ +// Type definitions for W3C Web USB API 1.0 +// Project: https://wicg.github.io/webusb/ +// Definitions by: Lars Knudsen +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.1 + +type USBDirection = "in" | "out"; +type USBEndpointType = "bulk" | "interrupt" | "isochronous"; +type USBRequestType = "standard" | "class" | "vendor"; +type USBRecipient = "device" | "interface" | "endpoint" | "other"; +type USBTransferStatus = "ok" | "stall" | "babble"; + +interface USBEndpoint { + readonly endpointNumber: number; + readonly direction: USBDirection; + readonly type: USBEndpointType; + readonly packetSize: number; +} + +interface USBControlTransferParameters { + requestType: USBRequestType; + recipient: USBRecipient; + request: number; + value: number; + index: number; +} + +interface USBDeviceFilter { + vendorId?: number; + productId?: number; + classCode?: number; + subclassCode?: number; + protocolCode?: number; + serialNumber?: string; +} + +interface USBDeviceRequestOptions { + filters: USBDeviceFilter[]; +} + +interface USBConnectionEventInit extends EventInit { + device: USBDevice; +} + +declare class USBConfiguration { + readonly configurationValue: number; + readonly configurationName?: string; + readonly interfaces: USBInterface[]; +} + +declare class USBInterface { + constructor(configuration: USBConfiguration, interfaceNumber: number); + readonly interfaceNumber: number; + readonly alternate: USBAlternateInterface; + readonly alternates: USBAlternateInterface[]; + readonly claimed: boolean; +} + +declare class USBAlternateInterface { + constructor(deviceInterface: USBInterface, alternateSetting: number); + readonly alternateSetting: number; + readonly interfaceClass: number; + readonly interfaceSubclass: number; + readonly interfaceProtocol: number; + readonly alternatinterfaceName?: string; + readonly endpoints: USBEndpoint[]; +} + +declare class USBInTransferResult { + constructor(status: USBTransferStatus, data?: DataView); + readonly data?: DataView; + readonly status?: USBTransferStatus; +} + +declare class USBOutTransferResult { + constructor(status: USBTransferStatus, bytesWriten?: number); + readonly bytesWritten: number; + readonly status: USBTransferStatus; +} + +declare class USBIsochronousInTransferPacket { + constructor(status: USBTransferStatus, data?: DataView); + readonly data?: DataView; + readonly status?: USBTransferStatus; +} + +declare class USBIsochronousInTransferResult { + constructor(packets: USBIsochronousInTransferPacket[], data?: DataView); + readonly data?: DataView; + readonly packets: USBIsochronousInTransferPacket[]; +} + +declare class USBIsochronousOutTransferPacket { + constructor(status: USBTransferStatus, bytesWritten?: number); + readonly bytesWritten: number; + readonly status: USBTransferStatus; +} + +declare class USBConnectionEvent extends Event { + constructor(type: string, eventInitDict: USBConnectionEventInit); + readonly device: USBDevice; +} + +declare class USBIsochronousOutTransferResult { + constructor(packets: USBIsochronousOutTransferPacket[]); + readonly packets: USBIsochronousOutTransferPacket[]; +} + +declare class USB extends EventTarget { + onconnect(): (this: this, ev: Event) => any; + ondisconnect(): (this: this, ev: Event) => any; + getDevices(): Promise; + requestDevice(options?: USBDeviceRequestOptions): Promise; + + addEventListener(type: "connect" | "disconnect", listener: (this: this, ev: USBConnectionEvent) => any, useCapture?: boolean): void; +} + +declare class USBDevice { + readonly usbVersionMajor: number; + readonly usbVersionMinor: number; + readonly usbVersionSubminor: number; + readonly deviceClass: number; + readonly deviceSubclass: number; + readonly deviceProtocol: number; + readonly vendorId: number; + readonly productId: number; + readonly deviceVersionMajor: number; + readonly deviceVersionMinor: number; + readonly deviceVersionSubminor: number; + readonly manufacturerName?: string; + readonly productName?: string; + readonly serialNumber?: string; + readonly configuration?: USBConfiguration; + readonly configurations: USBConfiguration[]; + readonly opened: boolean; + open(): Promise; + close(): Promise; + selectConfiguration(configurationValue: number): Promise; + claimInterface(interfaceNumber: number): Promise; + releaseInterface(interfaceNumber: number): Promise; + selectAlternateInterface(interfaceNumber: number, alternateSetting: number): Promise; + controlTransferIn(setup: USBControlTransferParameters, length: number): Promise; + controlTransferOut(setup: USBControlTransferParameters, data?: BufferSource): Promise; + clearHalt(direction: USBDirection, endpointNumber: number): Promise; + transferIn(endpointNumber: number, length: number): Promise; + transferOut(endpointNumber: number, data: BufferSource): Promise; + isochronousTransferIn(endpointNumber: number, packetLengths: number[]): Promise; + isochronousTransferOut(endpointNumber: number, data: BufferSource, packetLengths: number[]): Promise; +} + +interface Navigator { + readonly usb: USB; +} diff --git a/types/w3c-web-usb/tsconfig.json b/types/w3c-web-usb/tsconfig.json new file mode 100644 index 0000000000..aebd30aa3d --- /dev/null +++ b/types/w3c-web-usb/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6", + "dom" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": false, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "w3c-web-usb-tests.ts" + ] +} diff --git a/types/w3c-web-usb/tslint.json b/types/w3c-web-usb/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/w3c-web-usb/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" } diff --git a/types/w3c-web-usb/w3c-web-usb-tests.ts b/types/w3c-web-usb/w3c-web-usb-tests.ts new file mode 100644 index 0000000000..8b8a1570fc --- /dev/null +++ b/types/w3c-web-usb/w3c-web-usb-tests.ts @@ -0,0 +1,72 @@ +// Modified from WebUSB spec: https://wicg.github.io/webusb/ + +let connectedDevices: USBDevice[] = null; + +document.addEventListener('DOMContentLoaded', async () => { + const devices = await navigator.usb.getDevices(); + devices.forEach(handleConnectedDevice); +}); + +let button = document.getElementById('request-device'); +button.addEventListener('click', async () => { + let device; + try { + device = await navigator.usb.requestDevice({ filters: [{ + vendorId: 0xABCD, + classCode: 0xFF, // vendor-specific + protocolCode: 0x01 + }]}); + } catch (e) { + // No device was selected. + } + + if (device !== undefined) { + // Add |device| to the UI. + handleConnectedDevice(device); + } +}); + +navigator.usb.addEventListener('connect', evt => { + // Add |device| to the UI. + handleConnectedDevice(evt.device); +}); + +navigator.usb.addEventListener('disconnect', evt => { + // Remove |device| from the UI. + const i = connectedDevices.indexOf(evt.device); + if (i >= 0) { + connectedDevices.splice(i, 1); + } +}); + +async function handleConnectedDevice(device: USBDevice) { + connectedDevices.push(device); + + await device.open(); + if (device.configuration === null) + await device.selectConfiguration(1); + await device.claimInterface(1); + + await device.controlTransferOut({ + requestType: 'vendor', + recipient: 'interface', + request: 0x01, // vendor-specific request: enable channels + value: 0x0013, // 0b00010011 (channels 1, 2 and 5) + index: 0x0001 // Interface 1 is the recipient + }); + + while (true) { + const result = await device.transferIn(1, 6); + + if (result.data && result.data.byteLength === 6) { + console.log('Channel 1: ' + result.data.getUint16(0)); + console.log('Channel 2: ' + result.data.getUint16(2)); + console.log('Channel 5: ' + result.data.getUint16(4)); + } + + if (result.status === 'stall') { + console.warn('Endpoint stalled. Clearing.'); + await device.clearHalt("in", 1); + } + } +} From e245db618be9b300d6a92b9be7bea19eb8f1aa1b Mon Sep 17 00:00:00 2001 From: Lars Knudsen Date: Tue, 19 Sep 2017 12:29:52 +0200 Subject: [PATCH 2/2] Minor fixes from review --- types/w3c-web-usb/tsconfig.json | 2 +- types/w3c-web-usb/w3c-web-usb-tests.ts | 39 ++++++++++++++------------ 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/types/w3c-web-usb/tsconfig.json b/types/w3c-web-usb/tsconfig.json index aebd30aa3d..4bd03116b6 100644 --- a/types/w3c-web-usb/tsconfig.json +++ b/types/w3c-web-usb/tsconfig.json @@ -7,7 +7,7 @@ ], "noImplicitAny": true, "noImplicitThis": true, - "strictNullChecks": false, + "strictNullChecks": true, "baseUrl": "../", "typeRoots": [ "../" diff --git a/types/w3c-web-usb/w3c-web-usb-tests.ts b/types/w3c-web-usb/w3c-web-usb-tests.ts index 8b8a1570fc..fe29480479 100644 --- a/types/w3c-web-usb/w3c-web-usb-tests.ts +++ b/types/w3c-web-usb/w3c-web-usb-tests.ts @@ -1,30 +1,33 @@ // Modified from WebUSB spec: https://wicg.github.io/webusb/ +// NOTE: Code kept as close to spec examples as possible -let connectedDevices: USBDevice[] = null; +const connectedDevices: USBDevice[] = []; document.addEventListener('DOMContentLoaded', async () => { const devices = await navigator.usb.getDevices(); devices.forEach(handleConnectedDevice); }); -let button = document.getElementById('request-device'); -button.addEventListener('click', async () => { - let device; - try { - device = await navigator.usb.requestDevice({ filters: [{ - vendorId: 0xABCD, - classCode: 0xFF, // vendor-specific - protocolCode: 0x01 - }]}); - } catch (e) { - // No device was selected. - } +const button = document.getElementById('request-device'); +if (button) { + button.addEventListener('click', async () => { + let device; + try { + device = await navigator.usb.requestDevice({ filters: [{ + vendorId: 0xABCD, + classCode: 0xFF, // vendor-specific + protocolCode: 0x01 + }]}); + } catch (e) { + // No device was selected. + } - if (device !== undefined) { - // Add |device| to the UI. - handleConnectedDevice(device); - } -}); + if (device !== undefined) { + // Add |device| to the UI. + handleConnectedDevice(device); + } + }); +} navigator.usb.addEventListener('connect', evt => { // Add |device| to the UI.