القراءة من منفذ تسلسلي والكتابة فيه

تسمح Web Serial API للمواقع الإلكترونية بالاتصال بالأجهزة التسلسلية.

François Beaufort
François Beaufort

ما هي Web Serial API؟

المنفذ التسلسلي هو واجهة اتصال ثنائية الاتجاه تسمح بإرسال واستقبال بايت البيانات.

توفّر Web Serial API طريقةً للمواقع الإلكترونية للقراءة من جهاز تسلسلي والكتابة إليه باستخدام JavaScript. يتم توصيل الأجهزة التسلسلية إما من خلال منفذ تسلسلي على نظام المستخدم أو من خلال USB وأجهزة بلوتوث قابلة للإزالة تحاكي منفذًا تسلسليًا.

بعبارة أخرى، تعمل واجهة برمجة التطبيقات Web Serial API على الربط بين الويب والعالم من خلال السماح للمواقع الإلكترونية بالاتصال بالأجهزة التسلسلية، مثل وحدات التحكم الدقيقة والطابعات ثلاثية الأبعاد.

تشكّل واجهة برمجة التطبيقات هذه أيضًا رفيقًا رائعًا لـ WebUSB، وذلك لأنّ أنظمة التشغيل تتطلب من التطبيقات الاتصال ببعض المنافذ التسلسلية باستخدام واجهة برمجة التطبيقات التسلسلية الأعلى مستوى بدلاً من واجهة برمجة تطبيقات USB المنخفضة المستوى.

حالات الاستخدام المقترَحة

في القطاعات التعليمية والهواة والصناعية، يقوم المستخدمون بتوصيل الأجهزة الملحقة بأجهزة الكمبيوتر الخاصة بهم. غالبًا ما يتم التحكم في هذه الأجهزة بواسطة وحدات التحكم الدقيقة عبر اتصال تسلسلي تستخدمه البرامج المخصصة. تم إنشاء بعض البرامج المخصصة للتحكم في هذه الأجهزة باستخدام تقنية الويب:

في بعض الحالات، تتواصل مواقع الويب مع الجهاز من خلال تطبيق وكيل يقوم المستخدمون بتثبيته يدويًا. وفي حالات أخرى، يتم تسليم التطبيق في تطبيق حزمة من خلال إطار عمل مثل Electron. وفي حالات أخرى، يُطلب من المستخدم تنفيذ خطوة إضافية مثل نسخ تطبيق مجمّع إلى الجهاز من خلال محرك أقراص USB.

في جميع هذه الحالات، سيتم تحسين تجربة المستخدم من خلال توفير اتصال مباشر بين موقع الويب والجهاز الذي يتحكم فيه.

الوضع الحالي

الخطوة الحالة
1- إنشاء شرح مكتمل
2. إنشاء مسودة أولية للمواصفات مكتمل
3- جمع الملاحظات والتكرار التحسيني للتصميم مكتمل
4. مرحلة التجربة والتقييم مكتمل
‫5. الإطلاق مكتمل

استخدام Web Serial API

رصد الميزات

للتحقّق مما إذا كانت واجهة Web Serial API متوافقة، استخدِم ما يلي:

if ("serial" in navigator) {
  // The Web Serial API is supported.
}

فتح منفذ تسلسلي

واجهة برمجة التطبيقات Web Serial API غير متزامنة حسب التصميم. يمنع هذا واجهة المستخدم الخاصة بموقع الويب من الحظر عند انتظار الإدخال، وهو أمر مهم لأنّه يمكن استقبال البيانات التسلسلية في أي وقت، ما يتطلب طريقة للاستماع إليها.

لفتح منفذ تسلسلي، عليك أولاً الوصول إلى عنصر SerialPort. لإجراء ذلك، يمكنك إمّا أن تطلب من المستخدم اختيار منفذ تسلسلي واحد من خلال استدعاء navigator.serial.requestPort() استجابةً لإيماءة مستخدم، مثلاً النقر بالنقر أو الماوس، أو اختيار منفذ من navigator.serial.getPorts() لعرض قائمة بالمنافذ التسلسلية التي تم منح الموقع الإلكتروني إذن الوصول إليها.

document.querySelector('button').addEventListener('click', async () => {
  // Prompt user to select any serial port.
  const port = await navigator.serial.requestPort();
});
// Get all serial ports the user has previously granted the website access to.
const ports = await navigator.serial.getPorts();

