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

تسمح واجهة برمجة التطبيقات 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").

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

تعالج واجهة برمجة التطبيقات 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 على مراجعتهما لهذه المقالة. صورة من مصنع طائرات بواسطة صندوق متحف برمنغهام للطيران على موقع أنسبلاش