WebUSB API를 사용하면 USB를 웹으로 가져와 USB를 더 안전하고 쉽게 사용할 수 있습니다.
간단명료하게 'USB'라고 말하면 키보드, 마우스, 오디오, 비디오, 저장 장치가 바로 떠오를 것입니다. 맞습니다. 하지만 다른 종류의 범용 직렬 버스 (USB) 기기를 찾을 수 있습니다.
이러한 표준화되지 않은 USB 기기를 개발자가 활용하려면 하드웨어 공급업체가 플랫폼별 드라이버와 SDK를 작성해야 합니다. 안타깝게도 이 플랫폼별 코드로 인해 지금까지는 웹에서 이러한 기기를 사용할 수 없었습니다. WebUSB API가 만들어진 이유 중 하나는 USB 기기 서비스를 웹에 노출하는 방법을 제공하기 위함입니다. 이 API를 사용하면 하드웨어 제조업체가 기기용 크로스 플랫폼 JavaScript SDK를 빌드할 수 있습니다.
하지만 무엇보다도 이렇게 하면 USB를 웹으로 가져와 더욱 안전하고 쉽게 사용할 수 있게 됩니다.
WebUSB API에서 예상할 수 있는 동작을 살펴보겠습니다.
- USB 기기를 구매합니다.
- 컴퓨터에 연결합니다. 이 기기에 관해 이동할 올바른 웹사이트와 함께 알림이 즉시 표시됩니다.
- 알림을 클릭합니다. 웹사이트가 표시되고 사용할 준비가 되었습니다.
- 클릭하여 연결하면 Chrome에 USB 기기 선택기가 표시되며 여기에서 기기를 선택할 수 있습니다.
짜잔!
WebUSB API가 없으면 이 절차는 어떻게 진행되나요?
- 플랫폼별 애플리케이션을 설치합니다.
- 내 운영 체제에서도 지원하는 경우 올바른 앱을 다운로드했는지 확인합니다.
- 항목을 설치합니다. 운이 좋으면 인터넷에서 드라이버/애플리케이션 설치에 관해 경고하는 무서운 OS 메시지나 팝업이 표시되지 않습니다. 불운하면 설치된 드라이버나 애플리케이션이 오작동하여 컴퓨터에 해를 입힐 수 있습니다. (웹은 오작동하는 웹사이트를 포함하도록 빌드되었습니다.)
- 이 기능을 한 번만 사용하는 경우 코드는 삭제할 때까지 컴퓨터에 유지됩니다. 웹에서는 사용되지 않는 공간이 결국 재사용됩니다.
시작하기 전에
이 도움말에서는 USB 작동 방식에 대한 기본적인 지식이 있다고 가정합니다. 그렇지 않은 경우 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 객체를 사용합니다. 이 필터는 USB 기기를 지정된 공급업체 (vendorId
) 및 선택적으로 제품 (productId
) 식별자와 일치시키는 데 사용됩니다.
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 보드에서 통신하는 것이 얼마나 쉬운지 알아보겠습니다. https://github.com/webusb/arduino에서 안내를 확인하여 스케치에 WebUSB를 사용 설정하세요.
걱정하지 마세요. 이 문서의 뒷부분에서 아래에 언급된 모든 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 라이브러리는 기본적으로 두 가지 작업을 실행합니다.
- 이 기기는 Chrome에서 방문 페이지 URL을 읽을 수 있도록 하는 WebUSB 기기 역할을 합니다.
- 기본 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)
를 사용한 BULK 전송과 동일한 메서드로 처리됩니다. - 동영상 및 사운드와 같은 데이터 스트림에 사용되는 ISOCHRONOUS 전송은
isochronousTransferIn(endpointNumber, packetLengths)
및isochronousTransferOut(endpointNumber, data, packetLengths)
로 처리됩니다. - 시간에 민감하지 않은 대량의 데이터를 안정적인 방식으로 전송하는 데 사용되는 일괄 전송은
transferIn(endpointNumber, length)
및transferOut(endpointNumber, data)
로 처리됩니다.
WebUSB API용으로 설계된 USB 제어 LED 기기 (여기서는 Arduino를 사용하지 않음)를 빌드하는 기본 예를 제공하는 Mike Tsao의 WebLight 프로젝트도 살펴보는 것이 좋습니다. 하드웨어, 소프트웨어, 펌웨어가 표시됩니다.
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);
}
팁
Chrome의 USB 디버깅은 모든 USB 기기 관련 이벤트를 한곳에서 확인할 수 있는 about://device-log
내부 페이지를 통해 더 쉽게 디버깅됩니다.
내부 페이지 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"
여기서 [yourdevicevendor]
는 기기가 Arduino인 경우 2341
입니다.
더 구체적인 규칙을 위해 ATTR{idProduct}
를 추가할 수도 있습니다. user
이 plugdev
그룹의 구성원인지 확인합니다. 그런 다음 기기를 다시 연결합니다.
리소스
- Stack Overflow: 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에 트윗을 보내고 사용 위치와 사용 방법을 알려주세요.
감사의 말씀
이 도움말을 검토해 주신 조 미들리님께 감사드립니다.