تستخدم الدالة navigator.serial.requestPort() كائنًا اختياريًا حرفيًا يحدد عوامل التصفية. وتُستخدَم هذه المعرّفات لمطابقة أي جهاز تسلسلي متصل عبر USB مع مورّد USB إلزامي (usbVendorId) ومعرّفات منتجات USB الاختيارية (usbProductId).

// Filter on devices with the Arduino Uno USB Vendor/Product IDs.
const filters = [
  { usbVendorId: 0x2341, usbProductId: 0x0043 },
  { usbVendorId: 0x2341, usbProductId: 0x0001 }
];

// Prompt user to select an Arduino Uno device.
const port = await navigator.serial.requestPort({ filters });

const { usbProductId, usbVendorId } = port.getInfo();
لقطة شاشة لطلب منفذ تسلسلي على موقع إلكتروني
طلب من المستخدم لاختيار ملف BBC micro:bit

يؤدي طلب الرمز requestPort() إلى الطلب من المستخدم اختيار جهاز وعرض عنصر SerialPort. بعد إنشاء كائن SerialPort، سيؤدي استدعاء port.open() بمعدّل الباود المطلوب إلى فتح المنفذ التسلسلي. يحدّد عضو قاموس baudRate مدى سرعة إرسال البيانات على سطر تسلسلي. ويتم التعبير عنه بوحدات بت في الثانية (bps). تحقق من وثائق جهازك بحثًا عن القيمة الصحيحة لأن جميع البيانات التي ترسلها وتتلقاها ستكون غير مفهومة في حال تحديد ذلك بشكل غير صحيح. بالنسبة إلى بعض أجهزة USB والبلوتوث التي تحاكي منفذًا تسلسليًا، قد يتم ضبط هذه القيمة بأمان على أي قيمة، حيث يتم تجاهلها من خلال عملية المحاكاة.

// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();

// Wait for the serial port to open.
await port.open({ baudRate: 9600 });

يمكنك أيضًا تحديد أي من الخيارات أدناه عند فتح منفذ تسلسلي. هذه الخيارات اختيارية وتتضمن قيمًا تلقائية ملائمة.

  • dataBits: عدد وحدات بت البيانات لكل إطار (إما 7 أو 8).
  • stopBits: عدد وحدات بت الإيقاف في نهاية الإطار (إما 1 أو 2).
  • parity: وضع التكافؤ (إما "none" أو "even" أو "odd").
  • bufferSize: حجم الموارد الاحتياطية للقراءة والكتابة التي يجب إنشاؤها (يجب أن يكون حجمها أقل من 16 ميغابايت)
  • flowControl: وضع التحكم في التدفق (إما "none" أو "hardware").

القراءة من منفذ تسلسلي

تتم معالجة عمليات بث الإدخال والإخراج في Web Serial API من خلال Streams API.

بعد إنشاء اتصال المنفذ التسلسلي، تعرض السمتان readable وwritable من الكائن SerialPort مصدرَي ReadableStream وWritableStream. سيتم استخدام تلك الأدوات لتلقّي البيانات من الجهاز التسلسلي وإرسال البيانات إليه. يستخدم كلاهما Uint8Array مثيل لنقل البيانات.

عند وصول بيانات جديدة من الجهاز التسلسلي، يعرض port.readable.getReader().read() خاصيتين بشكل غير متزامن: value وdone. إذا كانت قيمة done صحيحة، يعني ذلك أنه تم إغلاق المنفذ التسلسلي أو لن تصلك أي بيانات أخرى. يؤدي الاتصال بـ port.readable.getReader() إلى إنشاء قارئ وإقفال readable أمامه. عندما يكون readable مقفلًا، لا يمكن إغلاق المنفذ التسلسلي.

const reader = port.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

يمكن أن تحدث بعض أخطاء قراءة المنافذ التسلسلية غير الفادحة في بعض الحالات، مثل فائض سعة المخزن المؤقت أو أخطاء الإطارات أو أخطاء التكافؤ. يتم طرح هذه المشاكل باعتبارها استثناءات ويمكن رصدها من خلال إضافة حلقة تكرار أخرى أعلى الحلقة السابقة التي تتحقق من port.readable. هذه الطريقة مناسبة لأنّه يتم إنشاء ReadableStream تلقائيًا ما دامت الأخطاء غير فادحة. في حال حدوث خطأ جسيم، مثل إزالة الجهاز التسلسلي، يصبح port.readable فارغًا.

