שילוב Web Serial API לתקשורת עם יציאות COM בדפדפן

כאשר מדפסת תוויות, ארדואינו או חיישן תעשייתי לא מגיבים, הבעיה נעוצה פעמים רבות בחוסר תקשורת בין הדפדפן ליציאת ה-COM. אנחנו משלבים את Web Serial API כך שהיישום האינטרנטי שלך יוכל לתקשר ישירות עם היציאה הטורית ללא התקנת דרייברים או תוכנות מקומיות. הצוות שלנו מספק פרויקט סוהר—משלב הבדיקה ועד היישום והתמיכה השוטפת—ומבטיח פתרון אמין ובר-הרחבה.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
שילוב Web Serial API לתקשורת עם יציאות COM בדפדפן
מורכב
~2-3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1050
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

מדפסת התוויות לא מדפיסה, ארדואינו לא מגיב, חיישן תעשייתי שותק — כל זה בגלל היעדר חיבור בין הדפדפן ליציאת COM. Web Serial API פותר את זה: הדפדפן מקבל גישה ישירה ליציאה הטורית ללא התקנת דרייברים או יישומים מקומיים. אנו מספקים אינטגרציה מלאה של יציאת COM עם יישומי אינטרנט המיועדים לשליטה תעשייתית בציוד, סורקי ברקוד ופרויקטי אינטרנט של ארדואינו. אנו מיישמים פתרון זה כבר למעלה מ-5 שנים (מאז 2019) והשלמנו יותר מ-50 פרויקטים עבור חיישנים תעשייתיים, מכשור רפואי ומסופי POS. החיסכון ברישיונות מגיע ל-50%, עלויות התחזוקה מופחתות ב-40%. עלויות האינטגרציה מתחילות מ-800 דולר.

למה להשתמש ב-Web Serial API במקום ביישומים מקוריים?

זה מפשט את הפריסה פי 10: אין צורך להתקין דרייברים, אין עדכוני יישומים, אין צורך בהגדרת סביבה. המשתמש פשוט פותח דף בדפדפן Chromium ועובד. השוואה עם גישות מסורתיות:

קריטריון Web Serial API Java Applet יישום Electron
התקנה לא נדרשת נדרש JRE נדרשת התקנה
עדכון אוטומטי (אינטרנט) ידני ידני
אבטחה מקור זהה, HTTPS מוטלת בספק תלוי בבנייה
תמיכה במכשירים USB/Bluetooth/COM COM בלבד דרך Native Messaging

ה-API הוא פתרון מודרני ומאובטח. אנו מיישמים אותו במפתח מלא תוך 2–4 ימים.

תמיכת דפדפנים ומגבלות

ה-API נתמך ב-Chrome 89+ (מאז מרץ 2021), Edge 89+, Opera 75+. Firefox ו-Safari לא תומכים בו. לכן, הדף חייב לדרוש דפדפן Chromium או לספק חלופה ל-Serial API.

בדיקת תמיכה:

if (!('serial' in navigator)) { throw new Error('Web Serial API не поддерживается. Используйте Chrome 89+') } 

הרשאת מקור: בסביבת ייצור, יש להוסיף את הכותרת if (!('serial' in navigator)) { throw new Error('Web Serial API не поддерживается. Используйте Chrome 89+') } .

הבטחת פעולה בדפדפנים לא נתמכים

אם הקהל שלך משתמש ב-Firefox או Safari, ספק חלופה. אנו מציעים שלוש גישות:

שיטת חלופה תיאור מורכבות
קלט ידני המשתמש מזין נתונים ידנית דרך טופס מינימלית
העלאת קובץ נתונים מיוצאים מהמכשיר לקובץ, ואז מועלים נמוכה
סוכן WebSocket סוכן מקומי על המכשיר מעביר נתונים דרך WebSocket בינונית

הבחירה תלויה בסוג הנתונים ובדרישות האוטומציה. לדוגמה, עבור סורק ברקוד, העלאת קובץ מספיקה; עבור שליטה אינטראקטיבית בארדואינו, יישום האינטרנט ידרוש סוכן 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 בדפדפן. דוגמה למכשיר עם פרוטוקול בקשה-תגובה המשתמש במפרידים Permissions-Policy: serial=*:

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, השתמש ב-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 } } עם אפשרות \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. עבור פרוטוקולים בינאריים, עבוד ישירות עם navigator.serial.requestPort().

איך ליישם אינטגרציה: תוכנית שלב אחר שלב

  1. ניתוח פרוטוקול המכשיר. למד את התיעוד: הגדרת קצב באוד, זוגיות, פורמט פקודות. אסוף נתוני בדיקה.
  2. הגדר את היציאה. פתח את היציאה עם הפרמטרים הנדרשים דרך filters. השתמש במחלקת SerialService מהדוגמה.
  3. יישם את מתאם הפרוטוקול. עבור פרוטוקולים טקסטואליים, כתוב navigator.serial.getPorts(); עבור פרוטוקולים בינאריים, עבוד ישירות עם Uint8Array.
  4. אינטגרציה עם UI. עטוף את השירות ב-React Serial Service (הוק SerialPort.open()) או ב-Vue-composable. הוסף טיפול בשגיאות וחיבור מחדש.
  5. בדיקות. בדוק על מכשיר אמיתי או אמולטור (socat/VSPE). ודא שחלופת Serial API עובדת.
טעויות אינטגרציה נפוצות
  • שכחת לקצץ תווים מיותרים מהמאגר — מקבל ג'יבריש.
  • לא טיפלת בניתוק המכשיר — היציאה נתקעת.
  • שימוש בקצב באוד שגוי — הנתונים לא קריאים.
  • לא יישמת timeout לפקודות — היישום נתקע לנצח.

מה כלול בעבודה

  • ניתוח פרוטוקול של המכשיר היעד
  • הגדרת פרמטרי יציאה (הגדרת קצב באוד, זוגיות, בקרת זרימה)
  • יישום מחלקות SerialService ומתאם פרוטוקול
  • React hook או Vue-composable
  • טיפול בחיבור מחדש
  • חלופה לדפדפנים לא נתמכים
  • בדיקות על חומרה אמיתית או אמולטור

אם המכשיר משתמש בפרוטוקול בינארי קנייני, נדרש זמן נוסף להנדסה הפוכה או ללימוד התיעוד. אנו גם מבטיחים תמיכה בפתרון שפותח וייעוץ חינם לחודש אחד לאחר הפריסה.

לוח זמנים: 2–4 ימים בהתאם למורכבות פרוטוקול המכשיר. בצע הזמנה — המהנדסים שלנו יתאימו את הפתרון לציוד שלך. המחירים מתחילים ב-800 דולר לאינטגרציה סטנדרטית.