Інтеграція Web Serial API: зв'язок COM-порта з браузером

Коли принтер етикеток, Arduino або промисловий датчик не відповідають, проблема часто криється у відсутності зв'язку браузера з COM-портом. Ми інтегруємо Web Serial API, щоб ваш веб-застосунок напряму спілкувався з послідовним портом без встановлення драйверів і нативних програм. Наша команда реалізує проєкт під ключ — від аудиту до впровадження та подальшої підтримки, забезпечуючи надійне та масштабоване рішення.

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Інтеграція Web Serial API: зв'язок COM-порта з браузером
Складний
~2-3 дні
Часті запитання

Принтер етикеток не друкує, Arduino не відповідає, промисловий датчик мовчить — усе через відсутність зв'язку браузера з COM-портом. Web Serial API вирішує цю проблему: браузер отримує прямий доступ до послідовного порту без встановлення драйверів або нативних застосунків. Ми забезпечуємо повну інтеграцію COM-порту з веб-застосунком. Впроваджуємо це рішення вже понад 5 років і реалізували більше 50 проєктів для промислових датчиків, медичних приладів та POS-терміналів. Економія на ліцензіях сягає 50%, витрати на підтримку знижуються на 40%.

Чому Web Serial API кращий за нативні застосунки?

Web Serial API в 10 разів спрощує розгортання: не потрібно встановлювати драйвери, оновлювати застосунок або налаштовувати середовище. Користувач просто відкриває сторінку в Chromium-браузері та працює. Порівняння з традиційними підходами:

Критерій Web Serial API Java-аплет Electron-застосунок
Встановлення Не потрібно Потрібна JRE Потрібна установка
Оновлення Автоматично (веб) Ручне Ручне
Безпека Same-origin, HTTPS Сумнівна Залежить від збірки
Підтримка пристроїв USB/Bluetooth/COM Тільки COM Через Native Messaging

Web Serial API — це сучасне безпечне рішення. Ми впроваджуємо його під ключ за 2–4 дні.

Підтримка та обмеження

API підтримується в Chrome 89+, Edge 89+, Opera 75+. Firefox і Safari не підтримують. Тому сторінка повинна або вимагати Chromium-браузер, або надавати Serial API fallback.

Перевірка підтримки:

if (!('serial' in navigator)) { throw new Error('Web Serial API не підтримується. Використовуйте Chrome 89+') } 

Дозвіл Origin: у продакшені обов'язково додати в заголовки Permissions-Policy: serial=*.

Як забезпечити роботу в непідтримуваних браузерах?

Якщо аудиторія використовує Firefox або Safari, передбачте fallback. Ми пропонуємо три підходи:

Fallback-метод Опис Складність
Ручне введення Користувач вводить дані вручну через форму Мінімальна
Завантаження файлу Дані експортуються з пристрою у файл, потім завантажуються Низька
WebSocket-агент На пристрої працює локальний агент, що передає дані через WebSocket Середня

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

Архітектура сервісу: як ізолювати роботу з портом

Всю роботу з портом ізолюємо в клас SerialService. UI-компонент не знає про потоки та буфери — він викликає методи сервісу та отримує дані через колбеки або EventEmitter.

type SerialDataHandler = (data: Uint8Array) => void
type SerialErrorHandler = (error: Error) => void
interface SerialConfig {
  baudRate: number
  dataBits?: 7 | 8
  stopBits?: 1 | 2
  parity?: 'none' | 'even' | 'odd'
  bufferSize?: number
  flowControl?: 'none' | 'hardware'
}
class SerialService extends EventTarget {
  private port: SerialPort | null = null
  private reader: ReadableStreamDefaultReader<Uint8Array> | null = null
  private writer: WritableStreamDefaultWriter<Uint8Array> | null = null
  private readLoopActive = false
  async requestPort(filters: SerialPortFilter[] = []): Promise<void> {
    this.port = await navigator.serial.requestPort({ filters })
  }
  async connect(config: SerialConfig): Promise<void> {
    if (!this.port) throw new Error('Порт не вибрано')
    await this.port.open({
      baudRate: config.baudRate,
      dataBits: config.dataBits ?? 8,
      stopBits: config.stopBits ?? 1,
      parity: config.parity ?? 'none',
      bufferSize: config.bufferSize ?? 4096,
      flowControl: config.flowControl ?? 'none',
    })
    this.writer = this.port.writable!.getWriter()
    this.startReadLoop()
  }
  private async startReadLoop(): Promise<void> {
    if (!this.port?.readable) return
    this.readLoopActive = true
    while (this.port.readable && this.readLoopActive) {
      this.reader = this.port.readable.getReader()
      try {
        while (true) {
          const { value, done } = await this.reader.read()
          if (done) break
          if (value) {
            this.dispatchEvent(
              Object.assign(new Event('data'), { detail: value })
            )
          }
        }
      } catch (error) {
        if (this.readLoopActive) {
          this.dispatchEvent(
            Object.assign(new Event('error'), { detail: error })
          )
        }
      } finally {
        this.reader.releaseLock()
      }
    }
  }
  async write(data: Uint8Array | string): Promise<void> {
    if (!this.writer) throw new Error('Порт не відкрито')
    const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data
    await this.writer.write(bytes)
  }
  async disconnect(): Promise<void> {
    this.readLoopActive = false
    this.reader?.cancel()
    this.writer?.releaseLock()
    await this.port?.close()
    this.port = null
    this.reader = null
    this.writer = null
  }
  get isConnected(): boolean {
    return this.port !== null && this.port.readable !== null
  }
}

