Реалізація Web USB API на сайті
WebUSB API відкриває доступ до USB-пристроїв прямо з браузера — без драйверів, без нативної програми. Принтери етикеток, POS-термінали, промислові сканери, Arduino-пристрої, програматори мікроконтролерів — все це підключається до веб-інтерфейсу безпосередньо.
Обмеження та реальність
Підтримка: Chrome/Edge 61+ (десктоп та Android). Safari та Firefox — не підтримують.
Тільки HTTPS. Тільки після користувацького жесту. USB-пристрої з активними ОС USB-драйверами (HID, масс-стораж, принтери з системними драйверами) — недоступні через WebUSB: браузер не перехоплює їх у ОС. Потрібен спеціальний firmware з WebUSB дескрипторами або пристрій без автоматично встановлювального драйвера.
Запит та підключення пристрою
interface USBDeviceInfo {
vendorId: number // з документації виробника
productId: number
}
async function requestUSBDevice(filters: USBDeviceInfo[]): Promise<USBDevice> {
const device = await navigator.usb.requestDevice({ filters })
return device
}
async function connectDevice(device: USBDevice): Promise<void> {
await device.open()
// Якщо пристрій має кілька конфігурацій — вибираємо потрібну
if (device.configuration === null) {
await device.selectConfiguration(1)
}
// Захоплюємо інтерфейс (номер з документації або USB descriptor)
await device.claimInterface(0)
console.log(`Підключено: ${device.manufacturerName} ${device.productName}`)
console.log(`USB ${device.usbVersionMajor}.${device.usbVersionMinor}`)
}
Принтер етикеток ZPL (Zebra/аналоги)
Принтери Zebra використовують ZPL (Zebra Programming Language). Команди відправляються як простий текст через bulk transfer:
class ZebraPrinter {
private device: USBDevice
private interfaceNumber = 0
private endpointOut = 1 // bulk OUT endpoint — з USB descriptor пристрою
constructor(device: USBDevice) {
this.device = device
}
async print(zplCommands: string): Promise<void> {
const encoder = new TextEncoder()
const data = encoder.encode(zplCommands)
const result = await this.device.transferOut(this.endpointOut, data)
if (result.status !== 'ok') {
throw new Error(`Print error: ${result.status}`)
}
}
async printLabel(params: {
barcode: string
title: string
price: string
sku: string
}): Promise<void> {
// ZPL розмітка етикетки 60x40mm
const zpl = `
^XA
^CI28
^FO20,10^A0N,24,24^FD${params.title}^FS
^FO20,40^BY2^BCN,50,Y,N,N^FD${params.barcode}^FS
^FO20,100^A0N,20,20^FDSKU: ${params.sku}^FS
^FO20,125^A0N,28,28^FD${params.price}^FS
^XZ
`.trim()
await this.print(zpl)
}
async getStatus(): Promise<string> {
// Host Status Command
await this.print('~HS')
// Читаємо відповідь з bulk IN endpoint
const result = await this.device.transferIn(1, 64) // endpoint IN, 64 bytes
const decoder = new TextDecoder()
return decoder.decode(result.data!)
}
}
Arduino: двусторонній обмін даними
Arduino з CDC firmware працює як USB Serial. Але без CDC-драйвера ОС — кастомний підхід через WebUSB бібліотеку для Arduino:
class ArduinoDevice {
private device: USBDevice
private interfaceNumber = 2
private endpointIn = 5
private endpointOut = 4
private decoder = new TextDecoder()
private encoder = new TextEncoder()
private readBuffer = ''
private isReading = false
constructor(device: USBDevice) {
this.device = device
}
async startReading(onData: (line: string) => void) {
this.isReading = true
while (this.isReading) {
try {
const result = await this.device.transferIn(this.endpointIn, 64)
const chunk = this.decoder.decode(result.data!, { stream: true })
this.readBuffer += chunk
const lines = this.readBuffer.split('\n')
this.readBuffer = lines.pop()! // Останній елемент — неповна строка
for (const line of lines) {
if (line.trim()) onData(line.trim())
}
} catch (err) {
if ((err as Error).name === 'NetworkError') break // Пристрій відключився
throw err
}
}
}
async sendCommand(command: string): Promise<void> {
const data = this.encoder.encode(command + '\n')
await this.device.transferOut(this.endpointOut, data)
}
stopReading() {
this.isReading = false
}
}
// Використання
const arduino = new ArduinoDevice(device)
arduino.startReading((line) => {
// Парсимо дані датчика: "TEMP:23.5,HUM:65.2"
const match = line.match(/TEMP:([\d.]+),HUM:([\d.]+)/)
if (match) {
setSensorData({ temp: parseFloat(match[1]), humidity: parseFloat(match[2]) })
}
})
await arduino.sendCommand('LED:ON')
await arduino.sendCommand('SERVO:90')
React-хук для WebUSB
function useWebUSB() {
const [device, setDevice] = useState<USBDevice | null>(null)
const [isConnected, setIsConnected] = useState(false)
const [isSupported] = useState(() => 'usb' in navigator)
useEffect(() => {
if (!isSupported) return
function onConnect(event: USBConnectionEvent) {
console.log('USB підключено:', event.device.productName)
}
function onDisconnect(event: USBConnectionEvent) {
if (event.device === device) {
setDevice(null)
setIsConnected(false)
}
}
navigator.usb.addEventListener('connect', onConnect)
navigator.usb.addEventListener('disconnect', onDisconnect)
return () => {
navigator.usb.removeEventListener('connect', onConnect)
navigator.usb.removeEventListener('disconnect', onDisconnect)
}
}, [device, isSupported])
// Переподключення до раніше авторизованих пристроїв (без діалогу)
async function reconnectPaired() {
const devices = await navigator.usb.getDevices()
if (devices.length > 0) {
const dev = devices[0]
await connectDevice(dev)
setDevice(dev)
setIsConnected(true)
}
}
return { device, isConnected, isSupported, reconnectPaired }
}
Читання USB-дескриптора
Коли документації немає — читаємо дескриптори пристрою напрямки:
function inspectDevice(device: USBDevice) {
console.log('Vendor ID:', device.vendorId.toString(16))
console.log('Product ID:', device.productId.toString(16))
device.configuration?.interfaces.forEach((iface) => {
console.log(`\nInterface ${iface.interfaceNumber}:`)
iface.alternates.forEach((alt) => {
alt.endpoints.forEach((ep) => {
console.log(` Endpoint ${ep.endpointNumber}: ${ep.direction} ${ep.type}, packet size: ${ep.packetSize}`)
})
})
})
}
Це дозволяє знайти потрібні номери endpoints без документації.
Що ми робимо
Отримуємо Vendor ID та Product ID пристрою, вивчаємо документацію на протокол або робимо реверс-інжиніринг. Реалізуємо підключення, отправку команд та прийом даних, обробку відключення, UI статусу. Тестуємо на цільових ОС (Windows вимагає особливої уваги з WinUSB-драйверами).
Строк: інтеграція з задокументованим USB-пристроєм — 3–5 днів. Реверс-інжиніринг протоколу — 8–14 днів.