while (port.readable) {
  const reader = port.readable.getReader();

  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) {
        // Allow the serial port to be closed later.
        reader.releaseLock();
        break;
      }
      if (value) {
        console.log(value);
      }
    }
  } catch (error) {
    // TODO: Handle non-fatal read error.
  }
}

إذا أرسل الجهاز التسلسلي النص مرة أخرى، يمكنك توجيه port.readable عبر TextDecoderStream كما هو موضّح أدناه. TextDecoderStream عبارة عن تدفق تحويل يجمع كل مقاطع Uint8Array وتحوّلها إلى سلاسل.

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a string.
  console.log(value);
}

يمكنك التحكّم في كيفية تخصيص الذاكرة عند القراءة من البث باستخدام قارئ "جلب المخزن المؤقت الخاص بك". يمكنك الاتصال بـ port.readable.getReader({ mode: "byob" }) للحصول على واجهة ReadableStreamBYOBReader وتقديم ArrayBuffer الخاص بك عند الاتصال بـ read(). يُرجى العِلم أنّ Web Serial API تتوافق مع هذه الميزة في الإصدار 106 من Chrome أو الإصدارات الأحدث.

try {
  const reader = port.readable.getReader({ mode: "byob" });
  // Call reader.read() to read data into a buffer...
} catch (error) {
  if (error instanceof TypeError) {
    // BYOB readers are not supported.
    // Fallback to port.readable.getReader()...
  }
}

في ما يلي مثال على كيفية إعادة استخدام المورد الاحتياطي من value.buffer:

const bufferSize = 1024; // 1kB
let buffer = new ArrayBuffer(bufferSize);

// Set `bufferSize` on open() to at least the size of the buffer.
await port.open({ baudRate: 9600, bufferSize });

const reader = port.readable.getReader({ mode: "byob" });
while (true) {
  const { value, done } = await reader.read(new Uint8Array(buffer));
  if (done) {
    break;
  }
  buffer = value.buffer;
  // Handle `value`.
}

في ما يلي مثال آخر على كيفية قراءة كمية محدّدة من البيانات من منفذ تسلسلي:

async function readInto(reader, buffer) {
  let offset = 0;
  while (offset < buffer.byteLength) {
    const { value, done } = await reader.read(
      new Uint8Array(buffer, offset)
    );
    if (done) {
      break;
    }
    buffer = value.buffer;
    offset += value.byteLength;
  }
  return buffer;
}

const reader = port.readable.getReader({ mode: "byob" });
let buffer = new ArrayBuffer(512);
// Read the first 512 bytes.
buffer = await readInto(reader, buffer);
// Then read the next 512 bytes.
buffer = await readInto(reader, buffer);

الكتابة على منفذ تسلسلي

لإرسال البيانات إلى جهاز تسلسلي، يُرجى تمرير البيانات إلى port.writable.getWriter().write(). يجب الاتصال بـ releaseLock() على الرقم port.writable.getWriter() ليتم إغلاق المنفذ التسلسلي لاحقًا.

const writer = port.writable.getWriter();

const data = new Uint8Array([104, 101, 108, 108, 111]); // hello
await writer.write(data);


// Allow the serial port to be closed later.
writer.releaseLock();

أرسِل نصًا إلى الجهاز من خلال TextEncoderStream يتم توجيهه إلى port.writable كما هو موضّح أدناه.

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

const writer = textEncoder.writable.getWriter();

await writer.write("hello");

إغلاق منفذ تسلسلي

يغلق port.close() المنفذ التسلسلي إذا تم فتح قفل المشترِكَين readable وwritable، ما يعني أنّه تم طلب "releaseLock()" من القارئ والكاتب المعنيَّين.

await port.close();

ومع ذلك، عند قراءة البيانات باستمرار من جهاز تسلسلي باستخدام التكرار الحلقي، سيتم دائمًا قفل "port.readable" إلى أن يظهر خطأ. في هذه الحالة، سيؤدي استدعاء reader.cancel() إلى فرض reader.read() على حلّ المشكلة في الحال مع { value: undefined, done: true }، وبالتالي السماح للحلقة بطلب reader.releaseLock().

