أطلق العنان للتواصل المباشر مع الأجهزة في تطبيقات الويب الخاصة بك. يشرح هذا الدليل دورة حياة جهاز WebHID الكاملة، من الاكتشاف والاتصال إلى التفاعل والتنظيف.
مدير أجهزة WebHID للواجهة الأمامية: دليل شامل لدورة حياة الأجهزة
لم تعد منصة الويب مجرد وسيط للمستندات. لقد تطورت لتصبح نظامًا بيئيًا قويًا للتطبيقات قادرًا على منافسة، وفي كثير من الحالات، التفوق على برامج سطح المكتب التقليدية. أحد أهم التطورات الأخيرة في هذا التطور هو قدرة تطبيقات الويب على التواصل مباشرة مع الأجهزة. أصبح هذا ممكنًا بفضل مجموعة من واجهات برمجة التطبيقات الحديثة، وفي طليعتها لفئة واسعة من الأجهزة تأتي واجهة WebHID API.
تمكّن واجهة WebHID (جهاز الواجهة البشرية) المطورين من سد الفجوة بين تطبيقات الويب الخاصة بهم ومجموعة واسعة من الأجهزة المادية—من وحدات التحكم في الألعاب وأجهزة الاستشعار الطبية إلى الآلات الصناعية المتخصصة. إنها تلغي حاجة المستخدمين إلى تثبيت برامج تشغيل مخصصة أو برامج وسيطة مرهقة، مما يوفر تجربة سلسة وآمنة وعابرة للمنصات مباشرة داخل المتصفح.
لكن مجرد استدعاء الواجهة البرمجية ليس كافيًا. لبناء تطبيق قوي وسهل الاستخدام، تحتاج إلى إدارة دورة حياة جهاز بأكملها. يتضمن هذا أكثر من مجرد إرسال واستقبال البيانات؛ فهو يتطلب نهجًا منظمًا للاكتشاف وإدارة الاتصال وتتبع الحالة والتعامل السلس مع انقطاع الاتصال. هذا هو دور مدير أجهزة WebHID للواجهة الأمامية.
سيرشدك هذا الدليل الشامل عبر المراحل الأربع الحاسمة لدورة حياة الأجهزة داخل تطبيق الويب. سنستكشف التفاصيل الفنية وأفضل ممارسات تجربة المستخدم والأنماط المعمارية اللازمة لبناء مدير أجهزة احترافي موثوق وقابل للتطوير لجمهور عالمي.
فهم WebHID: الأساس
قبل أن نتعمق في دورة الحياة، من الضروري فهم أساسيات WebHID ومبادئ الأمان التي تقوم عليها. هذا الأساس سيوجه كل قرار نتخذه عند بناء مدير الأجهزة الخاص بنا.
ما هي واجهة WebHID؟
بروتوكول HID هو معيار معتمد على نطاق واسع للأجهزة التي يستخدمها البشر للتفاعل مع أجهزة الكمبيوتر. بينما تم تصميمه في البداية للوحات المفاتيح والفأرة وعصي التحكم، فإن هيكله المرن القائم على التقارير يجعله مناسبًا لمجموعة هائلة من الأجهزة. "التقرير" هو ببساطة حزمة من البيانات يتم إرسالها بين الجهاز والمضيف (في حالتنا، المتصفح).
WebHID هي مواصفة من W3C تعرض هذا البروتوكول لمطوري الويب عبر جافاسكريبت. إنها توفر آلية آمنة من أجل:
- اكتشاف وطلب الإذن للوصول إلى أجهزة HID المتصلة.
- فتح اتصال بجهاز مسموح به.
- إرسال واستقبال تقارير البيانات.
- الاستماع لأحداث الاتصال وقطع الاتصال.
اعتبارات الأمان والخصوصية الرئيسية
إن منح موقع ويب وصولاً مباشرًا إلى الأجهزة هو قدرة قوية تتطلب إجراءات أمنية صارمة. تم تصميم واجهة WebHID API بنموذج أمان يركز على المستخدم لمنع إساءة الاستخدام وحماية الخصوصية:
- إذن بمبادرة من المستخدم: لا يمكن لصفحة الويب أبدًا الوصول إلى جهاز دون موافقة صريحة من المستخدم. يجب أن يبدأ الوصول بإيماءة من المستخدم (مثل النقر على زر) تؤدي إلى ظهور مطالبة إذن يتحكم فيها المتصفح. المستخدم دائمًا في موقع السيطرة.
- متطلب HTTPS: مثل معظم واجهات برمجة تطبيقات الويب الحديثة، لا تتوفر واجهة WebHID إلا في السياقات الآمنة (HTTPS).
- تحديد الجهاز: يجب على تطبيق الويب أن يعلن عن نوع الأجهزة التي يهتم بها باستخدام المرشحات. يرى المستخدم هذه المعلومات في مطالبة الإذن، مما يضمن الشفافية.
- معيار عالمي: كمعيار من W3C، فإنه يوفر نموذج أمان متسقًا ويمكن التنبؤ به عبر جميع المتصفحات الداعمة، وهو أمر حاسم لبناء الثقة مع قاعدة مستخدمين عالمية.
المكونات الأساسية لواجهة WebHID API
سيتم بناء مدير الأجهزة الخاص بنا على هذه المكونات الأساسية للواجهة البرمجية:
navigator.hid: نقطة الدخول الرئيسية للواجهة البرمجية. نتحقق أولاً من وجودها لتحديد ما إذا كان المتصفح يدعم WebHID.navigator.hid.requestDevice({ filters: [...] }): تطلق منتقي الأجهزة في المتصفح، وتطلب من المستخدم الإذن. تُرجع Promise يتم حلها بمصفوفة من كائناتHIDDeviceالمحددة.navigator.hid.getDevices(): تُرجع Promise يتم حلها بمصفوفة من كائناتHIDDeviceالتي تم منح التطبيق بالفعل إذنًا للوصول إليها في جلسات سابقة.HIDDevice: كائن يمثل الجهاز المتصل. لديه طرق مثلopen()وclose()وsendReport()، وخصائص مثلvendorIdوproductIdوproductName.- أحداث
connectوdisconnect: أحداث عامة علىnavigator.hidيتم إطلاقها عند توصيل جهاز مسموح به بالنظام أو فصله.
المراحل الأربع لدورة حياة الجهاز
إدارة الجهاز هي رحلة من أربع مراحل متميزة. يجب على مدير الأجهزة القوي التعامل مع كل مرحلة من هذه المراحل بسلاسة لتوفير تجربة مستخدم سلسة.
المرحلة 1: الاكتشاف والإذن
هذه هي نقطة التفاعل الأولى والأكثر أهمية. يحتاج تطبيقك إلى العثور على الأجهزة المتوافقة وطلب الإذن من المستخدم لاستخدام أحدها. تجربة المستخدم هنا تحدد نغمة التفاعل بأكمله.
صياغة استدعاء requestDevice()
مفتاح تجربة الاكتشاف الجيدة هو مصفوفة filters التي تمررها إلى requestDevice(). تخبر هذه المرشحات المتصفح بالأجهزة التي يجب عرضها في المنتقي. أن تكون محددًا أمر حاسم.
يمكن أن يتضمن المرشح:
vendorId(VID): المعرف الفريد للشركة المصنعة للجهاز.productId(PID): المعرف الفريد لنموذج المنتج المحدد من تلك الشركة المصنعة.usagePageوusage: تصف هذه الوظيفة عالية المستوى للجهاز وفقًا لمواصفات HID (على سبيل المثال، لوحة ألعاب عامة، جهاز تحكم في الإضاءة).
مثال: طلب الوصول إلى ميزان USB معين أو لوحة ألعاب عامة.
async function requestDeviceAccess() {
// First, check if WebHID is supported by the browser.
if (!("hid" in navigator)) {
alert("WebHID is not supported in your browser. Please use a compatible browser.");
return null;
}
try {
// requestDevice must be called in response to a user gesture, like a click.
const devices = await navigator.hid.requestDevice({
filters: [
// Example 1: A specific product (e.g., a Dymo M25 shipping scale)
{ vendorId: 0x0922, productId: 0x8004 },
// Example 2: Any device that identifies as a standard gamepad
{ usagePage: 0x01, usage: 0x05 },
],
});
// The promise resolves with an array of devices the user selected.
// Typically, the user can only select one device from the prompt.
if (devices.length === 0) {
return null; // User closed the prompt without selecting a device.
}
return devices[0]; // Return the selected device.
} catch (error) {
// The user may have cancelled the request or an error occurred.
console.error("Device request failed:", error);
return null;
}
}
التعامل مع إجراءات المستخدم
يمكن أن يؤدي استدعاء requestDevice() إلى عدة نتائج، ويجب أن تكون واجهة المستخدم الخاصة بك مستعدة لكل منها:
- تم منح الإذن: يتم حل الـ promise مع الجهاز المحدد. يجب أن تتحدث واجهة المستخدم الخاصة بك لإظهار أن الجهاز قد تم تحديده والانتقال إلى مرحلة الاتصال.
- تم رفض الإذن: إذا نقر المستخدم على "إلغاء" أو أغلق المطالبة، يتم رفض الـ promise مع خطأ
NotFoundError. يجب أن تلتقط هذا الخطأ وتتجنب إظهار رسالة خطأ مخيفة. ببساطة عد إلى الحالة الأولية. - لا توجد أجهزة متوافقة: إذا لم يتم توصيل أي أجهزة تطابق المرشحات الخاصة بك، فقد يعرض المتصفح قائمة فارغة أو رسالة. يجب أن توفر واجهة المستخدم الخاصة بك تعليمات واضحة، مثل، "يرجى توصيل جهازك والمحاولة مرة أخرى."
المرحلة 2: الاتصال والتهيئة
بمجرد حصولك على كائن HIDDevice، لم تقم بعد بإنشاء قناة اتصال نشطة. تحتاج إلى فتح الجهاز بشكل صريح.
فتح الجهاز
تؤسس طريقة device.open() الاتصال. إنها عملية غير متزامنة تُرجع promise.
async function connectToDevice(device) {
if (!device) return false;
// Check if the device is already open.
if (device.opened) {
console.log("Device is already open.");
return true;
}
try {
await device.open();
console.log(`Successfully opened device: ${device.productName}`);
// Now the device is ready for interaction.
return true;
} catch (error) {
console.error(`Failed to open device: ${device.productName}`, error);
return false;
}
}
يحتاج مدير الأجهزة الخاص بك إلى تتبع حالة الاتصال (على سبيل المثال، `isConnecting`، `isConnected`). عند استدعاء open()، تقوم بتعيين `isConnecting` إلى true. وعندما يتم حلها، تقوم بتعيين `isConnected` إلى true و `isConnecting` إلى false. هذه الحالة حاسمة لتحديث واجهة المستخدم، على سبيل المثال، عن طريق تعطيل زر "اتصال" وتمكين زر "قطع الاتصال".
تهيئة الجهاز (المصافحة)
العديد من الأجهزة المعقدة لا تبدأ في إرسال البيانات فور الاتصال. قد تتطلب أمرًا أوليًا—مصافحة—لوضعها في الوضع الصحيح، أو الاستعلام عن إصدار البرنامج الثابت الخاص بها، أو استرداد حالتها. توجد هذه المعلومات دائمًا في الوثائق الفنية للجهاز.
تقوم بإرسال البيانات باستخدام device.sendReport() أو device.sendFeatureReport(). لتسلسل التهيئة، غالبًا ما يتم استخدام تقرير الميزة.
مثال: إرسال أمر للحصول على إصدار البرنامج الثابت للجهاز.
async function initializeDevice(device) {
if (!device || !device.opened) {
console.error("Device is not open.");
return;
}
// Assume the device documentation says:
// To get the firmware version, send a feature report with Report ID 5.
// The report is 2 bytes: [Report ID, Command ID]
// Command ID for 'Get Version' is 1.
try {
const reportId = 5;
const getVersionCommand = new Uint8Array([1]); // Command ID
await device.sendFeatureReport(reportId, getVersionCommand);
console.log("Sent 'Get Version' command.");
// The device will respond with an input report containing the version,
// which we will handle in the next stage.
} catch (error) {
console.error("Failed to send initialization command:", error);
}
}
المرحلة 3: التفاعل النشط ومعالجة البيانات
هذا هو جوهر وظائف تطبيقك. الجهاز متصل، مهيأ، وجاهز لتبادل البيانات. تتضمن هذه المرحلة اتصالًا ثنائي الاتجاه: الاستماع للتقارير من الجهاز وإرسال التقارير إليه.
الحلقة الأساسية: الاستماع للبيانات
الطريقة الأساسية لتلقي البيانات من جهاز HID هي عن طريق الاستماع إلى حدث inputreport.
function startListening(device) {
device.addEventListener('inputreport', handleInputReport);
console.log("Started listening for input reports.");
}
function handleInputReport(event) {
const { data, device, reportId } = event;
// The `data` is a DataView object, which is a low-level interface
// for reading binary data from an ArrayBuffer.
console.log(`Received report ID ${reportId} from ${device.productName}`);
// Now, we parse the data based on the device's documentation.
parseDeviceData(data, reportId);
}
تحليل تقارير الإدخال
إن event.data هو DataView، وهو مخزن مؤقت خام للبيانات الثنائية. هذا هو الجزء الأكثر تحديدًا للجهاز في العملية بأكملها. يجب أن تكون لديك وثائق الجهاز لفهم بنية بيانات تقاريره.
مثال: تحليل تقرير من مستشعر طقس بسيط.
لنفترض أن الوثائق تقول إن الجهاز يرسل تقريرًا بمعرف 1، طوله 4 بايت: - البايتات 0-1: درجة الحرارة (عدد صحيح 16 بت بعلامة، little-endian)، القيمة بالدرجة المئوية * 10. - البايتات 2-3: الرطوبة (عدد صحيح 16 بت بدون علامة، little-endian)، القيمة بـ ٪RH * 10.
function parseDeviceData(dataView, reportId) {
if (reportId !== 1) return; // Not the report we are interested in
if (dataView.byteLength < 4) {
console.warn("Received a malformed report.");
return;
}
// getInt16(byteOffset, littleEndian)
const temperatureRaw = dataView.getInt16(0, true); // true for little-endian
const temperatureCelsius = temperatureRaw / 10.0;
// getUint16(byteOffset, littleEndian)
const humidityRaw = dataView.getUint16(2, true);
const humidityPercent = humidityRaw / 10.0;
console.log(`Current Weather: ${temperatureCelsius}°C, ${humidityPercent}% RH`);
// Here, you would update your application's state and UI.
updateWeatherUI(temperatureCelsius, humidityPercent);
}
إرسال البيانات إلى الجهاز
يتبع إرسال البيانات نمطًا مشابهًا: أنشئ مخزنًا مؤقتًا واستخدم device.sendReport(). يستخدم هذا لإجراءات مثل تغيير لون LED، أو تنشيط محرك، أو تحديث شاشة على الجهاز.
مثال: ضبط لون ضوء RGB LED على جهاز.
افترض أن الوثائق تقول لضبط LED، أرسل تقريرًا بالمعرف 3، متبوعًا بـ 3 بايتات للأحمر والأخضر والأزرق (0-255).
async function setDeviceLedColor(device, r, g, b) {
if (!device || !device.opened) return;
const reportId = 3;
const data = Uint8Array.from([r, g, b]);
try {
await device.sendReport(reportId, data);
console.log(`Set LED color to rgb(${r}, ${g}, ${b})`);
} catch (error) {
console.error("Failed to send LED command:", error);
}
}
المرحلة 4: قطع الاتصال والتنظيف
اتصال الجهاز ليس دائمًا. يمكن إنهاؤه من قبل المستخدم، أو يمكن أن يُفقد بشكل غير متوقع إذا تم فصل الجهاز أو فقد الطاقة. يجب على مديرك التعامل مع كلا السيناريوهين بسلاسة.
قطع الاتصال الطوعي (بمبادرة من المستخدم)
عندما ينقر المستخدم على زر "قطع الاتصال"، يجب على تطبيقك إجراء إيقاف تشغيل نظيف.
- استدعاء
device.close(). هذا غير متزامن ويعيد promise. - إزالة مستمعي الأحداث الذين أضفتهم لمنع تسرب الذاكرة:
device.removeEventListener('inputreport', handleInputReport); - تحديث حالة تطبيقك (على سبيل المثال، `connectedDevice = null`، `isConnected = false`).
- تحديث واجهة المستخدم لتعكس حالة قطع الاتصال.
قطع الاتصال غير الطوعي
هنا يكون حدث disconnect العام على navigator.hid ضروريًا. يتم إطلاق هذا الحدث كلما تم فصل جهاز يمتلك التطبيق إذنًا له عن النظام، بغض النظر عما إذا كان تطبيقك متصلاً به حاليًا أم لا.
let activeDevice = null; // Storing the currently connected device
navigator.hid.addEventListener('disconnect', (event) => {
console.log(`Device disconnected: ${event.device.productName}`);
// Check if the disconnected device is the one we are actively using.
if (activeDevice && event.device.productId === activeDevice.productId && event.device.vendorId === activeDevice.vendorId) {
// Our active device was unplugged!
handleUnexpectedDisconnection();
}
});
function handleUnexpectedDisconnection() {
// It's important to not call close() on a device that is already gone.
// Just perform cleanup.
if(activeDevice) {
activeDevice.removeEventListener('inputreport', handleInputReport);
}
activeDevice = null;
// Update state and UI to inform the user.
updateUiForDisconnection("Device was disconnected. Please reconnect.");
}
منطق إعادة الاتصال باستخدام getDevices()
للحصول على تجربة مستخدم فائقة، يجب أن يتذكر تطبيقك الأجهزة عبر الجلسات. عند تحميل تطبيق الويب الخاص بك، يمكنك استخدام navigator.hid.getDevices() للحصول على قائمة بالأجهزة التي وافق عليها المستخدم مسبقًا. يمكنك بعد ذلك تقديم واجهة مستخدم للسماح للمستخدم بإعادة الاتصال بنقرة واحدة، متجاوزًا مطالبة الإذن الرئيسية.
async function checkForPreviouslyPermittedDevices() {
const permittedDevices = await navigator.hid.getDevices();
if (permittedDevices.length > 0) {
// We have at least one device we can reconnect to without a new prompt.
// Update the UI to show a "Reconnect" button for the first device.
showReconnectOption(permittedDevices[0]);
}
}
بناء مدير أجهزة قوي للواجهة الأمامية
يتطلب ربط كل هذه المراحل معًا بنية أكثر رسمية من مجرد مجموعة من الوظائف. يمكن لفئة أو وحدة DeviceManager تغليف كل المنطق والحالة، مما يوفر واجهة نظيفة لبقية تطبيقك.
إدارة الحالة هي المفتاح
يجب على مديرك الحفاظ على حالة واضحة. قد يبدو كائن الحالة النموذجي كما يلي:
const deviceState = {
isSupported: true, // Does the browser support WebHID?
isConnecting: false, // Are we in the middle of an open() call?
connectedDevice: null, // The active HIDDevice object
deviceInfo: { // Parsed info from the device
name: '',
firmwareVersion: ''
},
lastError: null // A user-friendly error message
};
يجب أن يكون كائن الحالة هذا هو المصدر الوحيد للحقيقة لواجهة المستخدم الخاصة بك. سواء كنت تستخدم React أو Vue أو Svelte أو جافاسكريبت純، يظل هذا المبدأ كما هو. عندما تتغير الحالة، يتم إعادة عرض واجهة المستخدم.
بنية قائمة على الأحداث
لفصل أفضل، يمكن لـ DeviceManager الخاص بك إصدار أحداثه الخاصة. هذا يمنع مكونات واجهة المستخدم الخاصة بك من الحاجة إلى معرفة الأعمال الداخلية لواجهة WebHID API.
كود زائف لفئة DeviceManager:
class DeviceManager extends EventTarget {
constructor() {
this.state = { /* ... initial state ... */ };
navigator.hid.addEventListener('disconnect', this.onDeviceDisconnect.bind(this));
}
async connect() {
// ... handles requestDevice() and open() ...
// ... updates state ...
this.state.connectedDevice.addEventListener('inputreport', this.onInput.bind(this));
this.dispatchEvent(new CustomEvent('connected', { detail: this.state.connectedDevice }));
}
onInput(event) {
const parsedData = this.parse(event.data);
this.dispatchEvent(new CustomEvent('data', { detail: parsedData }));
}
onDeviceDisconnect(event) {
// ... handles cleanup and state update ...
this.dispatchEvent(new CustomEvent('disconnected'));
}
// ... other methods like disconnect(), sendCommand(), etc.
}
منظور عالمي: تباين الأجهزة والتدويل
عند التطوير لجمهور عالمي، تذكر أن الأجهزة ليست دائمًا موحدة. قد تحتوي الأجهزة التي لها نفس VID/PID على إصدارات برامج ثابتة مختلفة مع هياكل تقارير مختلفة قليلاً. يجب أن يكون منطق التحليل الخاص بك دفاعيًا، وأن يتحقق من أطوال التقارير ويضيف معالجة الأخطاء.
علاوة على ذلك، يجب إدارة جميع النصوص الموجهة للمستخدم - "توصيل الجهاز"، "تم فصل الجهاز"، "يرجى استخدام متصفح متوافق" - باستخدام مكتبة تدويل (i18n) لضمان أن تطبيقك متاح واحترافي في أي منطقة.
حالات الاستخدام العملية والنظرة المستقبلية
تطبيقات العالم الحقيقي
الإمكانيات التي تتيحها WebHID واسعة وتشمل العديد من الصناعات:
- الرعاية الصحية عن بعد: توصيل أجهزة قياس ضغط الدم، أو أجهزة قياس السكر، أو مقاييس التأكسج النبضي مباشرة ببوابة المريض المستندة إلى الويب لتسجيل البيانات في الوقت الفعلي دون تثبيت أي برامج خاصة.
- الألعاب: دعم مجموعة واسعة من وحدات التحكم غير القياسية، وعجلات السباق، وعصي التحكم في الطيران لتجارب ألعاب غامرة على الويب.
- الصناعة وإنترنت الأشياء: إنشاء لوحات معلومات على الويب لتكوين وإدارة ومراقبة أجهزة الاستشعار الصناعية في الموقع، أو الموازين، أو وحدات التحكم المنطقية القابلة للبرمجة (PLCs) مباشرة من متصفح الفني.
- الأدوات الإبداعية: السماح لمحرري الصور أو برامج إنتاج الموسيقى المستندة إلى الويب بالتحكم فيها بواسطة أجهزة مادية مثل الأقراص الدوارة، والمخففات، وأسطح التحكم مثل Stream Deck أو Palette Gear.
مستقبل تكامل أجهزة الويب
تعد واجهة WebHID جزءًا من عائلة أكبر من واجهات برمجة التطبيقات، بما في ذلك Web Serial و WebUSB و Web Bluetooth. يعتمد اختيار الواجهة البرمجية التي سيتم استخدامها على بروتوكول الجهاز:
- WebHID: الأفضل للأجهزة الموحدة القائمة على التقارير. غالبًا ما يكون الخيار الأبسط والأكثر أمانًا إذا كان الجهاز يدعم بروتوكول HID.
- Web Serial: مثالي للأجهزة التي تتواصل عبر منفذ تسلسلي، وهو أمر شائع في مجتمع الصانعين (Arduino, Raspberry Pi) ومع المعدات الصناعية القديمة.
- WebUSB: واجهة برمجة تطبيقات ذات مستوى أدنى وأكثر قوة للأجهزة التي تستخدم بروتوكولات USB مخصصة. إنها توفر أكبر قدر من التحكم ولكنها تتطلب منطق تشغيل أكثر تعقيدًا في جافاسكريبت الخاص بك.
يشير التطوير المستمر لواجهات برمجة التطبيقات هذه إلى اتجاه واضح: أصبح المتصفح منصة تطبيقات عالمية حقيقية، قادرة على التفاعل مع العالم المادي بطرق غنية وذات مغزى.
الخاتمة
تفتح واجهة WebHID API آفاقًا جديدة لمطوري الواجهة الأمامية، ولكن تسخير إمكاناتها الكاملة يتطلب نهجًا منضبطًا. من خلال فهم وإدارة دورة حياة الأجهزة الكاملة - الاكتشاف، والاتصال، والتفاعل، وقطع الاتصال - يمكنك بناء تطبيقات ليست قوية فحسب، بل موثوقة وآمنة وسهلة الاستخدام أيضًا.
إن بناء مدير أجهزة مخصص للواجهة الأمامية يغلف هذا التعقيد، ويوفر أساسًا مستقرًا لإنشاء الجيل التالي من تجارب الويب التفاعلية. من خلال ربط العالمين الرقمي والمادي مباشرة داخل المتصفح، يمكنك تقديم قيمة غير مسبوقة لمستخدميك، بغض النظر عن مكان وجودهم في العالم.