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







