Ви інтегрували дорогий принтер етикеток на склад, але він працює тільки через нативний додаток під 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 днів. Отримайте консультацію по вашому пристрою прямо зараз.