// Without transform streams.

let keepReading = true;
let reader;

async function readUntilClosed() {
  while (port.readable && keepReading) {
    reader = port.readable.getReader();
    try {
      while (true) {
        const { value, done } = await reader.read();
        if (done) {
          // reader.cancel() has been called.
          break;
        }
        // value is a Uint8Array.
        console.log(value);
      }
    } catch (error) {
      // Handle error...
    } finally {
      // Allow the serial port to be closed later.
      reader.releaseLock();
    }
  }

  await port.close();
}

const closedPromise = readUntilClosed();

document.querySelector('button').addEventListener('click', async () => {
  // User clicked a button to close the serial port.
  keepReading = false;
  // Force reader.read() to resolve immediately and subsequently
  // call reader.releaseLock() in the loop example above.
  reader.cancel();
  await closedPromise;
});

يكون إغلاق منفذ تسلسلي أكثر تعقيدًا عند استخدام تحويل مجموعات البث. يمكنك التواصل مع "reader.cancel()" كما في السابق. بعد ذلك، يمكنك الاتصال بـ writer.close() وport.close(). يؤدي ذلك إلى نشر الأخطاء من خلال تدفقات التحويل إلى المنفذ التسلسلي الأساسي. بما أنّ نشر الأخطاء لا يحدث على الفور، عليك استخدام وعود readableStreamClosed وwritableStreamClosed التي تم إنشاؤها سابقًا لرصد الوقت الذي تم فيه فتح قفل port.readable وport.writable. يؤدي إلغاء السمة reader إلى إيقاف البث، وبالتالي عليك اكتشاف الخطأ الناتج وتجاهله.

// With transform streams.

const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    reader.releaseLock();
    break;
  }
  // value is a string.
  console.log(value);
}

const textEncoder = new TextEncoderStream();
const writableStreamClosed = textEncoder.readable.pipeTo(port.writable);

reader.cancel();
await readableStreamClosed.catch(() => { /* Ignore the error */ });

writer.close();
await writableStreamClosed;

await port.close();

الاستماع إلى الاتصال وانقطاع الاتصال

في حال توفير منفذ تسلسلي بواسطة جهاز USB، قد يكون هذا الجهاز متصلاً أو غير متصل بالنظام. وعندما يتم منح الموقع الإلكتروني إذنًا بالوصول إلى منفذ تسلسلي، من المفترض أن يراقب حدثَي connect وdisconnect.

navigator.serial.addEventListener("connect", (event) => {
  // TODO: Automatically open event.target or warn user a port is available.
});

navigator.serial.addEventListener("disconnect", (event) => {
  // TODO: Remove |event.target| from the UI.
  // If the serial port was opened, a stream error would be observed as well.
});

التعامل مع الإشارات

بعد إنشاء اتصال المنفذ التسلسلي، يمكنك بشكل صريح طلب البحث وتحديد الإشارات التي يعرضها المنفذ التسلسلي لرصد الجهاز والتحكّم في التدفق. يتم تعريف هذه الإشارات على أنّها قيم منطقية. على سبيل المثال، ستدخل بعض الأجهزة مثل Arduino وضع برمجة في حال تفعيل إشارة "جهاز طرفي البيانات" (DTR) قيد التبديل.

يتم ضبط إشارات الإخراج والحصول على إشارات الإدخال على التوالي من خلال استدعاء port.setSignals() وport.getSignals(). يمكنك الاطّلاع على أمثلة الاستخدام أدناه.

// Turn off Serial Break signal.
await port.setSignals({ break: false });

// Turn on Data Terminal Ready (DTR) signal.
await port.setSignals({ dataTerminalReady: true });

// Turn off Request To Send (RTS) signal.
await port.setSignals({ requestToSend: false });
const signals = await port.getSignals();
console.log(`Clear To Send:       ${signals.clearToSend}`);
console.log(`Data Carrier Detect: ${signals.dataCarrierDetect}`);
console.log(`Data Set Ready:      ${signals.dataSetReady}`);
console.log(`Ring Indicator:      ${signals.ringIndicator}`);

تحويل ساحات المشاركات

