تسمح واجهة برمجة تطبيقات بلوتوث الويب للمواقع الإلكترونية بالاتصال بأجهزة البلوتوث.
ماذا لو أخبرتك بأنّ المواقع الإلكترونية يمكن أن تتصل بالأجهزة القريبة التي تتضمّن بلوتوث؟ بطريقة آمنة تحافظ على الخصوصية؟ بهذه الطريقة، يتم رصد معدل ضربات القلب والغناء والمصابيح الكهربائية، وحتى السلاحف يمكنها التفاعل مباشرةً مع موقع إلكتروني.
حتى الآن، يمكن التفاعل مع الأجهزة التي تتضمّن بلوتوث. للتطبيقات الخاصة بنظام التشغيل فقط. تهدف واجهة برمجة تطبيقات Web Bluetooth API إلى تغيير ينقله إلى متصفحات الويب أيضًا.
قبل أن نبدأ
يفترض هذا المستند أن لديك بعض المعرفة الأساسية حول كيفية انخفاض مستوى البلوتوث يعمل كل من الطاقة (BLE) والملف الشخصي للسمة العامة.
على الرغم من أنه لم يتم الانتهاء بعد من مواصفات Web Bluetooth API، لا يبحث المؤلفون بشكل نشط عن مطورين متحمسين لتجربة واجهة برمجة التطبيقات هذه قدِّم ملاحظاتك حول المواصفات وملاحظات حول عملية التنفيذ.
تتوفّر مجموعة فرعية من Web Bluetooth API في نظام التشغيل ChromeOS وChrome لنظام Android 6.0 وMac (Chrome 56) وWindows 10 (Chrome 70). هذا يعني أنك يجب أن تكون قادرًا طلب والاتصال بالأجهزة القريبة التي تعمل بالبلوتوث منخفض الطاقة قراءة/كتابة خصائص بلوتوث، تلقي إشعارات GATT، معرفة عند انقطاع اتصال جهاز بلوتوث، وحتى القراءة والكتابة إلى أدوات وصف البلوتوث راجع جدول توافق المتصفح الخاص بشبكة MDN لمعرفة المزيد. المعلومات.
بالنسبة إلى نظام التشغيل Linux والإصدارات السابقة من Windows، فعّل
تم الإبلاغ عن #experimental-web-platform-features
في about://flags
.
متاحة لمرحلة التجربة والتقييم
للحصول على أكبر قدر ممكن من الملاحظات من المطورين الذين يستخدمون الويب واجهة برمجة تطبيقات البلوتوث في الحقل، أضاف Chrome هذه الميزة من قبل في Chrome 53 كإصدار تجريبي المصدر لنظام التشغيل ChromeOS وAndroid وMac.
انتهت الفترة التجريبية بنجاح في كانون الثاني (يناير) 2017.
متطلبات الأمان
ننصحك باستخدام Web Bluetooth Security (بروتوكول أمان البلوتوث في الويب) لمعرفة الإيجابيات والسلبيات المتعلّقة بالأمان. مشاركة حول النموذج نشرها "جيفري ياسكين"، مهندس برمجيات في فريق Chrome نعمل على مواصفات واجهة برمجة تطبيقات بلوتوث الويب.
HTTPS فقط
ولأن واجهة برمجة التطبيقات التجريبية هذه هي ميزة جديدة قوية تمت إضافتها إلى الويب، فهي إتاحتها فقط للسياقات الآمنة. هذا يعني أنك ستحتاج إلى البناء باستخدام وضع TLS في الاعتبار.
إيماءة المستخدم مطلوبة
كميزة أمان، يمكن اعتبار اكتشاف أجهزة بلوتوث المزودة بـ
يجب تشغيل navigator.bluetooth.requestDevice
من خلال إيماءة مستخدم مثل
كلمسة أو نقرة بالماوس. نحن نتحدث عن الاستماع إلى
حدث pointerup
وclick
وtouchend
.
button.addEventListener('pointerup', function(event) {
// Call navigator.bluetooth.requestDevice
});
الدخول في الرمز
تعتمد Web Bluetooth API اعتمادًا كبيرًا على وعود JavaScript. إذا لم تكن
اطّلِع على هذا الدليل التعليمي حول التعهدات هناك شيء آخر،
() => {}
هي دوال السهم ECMAScript 2015.
طلب الحصول على أجهزة تتضمّن بلوتوث
يسمح هذا الإصدار من مواصفات واجهة برمجة تطبيقات بلوتوث الويب للمواقع الإلكترونية التي تعمل في الدور المركزي، للاتصال بخوادم GATT عن بُعد عبر اتصال BLE. أُنشأها جون هنتر، الذي كان متخصصًا يتيح الاتصال بين الأجهزة التي تستخدم الإصدار 4.0 من البلوتوث أو الإصدارات الأحدث.
عندما يطلب موقع ويب الوصول إلى الأجهزة المجاورة باستخدام
navigator.bluetooth.requestDevice
، يطلب المتصفّح من المستخدم الوصول إلى جهاز.
محدد حيث يمكنهم اختيار جهاز واحد أو إلغاء الطلب.
تستخدم الدالة navigator.bluetooth.requestDevice()
كائنًا إلزاميًا
تحدد عوامل التصفية. تُستخدَم هذه الفلاتر لعرض الأجهزة التي تتطابق مع بعض البيانات فقط
خدمات Bluetooth GATT المُعلَن عنها و/أو اسم الجهاز.
فلتر الخدمات
على سبيل المثال، لطلب أجهزة بلوتوث التي تعلن عن Bluetooth GATT خدمة البطارية:
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => { /* … */ })
.catch(error => { console.error(error); });
إذا لم تكن خدمة Bluetooth GATT مُدرَجة في قائمة إعدادات البلوتوث الموحّدة خدمات GATT، يمكنك توفير إما المعرّف الفريد العالمي (UUID) للبلوتوث الكامل أو معرّف قصير نموذج 16 أو 32 بت
navigator.bluetooth.requestDevice({
filters: [{
services: [0x1234, 0x12345678, '99999999-0000-1000-8000-00805f9b34fb']
}]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
فلتر الأسماء
يمكنك أيضًا طلب الأجهزة التي تتضمّن بلوتوث استنادًا إلى اسم الجهاز الذي يتم الإعلان عنه.
باستخدام مفتاح فلاتر name
أو حتى بادئة بهذا الاسم مع namePrefix
مفتاح التصفية. لاحظ أنه في هذه الحالة، ستحتاج أيضًا إلى تحديد
مفتاح optionalServices
للوصول إلى أي خدمات غير مضمّنة في
فلتر الخدمة. إذا لم تفعل ذلك، ستظهر لك رسالة خطأ لاحقًا عند محاولة الوصول
معهم.
navigator.bluetooth.requestDevice({
filters: [{
name: 'Francois robot'
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
فلتر بيانات الشركة المصنّعة
من الممكن أيضًا طلب الأجهزة التي تتضمّن بلوتوث استنادًا إلى الشركة المصنِّعة
البيانات المحدّدة التي يتمّ الإعلان عنها باستخدام مفتاح فلاتر manufacturerData
. هذا المفتاح
عبارة عن مصفوفة من الكائنات التي تتضمن مفتاح معرّف شركة بلوتوث إلزاميًا باسم
companyIdentifier
ويمكنك أيضًا إدخال بادئة بيانات تتم فلترتها
بيانات الشركة المصنّعة من الأجهزة التي تتضمّن بلوتوث التي تبدأ بها. لاحظ أنك سوف
عليك أيضًا تحديد مفتاح optionalServices
ليتمكن من الوصول إلى أي خدمات
لم يتم تضمينها في فلتر الخدمة. إذا لم تفعل ذلك، ستتلقى رسالة خطأ لاحقًا عند
محاولة الوصول إليها.
// Filter Bluetooth devices from Google company with manufacturer data bytes
// that start with [0x01, 0x02].
navigator.bluetooth.requestDevice({
filters: [{
manufacturerData: [{
companyIdentifier: 0x00e0,
dataPrefix: new Uint8Array([0x01, 0x02])
}]
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
يمكن أيضًا استخدام القناع مع بادئة بيانات لمطابقة بعض الأنماط في بيانات الشركة المصنعة. يمكنك الاطّلاع على شرح حول فلاتر بيانات البلوتوث للتعرّف على كيفية إجراء ذلك. أخرى.
فلاتر الاستبعاد
يتيح الخيار exclusionFilters
في navigator.bluetooth.requestDevice()
استبعاد بعض الأجهزة من أداة اختيار المتصفّح يمكن استخدامه لاستبعاد
الأجهزة التي تطابق فلترًا أوسع نطاقًا، ولكنّها غير متوافقة
// Request access to a bluetooth device whose name starts with "Created by".
// The device named "Created by Francois" has been reported as unsupported.
navigator.bluetooth.requestDevice({
filters: [{
namePrefix: "Created by"
}],
exclusionFilters: [{
name: "Created by Francois"
}],
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
عدم استخدام الفلاتر
أخيرًا، بدلاً من filters
يمكنك استخدام المفتاح acceptAllDevices
لعرض كل
الأجهزة القريبة التي تتضمّن بلوتوث. عليك أيضًا تحديد optionalServices
.
لكي تتمكن من الوصول إلى بعض الخدمات. إذا لم تفعل ذلك، ستظهر لك رسالة خطأ لاحقًا
عند محاولة الوصول إليها.
navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
الاتصال بجهاز بلوتوث
إذًا، ما هي الخطوات التي يمكنك اتّباعها الآن بعد الحصول على BluetoothDevice
؟ دعونا نتواصل مع
خادم GATT عن بُعد عبر البلوتوث والذي يضم الخدمة والميزات
التعريفات.
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => {
// Human-readable name of the device.
console.log(device.name);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
قراءة إحدى خصائص البلوتوث
هنا نتصل بخادم GATT الخاص بجهاز البلوتوث البعيد. الآن نحن يريدون الحصول على خدمة GATT أساسية وقراءة إحدى الخصائص التي تنتمي إلى هذه الخدمة. ولنجرب، على سبيل المثال، قراءة مستوى الشحن الحالي بطارية الجهاز.
في المثال القادم، battery_level
هو مستوى البطارية الموحّد
سمة:
navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
.then(device => device.gatt.connect())
.then(server => {
// Getting Battery Service…
return server.getPrimaryService('battery_service');
})
.then(service => {
// Getting Battery Level Characteristic…
return service.getCharacteristic('battery_level');
})
.then(characteristic => {
// Reading Battery Level…
return characteristic.readValue();
})
.then(value => {
console.log(`Battery percentage is ${value.getUint8(0)}`);
})
.catch(error => { console.error(error); });
إذا كنت تستخدم ميزة Bluetooth GATT مخصصة، يمكنك تقديم إما
معرف فريد كامل للبلوتوث أو نموذج قصير 16 أو 32 بت
service.getCharacteristic
يُرجى ملاحظة أنه يمكنك أيضًا إضافة أداة معالجة حدث characteristicvaluechanged
على
مميزة للتعامل مع قراءة قيمتها. اطلع على خاصية القراءة
نموذج القيمة التي تم تغييرها لمعرفة كيفية التعامل بشكل اختياري مع GATT القادم
والإشعارات كذلك.
…
.then(characteristic => {
// Set up event listener for when characteristic value changes.
characteristic.addEventListener('characteristicvaluechanged',
handleBatteryLevelChanged);
// Reading Battery Level…
return characteristic.readValue();
})
.catch(error => { console.error(error); });
function handleBatteryLevelChanged(event) {
const batteryLevel = event.target.value.getUint8(0);
console.log('Battery percentage is ' + batteryLevel);
}
الكتابة إلى إحدى خصائص البلوتوث
الكتابة إلى خاصية Bluetooth GATT سهلة مثل قراءتها. هذه المرة، لنستخدم نقطة التحكم في معدل ضربات القلب لإعادة ضبط قيمة الطاقة المستهلَكة إلى 0 على جهاز مراقبة معدل ضربات القلب.
أعدك أنه ليس هناك سحر هنا. وقد تم شرح كل ذلك في مقالة التحكّم في معدّل نبضات القلب. صفحة "خصائص النقاط"
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
// Writing 1 is the signal to reset energy expended.
const resetEnergyExpended = Uint8Array.of(1);
return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
console.log('Energy expended has been reset.');
})
.catch(error => { console.error(error); });
تلقّي إشعارات GATT
والآن، لنرى كيف يتم إعلامك عند استخدام قياس معدل ضربات القلب التغييرات في الخصائص على الجهاز:
navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
characteristic.addEventListener('characteristicvaluechanged',
handleCharacteristicValueChanged);
console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });
function handleCharacteristicValueChanged(event) {
const value = event.target.value;
console.log('Received ' + value);
// TODO: Parse Heart Rate Measurement value.
// See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js
}
يعرض لك نموذج الإشعارات كيفية إيقاف الإشعارات باستخدام
stopNotifications()
وإزالة characteristicvaluechanged
المُضافة بشكل صحيح
أداة معالجة الأحداث.
إلغاء الربط بجهاز يتضمّن بلوتوث
لتوفير تجربة أفضل للمستخدم، قد تحتاج إلى الاستماع إلى أحداث قطع الاتصال وادعُ المستخدم لإعادة الاتصال:
navigator.bluetooth.requestDevice({ filters: [{ name: 'Francois robot' }] })
.then(device => {
// Set up event listener for when device gets disconnected.
device.addEventListener('gattserverdisconnected', onDisconnected);
// Attempts to connect to remote GATT Server.
return device.gatt.connect();
})
.then(server => { /* … */ })
.catch(error => { console.error(error); });
function onDisconnected(event) {
const device = event.target;
console.log(`Device ${device.name} is disconnected.`);
}
يمكنك أيضًا الاتصال بـ device.gatt.disconnect()
لإلغاء ربط تطبيق الويب
جهاز يتضمّن بلوتوث. سيؤدي ذلك إلى تشغيل حدث gattserverdisconnected
الحالي.
المستمعين. ملاحظة: لن يتم إيقاف الاتصال بجهاز البلوتوث إذا كانت هناك
يجري تطبيق اتصال بالفعل مع الجهاز الذي يتضمن بلوتوث. تحقق من الجهاز
إلغاء ربط النموذج ونموذج إعادة الربط التلقائي للاطّلاع على مزيد من التفاصيل
القراءة والكتابة إلى الأدوات الوصفية التي تعمل بالبلوتوث
واصفات GATT للبلوتوث هي سمات تصف قيمة مميزة. يمكنك قراءتها والكتابة إليها بطريقة مشابهة لـ Bluetooth GATT وسماتها الشخصية.
لنرَ مثلاً كيفية قراءة وصف المستخدم للقياس. الفاصل الزمني لميزان الحرارة المتعلّق بالصحة في الجهاز
في المثال أدناه، تشير health_thermometer
إلى خدمة ميزان الحرارة الصحي،
measurement_interval
خاصية فاصل القياس
gatt.characteristic_user_description
وصف المستخدم المميز
.
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => descriptor.readValue())
.then(value => {
const decoder = new TextDecoder('utf-8');
console.log(`User Description: ${decoder.decode(value)}`);
})
.catch(error => { console.error(error); });
والآن بعد أن قرأنا وصف المستخدم للفاصل الزمني للقياس لمقياس درجة الحرارة في جهازك، لنرَ كيفية تعديله ونكتب
navigator.bluetooth.requestDevice({ filters: [{ services: ['health_thermometer'] }] })
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('health_thermometer'))
.then(service => service.getCharacteristic('measurement_interval'))
.then(characteristic => characteristic.getDescriptor('gatt.characteristic_user_description'))
.then(descriptor => {
const encoder = new TextEncoder('utf-8');
const userDescription = encoder.encode('Defines the time between measurements.');
return descriptor.writeValue(userDescription);
})
.catch(error => { console.error(error); });
النماذج والعروض التوضيحية والدروس التطبيقية حول الترميز
تم بنجاح اختبار جميع نماذج البلوتوث على الويب أدناه. للاستمتاع بهذه الألعاب البيانات إلى أقصى حد، فأوصيك بتثبيت [BLE Peripheral Simulator تطبيق Android] الذي يحاكي جهازًا ملحقًا بتقنية البلوتوث المنخفض الطاقة (BLE) من خلال خدمة البطارية، وهي ميزة معدّل نبضات القلب أو خدمة ميزان حرارة صحي.
مبتدئ
- معلومات الجهاز: لاسترداد معلومات الجهاز الأساسية من جهاز BLE
- مستوى البطارية - لاسترداد معلومات البطارية من جهاز BLE الذي يعلن عن معلومات البطارية.
- إعادة ضبط الطاقة - إعادة ضبط الطاقة المستهلكة من جهاز BLE الذي يعلن عن معدّل نبضات القلب.
- الخصائص المميزة - تعرض جميع خصائص خاصية معينة من جهاز BLE.
- الإشعارات - لتشغيل الإشعارات المميزة وإيقافها من جهاز BLE.
- إلغاء ربط الجهاز: يمكنك قطع الاتصال وتلقّي إشعار بانقطاع اتصال جهاز BLE بعد الاتصال به.
- الحصول على الخصائص - الحصول على جميع خصائص الخدمة المُعلَن عنها من جهاز BLE.
- الحصول على كلمات وصفية - الحصول على جميع السمات الخدمة المُعلَن عنها من جهاز BLE.
- فلتر بيانات الشركة المصنّعة: يتيح استرداد معلومات الجهاز الأساسية من جهاز البلوتوث المنخفض الطاقة الذي يطابق بيانات الشركة المصنّعة.
- فلاتر الاستبعاد - استرداد معلومات الجهاز الأساسية من جهاز بلوتوث منخفض الطاقة (BLE) يضم فلاتر استبعاد أساسية.
الجمع بين عمليات متعددة
- خصائص GAP - الحصول على جميع خصائص GAP لجهاز BLE.
- خصائص معلومات الجهاز: يمكنك الحصول على جميع خصائص معلومات الجهاز الخاصة بجهاز BLE.
- فقدان الرابط - ضبط خاصية مستوى التنبيه لجهاز BLE (readValue &writeValue).
- اكتشاف الخدمات الخصائص: تعرَّف على جميع الخدمات الأساسية التي يمكن الوصول إليها وخصائصها من جهاز BLE.
- إعادة الاتصال التلقائي - إعادة الاتصال بجهاز BLE غير متصل باستخدام خوارزمية رقود أسي.
- قراءة تغيير قيمة الخصائص - يمكنك قراءة مستوى البطارية وتلقّي إشعارات بالتغييرات التي تطرأ على جهاز البلوتوث المنخفض الطاقة.
- قراءة الواصفات - قراءة جميع واصفات خصائص الخدمات من جهاز BLE.
- كتابة الواصف - اكتب إلى الواصف "وصف المستخدم المميز" على جهاز BLE.
يمكنك أيضًا الاطّلاع على عروض Web Bluetooth التجريبية التي تم تنظيمها والدروس التطبيقية الرسمية حول ترميز Web Bluetooth.
المكتبات
- web-bluetooth-utils عبارة عن وحدة npm تضيف بعض الوظائف الملائمة إلى واجهة برمجة التطبيقات.
- يتوفّر دعم واجهة برمجة التطبيقات Web Bluetooth API في noble، وهو الأكثر شيوعًا في Node.js BLE الوحدة المركزية. يتيح لك ذلك استخدام حزم الويب أو المتصفّحات الذكية بدون الحاجة إلى لخادم WebSocket أو مكوّنات إضافية أخرى.
- angular-web-bluetooth هي وحدة لتقنية Angular تمت إزالة جميع يجب أن يتوفّر نصّ نموذجي لإعداد Web Bluetooth API.
الأدوات
- بدء استخدام البلوتوث على الويب هو تطبيق ويب بسيط سينشئ جميع رمز JavaScript النموذجي لبدء التفاعل مع جهاز بلوتوث. أدخِل اسمًا للجهاز أو خدمة أو سمة وحدِّد خصائصه فأنت جاهز للبدء.
- إذا كنت مطوّر برامج بلوتوث في الوقت الحالي، يمكنك زيارة Web Bluetooth Developer Studio؟ سينشئ المكوِّن الإضافي أيضًا رمز JavaScript Web Bluetooth جهاز يتضمّن بلوتوث.
نصائح
تتوفر صفحة الإعدادات الداخلية للبلوتوث في Chrome على
about://bluetooth-internals
كي تتمكّن من فحص كل العناصر المجاورة
الأجهزة التي تتضمّن بلوتوث: الحالة والخدمات والخصائص والأدوات الوصفية
![لقطة شاشة للصفحة الداخلية لتصحيح أخطاء البلوتوث في Chrome](https://developer.chrome.google.cn/static/docs/capabilities/bluetooth/image/screenshot-the-internal-acdfb455a1a38.jpg?authuser=9&hl=ar)
أنصحك أيضًا بالاطّلاع على صفحة كيفية الإبلاغ عن أخطاء Web Bluetooth الرسمي. لأن تصحيح أخطاء البلوتوث قد يكون صعبًا في بعض الأحيان.
الخطوات التالية
تحقّق أولاً من حالة تنفيذ المتصفّح والنظام الأساسي لمعرفة الأجزاء. يتم حاليًا تنفيذ واجهة برمجة تطبيقات Web Bluetooth.
على الرغم من أنها لا تزال غير مكتملة، إليك نظرة سريعة على ما يمكن توقعه في المستقبل:
- جارٍ البحث عن إعلانات قريبة من البلوتوث المنخفض الطاقة (BLE)
سيحدث مع
navigator.bluetooth.requestLEScan()
. - سيتتبّع حدث
serviceadded
الجديد خدمات Bluetooth GATT التي تم اكتشافها مؤخرًا في حين أنّ حدث واحد (serviceremoved
) سيتتبّع الأحداث التي تمّت إزالتها.servicechanged
جديد فسيتم تنشيط الحدث عند إضافة أي خاصية و/أو واصف أو من خدمة Bluetooth GATT.
إظهار الدعم لواجهة برمجة التطبيقات
هل تخطط لاستخدام واجهة برمجة تطبيقات Web Bluetooth؟ يساعد دعمك العام فريق Chrome تحديد أولويات الميزات وإظهار لموردي المتصفحات الآخرين مدى أهمية دعمها.
إرسال تغريدة إلى @ChromiumDev باستخدام علامة التصنيف
#WebBluetooth
عليك إعلامنا بمكان تطبيقك وطريقة استخدامه
الموارد
- Stack Overflow
- حالة ميزات Chrome
- أخطاء "تنفيذ Chrome"
- مواصفات تقنية البلوتوث على الويب
- مشاكل في المواصفات على GitHub
- تطبيق BLE Peripheral Simulator
شكر وتقدير
نشكر كايس باسك على مراجعة هذه المقالة. صورة رئيسية بواسطة SparkFun Electronics من Bolder، الولايات المتحدة الأمريكية.