Реалізація Web USB API на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Web USB API на сайті
Складна
~2-3 робочих дні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація 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 днів.