Ви інтегрували дорогий принтер етикеток на склад, але він працює тільки через нативний додаток під Windows. Кожне оновлення — завантаження інсталятора, кожен новий співробітник — встановлення драйверів. Це уповільнює процес розгортання та збільшує cost of ownership. Ми вирішуємо цю проблему: підключаємо Zebra, Arduino, POS-термінали та будь-які USB-пристрої безпосередньо в браузер через WebUSB API. Без драйверів, без додаткового ПЗ. Просто відкрили Chrome — і пристрій готовий до роботи.
Кейс із практики: для одного логістичного центру ми замінили нативний Windows-додаток на веб-інтерфейс з WebUSB. Десять принтерів Zebra тепер керуються з будь-якого пристрою в локальній мережі через браузер. Встановлення нового робочого місця скоротилося з 40 хвилин до 2 хвилин. Витрати на супровід впали на 70%. Подібний результат ми гарантуємо і для вашого обладнання.
Наш досвід: 6+ років інтеграції промислового обладнання, 30+ успішних проектів з WebUSB. Ми гарантуємо сумісність з Chrome 61+. Для початку отримайте консультацію по вашому пристрою — ми оцінимо можливість підключення за 1 день. Зв'яжіться з нами, щоб обговорити деталі.
Які пристрої підходять для WebUSB
WebUSB підходить для пристроїв, які не мають системного драйвера або мають спеціальну прошивку. Приклади: принтери етикеток (Zebra, TSC), Arduino з бібліотекою WebUSB, POS-термінали, сканери штрих-кодів, світлодіодні панелі, вимірювальні прилади. Важливе обмеження: пристрої HID (клавіатури, миші) та mass-storage (флешки) недоступні, оскільки їх драйвери вже зайняті ОС.
Чому WebUSB вигідніше нативних рішень?
| Критерій | WebUSB | Нативний додаток |
|---|---|---|
| Встановлення | Не потрібно | Потрібен інсталятор + драйвери |
| Оновлення | Автоматичне (браузер) | Ручне або через менеджер оновлень |
| Кроссплатформенність | Chrome/Edge на Windows/Mac/Android | Під кожну ОС окрема збірка |
| Безпека | Пісочниця браузера, user gesture | Повний доступ до системи |
| Розробка | Один код на TypeScript | C++/C#/Java, різні SDK |
WebUSB дає виграш у швидкості розгортання в 3 рази, а вартість розробки нижча в 2-3 рази. Економія на підтримці — до 70%. На відміну від нативних рішень, WebUSB не вимагає встановлення — достатньо відкрити браузер. Це особливо важливо для компаній з частою ротацією персоналу.
Які обмеження у WebUSB?
Підтримка: Chrome/Edge 61+ (десктоп і Android). Safari та Firefox — не підтримують. Тільки HTTPS. Тільки user gesture. USB-пристрої з активними ядерними USB-драйверами ОС (HID, мас-стораж, принтери з системними драйверами) — недоступні через WebUSB: браузер не перехоплює їх у ОС. Потрібен спеціальний firmware з WebUSB дескрипторами, або пристрій без автоматично встановлюваного драйвера.
Як працює підключення: від запиту до claim?
Технічна реалізація
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. Команди надсилаються як plain text через bulk transfer:
class ZebraPrinter { private device: USBDevice private interfaceNumber = 0 private endpointOut = 1 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> { 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> { await this.print('~HS') const result = await this.device.transferIn(1, 64) const decoder = new TextDecoder() return decoder.decode(result.data!) } } Приклад: Arduino (двосторонній обмін)
Arduino з кастомною прошивкою через WebUSB бібліотеку:
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) => { 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}`) }) }) }) } Це дозволяє знайти потрібні endpoint-номери без документації.
Детальніше про реверс-інжиніринг протоколу
Якщо документація на пристрій відсутня, ми використовуємо сніфер USB-трафіку (наприклад, Wireshark з usbmon) для захоплення команд. Після аналізу ми відновлюємо протокол і реалізуємо його на TypeScript. Цей метод вимагає доступу до еталонного пристрою та додатку.
Типові проблеми та їх вирішення
| Проблема | Причина | Рішення |
|---|---|---|
Пристрій не знайдено requestDevice() |
Невірні фільтри або активний системний драйвер | Перевірити vendorId/productId, замінити драйвер на WinUSB через Zadig |
SecurityError при виклику API |
Не HTTPS або не user gesture | Відкрити сторінку за HTTPS, викликати API після кліку |
NotFoundError після requestDevice |
Пристрій вже зайнятий іншим додатком | Закрити інші програми, що використовують USB |
NetworkError при transferIn |
Відключення пристрою або таймаут | Перепідключити пристрій, реалізувати повторну спробу |
Чек-лист діагностики:
- Перевірте
'usb' in navigator— має бути true. - Переконайтеся, що сторінка відкрита за HTTPS (або localhost).
- Запустіть
navigator.usb.requestDevice()тільки після кліку користувача. - Якщо пристрій не з'являється — відкрийте DevTools, подивіться на консоль на помилки.
- На Windows спробуйте замінити драйвер пристрою на WinUSB через Zadig.
Що входить в інтеграцію
- Аналітика: підбір фільтрів, вивчення протоколу, визначення endpoint'ів.
- Розробка прототипу: підключення, надсилання команд, прийом даних, обробка помилок.
- Тестування: на цільових ОС (Windows, macOS, Android), перевірка відключення та повторного підключення.
- Документація: опис схеми взаємодії, приклади коду.
- Навчання: інструкція для користувачів, рекомендації з налаштування драйверів (WinUSB).
Замовте інтеграцію WebUSB під ключ — ми підготуємо прототип за 5 днів. Отримайте консультацію по вашому пристрою прямо зараз.







