WebUSB API는 USB를 웹에 제공하여 더 안전하고 사용하기 쉽게 만듭니다.
가볍게 'USB'라고 말했다면 키보드, 마우스, 오디오, 비디오, 저장 기기가 떠오르게 될 가능성이 높습니다. 맞습니다. 하지만 다른 종류의 범용 직렬 버스 (USB) 기기를 찾을 수 있습니다.
이러한 비표준 USB 기기의 경우 개발자 (개발자)가 이를 활용하려면 하드웨어 공급업체에서 플랫폼별 드라이버와 SDK를 작성해야 합니다. 안타깝게도 이 플랫폼별 코드로 인해 지금까지는 웹에서 이러한 기기를 사용할 수 없었습니다. 이는 WebUSB API가 만들어진 이유 중 하나입니다. USB 기기 서비스를 웹에 노출하는 방법을 제공하기 위해서입니다. 하드웨어 제조업체는 이 API를 사용하여 기기에 맞는 크로스 플랫폼 JavaScript SDK를 빌드할 수 있습니다.
하지만 무엇보다도 이렇게 하면 USB를 웹에 제공하여 더 안전하고 사용하기 쉽게 만들 수 있습니다.
WebUSB API를 사용하여 예상할 수 있는 동작을 살펴보겠습니다.
- USB 기기를 구매합니다.
- 컴퓨터에 연결합니다. 이 기기에 연결할 올바른 웹사이트와 함께 알림이 즉시 표시됩니다.
- 알림을 클릭합니다. 이제 웹사이트를 사용할 수 있습니다.
- 클릭하여 연결하면 Chrome에 기기를 선택할 수 있는 USB 기기 선택기가 표시됩니다.
짜잔!
WebUSB API가 없으면 이 절차는 어떻습니까?
- 플랫폼별 애플리케이션을 설치합니다.
- 내 운영체제에서도 지원되는 경우 올바른 파일을 다운로드했는지 확인합니다.
- 사물을 설치합니다. 운이 좋으면 인터넷에서 드라이버/애플리케이션을 설치하는 것과 관련해 경고하는 무서운 OS 메시지나 팝업이 표시되지 않습니다. 설치된 드라이버나 애플리케이션이 오작동하여 컴퓨터에 피해를 줄 수 있습니다. 단, 웹은 오작동하는 웹사이트를 포함하기 위해 구축됩니다.
- 이 기능을 한 번만 사용하면 코드가 삭제하려고 할 때까지 컴퓨터에 남아 있습니다. 웹에서는 사용되지 않은 공간이 결국 회수됩니다.
시작하기 전에
이 도움말에서는 사용자가 USB 작동 방식에 대한 기본 지식이 있다고 가정합니다. 아니라면 NutShell의 USB를 읽어보는 것이 좋습니다. USB에 관한 배경 정보는 공식 USB 사양을 참고하세요.
WebUSB API는 Chrome 61에서 사용할 수 있습니다.
오리진 트라이얼 사용 가능
현장에서 WebUSB API를 사용하는 개발자로부터 최대한 많은 의견을 얻기 위해 이전에 Chrome 54와 Chrome 57에 이 기능을 오리진 트라이얼로 추가했습니다.
최신 체험판이 2017년 9월에 종료되었습니다.
개인 정보 보호 및 보안
HTTPS 전용
이 기능은 보안 컨텍스트에서만 작동합니다. 즉, TLS를 염두에 두고 빌드해야 합니다.
사용자 동작 필요
보안 예방 조치로 navigator.usb.requestDevice()
는 터치 또는 마우스 클릭과 같은 사용자 동작을 통해서만 호출할 수 있습니다.
권한 정책
권한 정책은 개발자가 다양한 브라우저 기능 및 API를 선택적으로 사용 설정 및 중지할 수 있는 메커니즘입니다. HTTP 헤더 또는 iframe 'allow' 속성을 통해 정의할 수 있습니다.
usb
속성이 Navigator 객체에 노출되는지, 즉 WebUSB를 허용하는지 여부를 제어하는 권한 정책을 정의할 수 있습니다.
다음은 WebUSB가 허용되지 않는 헤더 정책의 예입니다.
Feature-Policy: fullscreen "*"; usb "none"; payment "self" https://payment.example.com
다음은 USB가 허용되는 컨테이너 정책의 또 다른 예입니다.
<iframe allowpaymentrequest allow="usb; fullscreen"></iframe>
코딩을 시작해 볼까요?
WebUSB API는 JavaScript 프로미스에 크게 의존합니다. 익숙하지 않다면 이 프로미스 튜토리얼을 확인하세요. 한 가지 더, () => {}
은 단순히 ECMAScript 2015 화살표 함수입니다.
USB 기기에 액세스
사용자에게 navigator.usb.requestDevice()
를 사용하여 연결된 단일 USB 기기를 선택하라는 메시지를 표시하거나 navigator.usb.getDevices()
를 호출하여 웹사이트에서 액세스 권한이 부여된 연결된 모든 USB 기기의 목록을 가져올 수 있습니다.
navigator.usb.requestDevice()
함수는 filters
를 정의하는 필수 JavaScript 객체를 사용합니다. 이러한 필터는 지정된 공급업체(vendorId
) 및 제품(productId
) 식별자(선택사항)와 모든 USB 기기를 일치시키는 데 사용됩니다.
classCode
, protocolCode
, serialNumber
, subclassCode
키도 여기서 정의할 수 있습니다.
예를 들어 다음은 출처를 허용하도록 구성된 연결된 Arduino 기기에 액세스하는 방법입니다.
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
})
.catch(error => { console.error(error); });
묻기도 전에 이 0x2341
16진수를 생각해 낸 적이 없습니다. USB ID 목록에서 'Arduino'를 검색해 봤습니다.
위의 처리된 프로미스에서 반환된 USB device
에는 지원되는 USB 버전, 최대 패킷 크기, 공급업체, 제품 ID, 기기에서 보유할 수 있는 구성 수와 같은 기기에 관한 기본적이지만 중요한 정보가 있습니다. 기본적으로 기기 USB 설명자의 모든 필드를 포함합니다.
// Get all connected USB devices the website has been granted access to.
navigator.usb.getDevices().then(devices => {
devices.forEach(device => {
console.log(device.productName); // "Arduino Micro"
console.log(device.manufacturerName); // "Arduino LLC"
});
})
USB 기기에서 WebUSB 지원을 발표하고 방문 페이지 URL을 정의하는 경우 USB 기기가 연결되면 Chrome에서 지속적인 알림을 표시합니다. 이 알림을 클릭하면 방문 페이지가 열립니다.
Arduino USB 보드에 연결
이제 USB 포트를 통해 WebUSB 호환 Arduino 보드에서 얼마나 쉽게 통신할 수 있는지 살펴보겠습니다. 스케치에서 WebUSB를 사용 설정하려면 https://github.com/webusb/arduino의 안내를 확인하세요.
걱정하지 마세요. 이 도움말의 뒷부분에서 설명하는 모든 WebUSB 기기 방법을 다루겠습니다.
let device;
navigator.usb.requestDevice({ filters: [{ vendorId: 0x2341 }] })
.then(selectedDevice => {
device = selectedDevice;
return device.open(); // Begin a session.
})
.then(() => device.selectConfiguration(1)) // Select configuration #1 for the device.
.then(() => device.claimInterface(2)) // Request exclusive control over interface #2.
.then(() => device.controlTransferOut({
requestType: 'class',
recipient: 'interface',
request: 0x22,
value: 0x01,
index: 0x02})) // Ready to receive data
.then(() => device.transferIn(5, 64)) // Waiting for 64 bytes of data from endpoint #5.
.then(result => {
const decoder = new TextDecoder();
console.log('Received: ' + decoder.decode(result.data));
})
.catch(error => { console.error(error); });
제가 사용 중인 WebUSB 라이브러리는 표준 USB 직렬 프로토콜을 기반으로 한 하나의 예시 프로토콜만 구현하며 제조업체는 원하는 모든 세트와 유형의 엔드포인트를 만들 수 있습니다. 제어 전송은 버스 우선순위를 얻고 구조가 잘 정의되어 있으므로 작은 구성 명령어에 특히 적합합니다.
다음은 Arduino 보드에 업로드된 스케치입니다.
// Third-party WebUSB Arduino library
#include <WebUSB.h>
WebUSB WebUSBSerial(1 /* https:// */, "webusb.github.io/arduino/demos");
#define Serial WebUSBSerial
void setup() {
Serial.begin(9600);
while (!Serial) {
; // Wait for serial port to connect.
}
Serial.write("WebUSB FTW!");
Serial.flush();
}
void loop() {
// Nothing here for now.
}
위의 샘플 코드에 사용된 서드 파티 WebUSB Arduino 라이브러리는 기본적으로 두 가지 작업을 실행합니다.
- 기기가 WebUSB 기기 역할을 하여 Chrome에서 방문 페이지 URL을 읽을 수 있도록 합니다.
- 이 API는 기본 API를 재정의하는 데 사용할 수 있는 WebUSB Serial API를 노출합니다.
JavaScript 코드를 다시 확인합니다. 사용자가 device
를 선택하면 device.open()
는 모든 플랫폼별 단계를 실행하여 USB 기기로 세션을 시작합니다. 그런 다음 device.selectConfiguration()
로 사용 가능한 USB 구성을 선택해야 합니다. 구성은 기기의 전원 공급 방식, 최대 전력 소모량, 인터페이스 수를 지정합니다.
인터페이스에 관해서도 device.claimInterface()
를 사용하여 독점 액세스를 요청해야 합니다. 인터페이스의 소유권을 주장한 경우에만 데이터를 인터페이스 또는 연결된 엔드포인트로 전송할 수 있기 때문입니다. 마지막으로 WebUSB Serial API를 통해 통신하기 위한 적절한 명령어로 Arduino 기기를 설정하려면 device.controlTransferOut()
를 호출해야 합니다.
여기서 device.transferIn()
는 기기로 일괄 전송을 실행하여 호스트가 대량 데이터를 수신할 준비가 되었음을 알립니다. 그런 다음
프로미스는 적절하게 파싱되어야 하는 DataView data
가
포함된 result
객체를 사용하여 처리됩니다.
USB에 익숙하다면 이 모든 것이 익숙할 것입니다.
더 보고 싶음
WebUSB API를 사용하면 모든 USB 전송/엔드포인트 유형과 상호작용할 수 있습니다.
- USB 기기로 구성 또는 명령어 매개변수를 전송하거나 수신하는 데 사용되는 CONTROL 전송은
controlTransferIn(setup, length)
및controlTransferOut(setup, data)
로 처리됩니다. - 소량의 민감한 정보에 사용되는 INTERRUPT 전송은
transferIn(endpointNumber, length)
및transferOut(endpointNumber, data)
를 사용한 일괄 전송과 동일한 메서드로 처리됩니다. - 동영상 및 사운드와 같은 데이터 스트림에 사용되는 ISOCHRONOUS 전송은
isochronousTransferIn(endpointNumber, packetLengths)
및isochronousTransferOut(endpointNumber, data, packetLengths)
로 처리됩니다. - 시간에 민감하지 않은 대량의 데이터를 안정적으로 전송하는 데 사용되는 BULK 전송은
transferIn(endpointNumber, length)
및transferOut(endpointNumber, data)
로 처리됩니다.
또한 마이크 차오의 WebLight 프로젝트에서 WebUSB API용으로 설계된 USB 제어 LED 기기 (여기서는 Arduino를 사용하지 않음)를 빌드하는 기초적인 예를 제공하는 것도 좋습니다. 하드웨어, 소프트웨어 및 펌웨어를 찾을 수 있습니다.
USB 기기에 대한 액세스 권한 취소
웹사이트는 USBDevice
인스턴스에서 forget()
를 호출하여 더 이상 필요하지 않은 USB 기기에 액세스하기 위한 권한을 정리할 수 있습니다. 예를 들어 여러 기기가 있는 공유 컴퓨터에서 사용되는 교육용 웹 애플리케이션의 경우 누적된 사용자 생성 권한이 많으면 사용자 환경이 저하됩니다.
// Voluntarily revoke access to this USB device.
await device.forget();
forget()
는 Chrome 101 이상에서 사용할 수 있으므로 다음에서 이 기능이 지원되는지 확인합니다.
if ("usb" in navigator && "forget" in USBDevice.prototype) {
// forget() is supported.
}
전송 크기 제한
일부 운영체제는 대기 중인 USB 트랜잭션에 포함될 수 있는 데이터 양에 제한을 적용합니다. 데이터를 더 작은 트랜잭션으로 분할하여 한 번에 몇 개만 제출하면 이러한 제한을 피할 수 있습니다. 또한 사용되는 메모리 양이 줄어들고 전송 완료 시 애플리케이션에서 진행 상황을 보고할 수 있습니다.
엔드포인트에 제출된 여러 전송은 항상 순서대로 실행되므로 큐에 추가된 청크를 여러 개 제출하여 처리량을 개선할 수 있습니다. 이렇게 하면 USB 전송 간의 지연 시간을 피할 수 있습니다. 청크가 완전히 전송될 때마다 아래 도우미 함수 예시에 설명된 대로 더 많은 데이터를 제공해야 함을 코드에 알립니다.
const BULK_TRANSFER_SIZE = 16 * 1024; // 16KB
const MAX_NUMBER_TRANSFERS = 3;
async function sendRawPayload(device, endpointNumber, data) {
let i = 0;
let pendingTransfers = [];
let remainingBytes = data.byteLength;
while (remainingBytes > 0) {
const chunk = data.subarray(
i * BULK_TRANSFER_SIZE,
(i + 1) * BULK_TRANSFER_SIZE
);
// If we've reached max number of transfers, let's wait.
if (pendingTransfers.length == MAX_NUMBER_TRANSFERS) {
await pendingTransfers.shift();
}
// Submit transfers that will be executed in order.
pendingTransfers.push(device.transferOut(endpointNumber, chunk));
remainingBytes -= chunk.byteLength;
i++;
}
// And wait for last remaining transfers to complete.
await Promise.all(pendingTransfers);
}
팁
USB 기기 관련 이벤트를 모두 한곳에서 확인할 수 있는 내부 페이지(about://device-log
)를 사용하면 Chrome에서 USB를 더 쉽게 디버깅할 수 있습니다.
내부 페이지 about://usb-internals
도 유용합니다. 이 페이지를 사용하면 가상 WebUSB 기기의 연결 및 연결 해제를 시뮬레이션할 수 있습니다.
이 기능은 실제 하드웨어를 사용하지 않고 UI를 테스트할 때 유용합니다.
대부분의 Linux 시스템에서 USB 기기는 기본적으로 읽기 전용 권한으로 매핑됩니다. Chrome에서 USB 기기를 열도록 허용하려면 새 udev 규칙을 추가해야 합니다. /etc/udev/rules.d/50-yourdevicename.rules
에서 다음 콘텐츠로 파일을 만듭니다.
SUBSYSTEM=="usb", ATTR{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"
예를 들어 기기가 Arduino인 경우 [yourdevicevendor]
은 2341
입니다.
ATTR{idProduct}
를 더 구체적인 규칙에 추가할 수도 있습니다. user
이 plugdev
그룹의 구성원인지 확인합니다. 그런 다음 기기를 다시 연결하세요.
자료
- 스택 오버플로: https://stackoverflow.com/questions/tagged/webusb
- WebUSB API 사양: http://wicg.github.io/webusb/
- Chrome 기능 상태: https://www.chromestatus.com/feature/5651917954875392
- 사양 문제: https://github.com/WICG/webusb/issues
- 구현 버그: http://crbug.com?q=component:Blink>USB
- WebUSB ❤ ️Arduino: https://github.com/webusb/arduino
- IRC: W3C IRC의 #webusb
- WICG 메일링 리스트: https://lists.w3.org/Archives/Public/public-wicg/
- WebLight 프로젝트: https://github.com/sowbug/weblight
해시태그 #WebUSB
를 사용하여 @ChromiumDev로 트윗을 보내고 사용 위치와 방법을 알려주세요.
감사의 말
이 도움말을 검토해 주신 Joe Medley님께 감사드립니다.