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