عندما تتلقى بيانات من الجهاز التسلسلي، لن تحصل بالضرورة على جميع البيانات مرة واحدة. ويمكن أن يتم تقسيمها بشكل عشوائي. لمزيد من المعلومات، يُرجى الاطّلاع على مفاهيم واجهة برمجة التطبيقات الخاصة بـ Streams.

للتعامل مع هذا الأمر، يمكنك استخدام بعض عمليات تدفق التحويل المدمَجة مثل TextDecoderStream أو إنشاء ساحة مشاركات تحويل خاصة بك، ما يسمح لك بتحليل مصدر البيانات الوارد وعرض البيانات التحليلية. يقع بث التحويل بين الجهاز التسلسلي وحلقة القراءة التي تستهلك البث. يمكنها إجراء تحويل عشوائي قبل استهلاك البيانات. فكر في الأمر كما لو كان خط التجميع: عندما تأتي الأداة إلى أسفل الخط، فإن كل خطوة في الخط تعدل الأداة، بحيث عند وصولها إلى وجهتها النهائية، تكون أداة تعمل بكامل طاقتها.

صورة لمصنع طائرات
مصنع الطائرات "قلعة برومويتش" من الحرب العالمية الثانية

على سبيل المثال، نقترح عليك كيفية إنشاء فئة بث تحويل تستهلك بثًا وتقسّمه حسب فواصل الأسطر. يتم استدعاء طريقة transform() في كل مرة يتم فيها تلقّي بيانات جديدة من خلال مصدر البيانات. يمكنه إما إدراج البيانات في قائمة البيانات أو حفظها لوقت لاحق. يتم استدعاء طريقة flush() عند إغلاق البث، وهي تعالج أي بيانات لم تتم معالجتها بعد.

لاستخدام فئة تدفق التحويل، تحتاج إلى توجيه تدفق وارد من خلالها. في المثال الثالث على الرمز ضمن القراءة من منفذ تسلسلي، كان مصدر الإدخال الأصلي يتم تمريره فقط من خلال TextDecoderStream، لذا نحتاج إلى استدعاء pipeThrough() لتوجيهه من خلال LineBreakTransformer الجديد.

class LineBreakTransformer {
  constructor() {
    // A container for holding stream data until a new line.
    this.chunks = "";
  }

  transform(chunk, controller) {
    // Append new chunks to existing chunks.
    this.chunks += chunk;
    // For each line breaks in chunks, send the parsed lines out.
    const lines = this.chunks.split("\r\n");
    this.chunks = lines.pop();
    lines.forEach((line) => controller.enqueue(line));
  }

  flush(controller) {
    // When the stream is closed, flush any remaining chunks out.
    controller.enqueue(this.chunks);
  }
}
const textDecoder = new TextDecoderStream();
const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
const reader = textDecoder.readable
  .pipeThrough(new TransformStream(new LineBreakTransformer()))
  .getReader();

لتصحيح مشاكل الاتصال على الأجهزة التسلسلية، يمكنك استخدام طريقة tee() في port.readable لتقسيم أحداث البث التي يتم إرسالها إلى الجهاز التسلسلي أو منه. ويمكن مشاهدة مصدرَي البث بشكل مستقل، ويتيح لك ذلك طباعة أحدهما على وحدة التحكم لفحصه.

const [appReadable, devReadable] = port.readable.tee();

// You may want to update UI with incoming data from appReadable
// and log incoming data in JS console for inspection from devReadable.

إبطال إمكانية الوصول إلى منفذ تسلسلي

بإمكان الموقع الإلكتروني حذف أذونات الوصول إلى منفذ تسلسلي لم يعُد مهتمًا بالاحتفاظ به من خلال استدعاء forget() على الجهاز الافتراضي SerialPort. فعلى سبيل المثال، بالنسبة إلى تطبيق ويب تعليمي يتم استخدامه على جهاز كمبيوتر مشترك مع عدة أجهزة، يؤدي العدد الكبير من الأذونات المتراكمة التي ينشئها المستخدم إلى ترك انطباع سيئ لدى المستخدم.

// Voluntarily revoke access to this serial port.
await port.forget();

بسبب توفّر forget() في Chrome 103 أو الإصدارات الأحدث، تحقَّق مما إذا كانت هذه الميزة متوافقة مع ما يلي:

