Интеграция 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