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

تسمح واجهة برمجة التطبيقات 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 مفقودة تحتاجها لتنفيذ فكرتك؟

يمكنك إرسال مشكلة في المواصفات على مستودع GitHub الخاص بواجهة برمجة تطبيقات التسلسل على الويب أو إضافة ملاحظاتك إلى مشكلة حالية.

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

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

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

إظهار الدعم

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

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

روابط مفيدة

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

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

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