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

تسمح واجهة برمجة التطبيقات 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 وBluetooth التي تحاكي منفذًا تسلسليًا، يمكن ضبط هذه القيمة بأمان على أي قيمة لأنّه يتم تجاهلها من خلال المحاكاة.

// 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").

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

تعالج واجهة برمجة التطبيقات Streams API مصادر البيانات ومخرجاتها في Web Serial 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);
}

يمكن أن تحدث بعض أخطاء القراءة غير المميتة في المنفذ التسلسلي في بعض الحالات، مثل overflow المخزن المؤقت أو أخطاء وضع الإطار أو أخطاء التكافؤ. يتم طرح هذه الأخطاء على هيئة استثناءات ويمكن رصدها عن طريق إضافة حلقة أخرى فوق الحلقة السابقة التي تتحقّق من 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، في وضع البرمجة إذا تم تبديل إشارة Data Terminal Ready (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 API.

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

صورة لمصنع طائرات
World War II Castle Bromwich Aeroplane Factory

على سبيل المثال، ننصحك بالتفكير في كيفية إنشاء فئة بث تحويل يستخدِم مجرى بيانات ويقسّمه إلى أجزاء استنادًا إلى فواصل الأسطر. يتمّ استدعاء طريقة 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() متوفّر في الإصدار 103 من Chrome أو الإصدارات الأحدث، تأكّد ممّا إذا كانت هذه الميزة متوافقة مع ما يلي:

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، ستستخدم Web Serial API للتفاعل مع لوحة BBC micro:bit لعرض الصور على مصفوفة مصباح LED بحجم 5×5.

دعم المتصفح

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

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

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

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

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

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

ملاحظات

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

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

هل هناك مشكلة في واجهة برمجة التطبيقات لا تعمل على النحو المتوقّع؟ هل هناك methods أو properties مفقودة تحتاجها لتنفيذ فكرتك؟

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

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

هل رصدت خطأ في عملية تنفيذ Chrome؟ أم هل التنفيذ مختلف عن المواصفات؟

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

إظهار الدعم

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

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

روابط مفيدة

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

الشكر والتقدير

نشكر Reilly Grant وJoe Medley على مراجعتهما لهذه المقالة. صورة مصنع طائرات من Birmingham Museums Trust على Unsplash.