שילוב WebUSB: מדפסות, סורקים וארדואינו בדפדפן

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

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

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

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

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
שילוב WebUSB: מדפסות, סורקים וארדואינו בדפדפן
מורכב
~2-3 ימים

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

שאלות נפוצות

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

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

שילבת מדפסת תוויות יקרה במחסן שלך, אבל היא עובדת רק דרך יישום Windows מקורי. כל עדכון דורש הורדת מתקין, כל עובד חדש צריך התקנת דרייבר. זה מאט את הפריסה ומעלה את עלות הבעלות הכוללת. עם ניסיון של למעלה מ-6 שנים ו-30+ פרויקטי WebUSB מוצלחים, אנחנו פותרים את זה: אנחנו מחברים Zebra, Arduino, מסופי POS וכל התקני USB ישירות בדפדפן דרך WebUSB API (MDN). ללא דרייברים, ללא תוכנה נוספת. פשוט פתחו את Chrome וההתקן מוכן.

מקרה בוחן: עבור מרכז לוגיסטי אחד, החלפנו יישום Windows מקורי בממשק אינטרנט מבוסס WebUSB. עשר מדפסות Zebra מנוהלות כעת מכל התקן ברשת המקומית דרך הדפדפן. זמן הגדרת תחנת עבודה חדשה ירד מ-40 דקות ל-2 דקות — שיפור של פי 20. עלויות התמיכה ירדו ב-70%. אנחנו מבטיחים תוצאות דומות עבור הציוד שלך.

הניסיון שלנו: 6+ שנים בשילוב ציוד תעשייתי, 30+ פרויקטים מוצלחים. אנחנו מבטיחים תאימות ל-Chrome 61+. כדי להתחיל, קבלו ייעוץ על ההתקן שלכם — אנו מעריכים קישוריות תוך יום אחד. צרו קשר כדי לדון בפרטים. עלויות אינטגרציה טיפוסיות מתחילות ב-$2,000, ולקוחות חוסכים בממוצע $50,000 בשנה על תמיכה.

אילו התקנים מתאימים ל-WebUSB

WebUSB מתאים להתקנים שאין להם דרייבר מערכת או שיש להם קושחה ייעודית. דוגמאות: מדפסות תוויות (Zebra, TSC), Arduino עם ספריית WebUSB, מסופי POS, סורקי ברקוד, פאנלי LED, מכשירי מדידה. מגבלה חשובה: התקני HID (מקלדות, עכברים) והתקני אחסון המוני (כונני הבזק) אינם נגישים מכיוון שהדרייברים שלהם כבר נתבעו על ידי מערכת ההפעלה.

מדוע WebUSB עדיף על פתרונות מקוריים?

קריטריון WebUSB יישום מקורי
התקנה לא נדרשת דורש מתקין + דרייברים
עדכונים אוטומטיים (דפדפן) ידני או דרך מנהל עדכונים
רוחב פלטפורמות Chrome/Edge ב-Windows, Mac, Android בנייה נפרדת לכל מערכת הפעלה
אבטחה ארגז חול של דפדפן, מחוות משתמש גישה מלאה למערכת
פיתוח קוד אחד ב-TypeScript C++/C#/Java, SDK שונים

WebUSB מהיר פי 3 לפריסה וזול פי 2-3 לפיתוח מאשר יישומים מקוריים. חיסכון בעלויות תמיכה עד 70%. זה חשוב במיוחד לחברות עם תחלופת עובדים גבוהה.

מהן המגבלות של WebUSB?

תמיכה: Chrome/Edge 61+ (שולחני ו-Android). Safari ו-Firefox — לא נתמכים. HTTPS בלבד. רק לאחר מחוות משתמש. התקני USB עם דרייברי ליבה פעילים (HID, אחסון המוני, מדפסות עם דרייברי מערכת) אינם נגישים. נדרשת קושחה מיוחדת עם מתארי 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. פקודות נשלחות כטקסט פשוט דרך העברת bulk:

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 hook עבור 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. שיטה זו דורשת גישה להתקן ייחוס וליישום.

בעיות טיפוסיות ופתרונותיהן

בעיה סיבה פתרון
ההתקן לא נמצא על ידי 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}`) } מסננים שגויים או דרייבר מערכת פעיל בדקו vendorId/productId, החליפו דרייבר ב-WinUSB דרך Zadig
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!) } } בקריאת API לא HTTPS או לא מחוות משתמש פתחו את הדף דרך HTTPS, קראו ל-API לאחר לחיצה
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') אחרי 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}`) }) }) }) } על requestDevice() ההתקן מנותק או פסק זמן חברו מחדש את ההתקן, יישמו לוגיקת ניסיון חוזר

רשימת בדיקות לאבחון:

  1. בדקו SecurityError — חייב להיות true.
  2. ודאו שהדף מוגש דרך HTTPS (או localhost).
  3. קראו ל-NotFoundError רק לאחר לחיצת משתמש.
  4. אם ההתקן לא מופיע — פתחו את DevTools ובדקו את הקונסול לשגיאות.
  5. ב-Windows, נסו להחליף את דרייבר ההתקן ב-WinUSB באמצעות Zadig.

מה כלול באינטגרציה

  • ניתוח: בחירת מסננים, חקר פרוטוקול, זיהוי endpoints.
  • פיתוח אב-טיפוס: חיבור, שליחת פקודות, קבלת נתונים, טיפול בשגיאות.
  • בדיקות: על מערכות הפעלה יעד (Windows, macOS, Android), אימות ניתוק וחיבור מחדש.
  • תיעוד: תיאור סכמת אינטראקציה, דוגמאות קוד.
  • הדרכה: הוראות משתמש, המלצות להגדרת דרייבר (WinUSB).

הזמינו אינטגרציית WebUSB סוהר — נכין אב-טיפוס תוך 5 ימים. קבלו ייעוץ על ההתקן שלכם כבר עכשיו. עם 6+ שנים בשוק ולמעלה מ-30 פרויקטים, אנחנו מספקים תוצאות. התקשרות טיפוסית חוסכת 200 שעות תחזוקת IT בחודש.

כדי להבטיח שאינטגרציית USB בדפדפן תעבוד בצורה חלקה, אנו בודקים עם Chrome WebUSB ומאמתים תאימות WebUSB בכל ה-endpoints. דוגמאות ה-Zebra WebUSB שלנו מראות כיצד לחבר מדפסת דרך הדפדפן. עבור Arduino WebUSB, מודגמת תקשורת דו-כיוונית. תהליך ההנדסה לאחור של פרוטוקול USB מפורט לעיל. החליפו את היישום המקורי שלכם בפתרון מבוסס אינטרנט היום.