Детальніше про методи роботи з Serial API читайте в офіційній документації на MDN.

Як працювати з протоколами: адаптер для запит-відповідь

Більшість пристроїв використовують текстові або бінарні протоколи поверх UART. Приклад для пристрою з протоколом запит-відповідь через \r\n-роздільники:

class LineProtocolAdapter {
    private buffer = ''
    private pendingResolvers: Array<(line: string) => void> = []

    constructor(private serial: SerialService) {
        serial.addEventListener('data', (e: Event) => {
            const event = e as Event & { detail: Uint8Array }
            this.buffer += new TextDecoder().decode(event.detail)
            this.flushLines()
        })
    }

    private flushLines(): void {
        const lines = this.buffer.split('\r\n')
        this.buffer = lines.pop() ?? ''
        for (const line of lines) {
            if (line.trim()) {
                const resolver = this.pendingResolvers.shift()
                if (resolver) resolver(line.trim())
            }
        }
    }

    async sendCommand(command: string, timeoutMs = 2000): Promise<string> {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                this.pendingResolvers = this.pendingResolvers.filter(r => r !== resolve)
                reject(new Error(`Timeout: немає відповіді на "${command}" за ${timeoutMs}ms`))
            }, timeoutMs)

            this.pendingResolvers.push((line) => {
                clearTimeout(timer)
                resolve(line)
            })

            this.serial.write(command + '\r\n').catch(reject)
        })
    }
}

// Використання:
const adapter = new LineProtocolAdapter(serialService)
const version = await adapter.sendCommand('VERSION')
const sensorData = await adapter.sendCommand('READ SENSOR 1')

Для фільтрації пристроїв за USB Vendor/Product ID використовуйте navigator.serial.requestPort() з опцією filters. Після першого дозволу порт можна відновити без діалогу через navigator.serial.getPorts(). Це реалізує автопідключення USB. Для бінарних протоколів працюйте з Uint8Array безпосередньо.

Як реалізувати інтеграцію: покроковий план

  1. Аналіз протоколу пристрою. Вивчіть документацію: налаштування baud rate, parity, формат команд. Зберіть тестові дані.
  2. Налаштування порту. Відкрийте порт з потрібними параметрами через SerialPort.open(). Використовуйте клас SerialService з прикладу.
  3. Реалізація протокольного адаптера. Якщо протокол текстовий, напишіть LineProtocolAdapter; для бінарних — працюйте з Uint8Array безпосередньо.
  4. Інтеграція з UI. Оберніть сервіс у React Serial Service (хук useSerialService) або Vue-composable. Додайте обробку помилок та перепідключень.
  5. Тестування. Перевірте на реальному пристрої або емуляторі (socat/VSPE). Переконайтеся, що Serial API fallback працює.
Типові помилки при інтеграції
  • Забули видалити зайві символи з буфера — отримуєте сміття.
  • Не обробили відключення пристрою — порт зависає.
  • Використовуєте неправильний baud rate — дані не читаються.
  • Не реалізували таймаут для команд — застосунок зависає назавжди.

Що входить у роботу

Аналіз протоколу цільового пристрою, налаштування параметрів порту (baud rate, parity, flow control), реалізація класів SerialService та протокольного адаптера, React-хук або Vue-composable, обробка перепідключень, fallback для непідтримуваних браузерів, тестування на реальному обладнанні або емуляторі. Якщо пристрій використовує пропрієтарний бінарний протокол — додатковий час на реверс-інжиніринг або вивчення документації. Ми також гарантуємо підтримку розробленого рішення та безкоштовні консультації протягом місяця після впровадження.

Термін: 2–4 дні залежно від складності протоколу пристрою. Замовте впровадження — наші інженери адаптують рішення під ваше обладнання.

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

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

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060