if ("serial" in navigator && "forget" in SerialPort.prototype) {
  // forget() is supported.
}

نصائح للمطوّرين

من السهل تصحيح أخطاء Web Serial API في Chrome من خلال الصفحة الداخلية about://device-log التي تتيح لك الاطّلاع على كل الأحداث التسلسلية ذات الصلة بالأجهزة في مكان واحد.

لقطة شاشة للصفحة الداخلية لتصحيح الأخطاء في Web Serial API.
الصفحة الداخلية في Chrome لتصحيح الأخطاء في Web Serial API

درس تطبيقي حول الترميز

في الدرس التطبيقي حول ترميز Google Developer، ستستخدم واجهة Web Serial API للتفاعل مع لوحة BBC micro:bit لعرض الصور على مصفوفة LED مقاس 5x5.

المتصفحات المتوافقة

تتوفّر Web Serial API على جميع الأنظمة الأساسية لأجهزة الكمبيوتر المكتبي (ChromeOS وLinux وmacOS وWindows) في الإصدار Chrome 89.

الملء التلقائي

على نظام التشغيل Android، يمكن إتاحة المنافذ التسلسلية المستندة إلى USB باستخدام واجهة برمجة تطبيقات WebUSB API ورمز واجهة برمجة التطبيقات التسلسلي polyfill. يقتصر هذا الرمز polyfill على الأجهزة والأنظمة الأساسية التي يمكن من خلالها الوصول إلى الجهاز عبر WebUSB API لأنّه لم تتم المطالبة به من خلال برنامج تشغيل مدمَج للجهاز.

الأمان والخصوصية

صمّم مؤلفو المواصفات واجهة Web Serial API ونفّذوها باستخدام المبادئ الأساسية المحدّدة في التحكّم في الوصول إلى الميزات الفعّالة لمنصة الويب، بما في ذلك عناصر التحكّم في المستخدم والشفافية وهندسة العمل. وتعتمد إمكانية استخدام واجهة برمجة التطبيقات هذه بشكل أساسي على نموذج إذن يمنح إمكانية الوصول إلى جهاز تسلسلي واحد فقط في كل مرة. استجابة لمطالبة المستخدم، يجب على المستخدم اتخاذ خطوات نشطة لتحديد جهاز تسلسلي معين.

للتعرّف على مُفاضلات الأمان، يُرجى الاطّلاع على قسمَي الأمان والخصوصية في Web Serial API.

إضافة ملاحظات

يود فريق Chrome معرفة رأيك وتجاربك في استخدام Web Serial API.

أخبرنا عن تصميم واجهة برمجة التطبيقات

هل هناك أي مشكلة في واجهة برمجة التطبيقات لا تعمل كما هو متوقع؟ أو هل هناك طرق أو خصائص مفقودة تحتاج إلى تنفيذ فكرتك؟

أبلِغ عن مشكلة في المواصفات على مستودع Web Serial API على GitHub أو أضِف أفكارك إلى مشكلة حالية.

الإبلاغ عن مشكلة في التنفيذ

هل واجهت خطأً في تنفيذ Chrome؟ أم أن التنفيذ مختلف عن المواصفات؟

عليك الإبلاغ عن الخطأ على https://new.crbug.com. واحرص على تضمين أكبر قدر ممكن من التفاصيل، وتقديم تعليمات بسيطة لإعادة إنتاج الخطأ، وضبط المكوّنات على Blink>Serial. تعمل Glitch بشكل رائع لمشاركة عمليات إعادة الإنشاء السريعة والسهلة.

إظهار الدعم

هل تخطّط لاستخدام Web Serial API؟ يساعد الدعم المتاح للجميع فريق Chrome في إعطاء الأولوية للميزات، كما يوضّح لمورّدي المتصفّحات الآخرين مدى أهمية دعمهم لهذه الميزات.

يمكنك إرسال تغريدة إلى @ChromiumDev باستخدام الهاشتاغ #SerialAPI وإعلامنا بمكان استخدامك لها وطريقة استخدامك لها.

روابط مفيدة

إصدارات تجريبية

شكر وتقدير

شكرًا لكل من رايلي غرانت وجو ميدلي على مراجعاتهما لهذه المقالة. صورة لمصنع طائرات بواسطة Birmingham Universitys Trust على موقع Unسبلاش