يُجري الجميع هذه الأيام الكثير من الأبحاث حول إنترنت الأشياء، ويُسعدهم معرفة كل ما يتعلّق به، ويُسعدني أيضًا معرفة كل ما يتعلّق به بصفتي أحد المبرمجين. ما مِن شيءٍ أروع من تحويل اختراعاتك إلى واقع و التحدّث إليها.
ولكن قد تكون أجهزة إنترنت الأشياء التي تُثبِّت تطبيقات نادرًا ما تستخدمها مزعجة، لذلك نستفيد من تكنولوجيات الويب القادمة، مثل الويب العميق وWeb Bluetooth، لجعل أجهزة إنترنت الأشياء أكثر سهولة وأقل تدخلاً.

الويب وإنترنت الأشياء، ثنائي مثالي
لا تزال هناك الكثير من العقبات التي يجب التغلب عليها قبل أن يحقّق إنترنت الأشياء نجاحًا كبيرًا. ومن بين العقبات التي تواجه المستخدمين الشركات والمنتجات التي تشترط تثبيت التطبيقات على كل جهاز يشترونه، ما يؤدي إلى تشويش هواتف المستخدمين بالعديد من التطبيقات التي نادرًا ما يستخدمونها.
لهذا السبب، نحن متحمّسون جدًا بشأن مشروع الويب المادي الذي يسمح للأجهزة ببث عنوان URL إلى موقع إلكتروني على الإنترنت، وذلك بطريقة غير مزعجة. وباستخدام تقنيات الويب الناشئة، مثل Web Bluetooth و Web USB و Web NFC، يمكن للمواقع الإلكترونية الاتصال بالجهاز مباشرةً أو على الأقل توضيح الطريقة المناسبة للقيام بذلك.
على الرغم من أنّنا نركّز في هذه المقالة على Web Bluetooth بشكل أساسي، قد تكون بعض حالات الاستخدام مناسبة بشكل أفضل لاستخدام Web NFC أو Web USB. على سبيل المثال، يُفضَّل استخدام Web USB إذا كنت بحاجة إلى اتصال مادي لأغراض تتعلّق بالحماية.
يمكن أن يعمل الموقع الإلكتروني أيضًا كتطبيق ويب تقدّمي (PWA). ننصحك بالاطّلاع على شرح Google لتطبيقات الويب المتقدّمة. تطبيقات الويب التقدّمية هي مواقع إلكترونية تقدّم تجربة مستخدم سريعة الاستجابة تشبه تجربة استخدام التطبيقات، ويمكنها العمل بلا إنترنت ويمكن إضافتها إلى الشاشة الرئيسية للجهاز.
كدليل على صحة الفكرة، كنت أُنشئ جهازًا صغيرًا باستخدام لوحة Intel® Edison Arduino breakout board. يحتوي الجهاز على جهاز استشعار درجة الحرارة (TMP36) بالإضافة إلى مشغل (cathode) مصابيح LED الملوّنة. يمكن العثور على المخططات البيانية لهذا الجهاز في نهاية هذه المقالة.

يُعدّ Intel Edison منتجًا مثيرًا للاهتمام لأنّه يمكنه تشغيل إصدار كامل من Linux* . وبالتالي يمكنني برمجتها بسهولة باستخدام Node.js. يتيح لك مُثبِّت تثبيت Intel* XDK الذي يسهّل عليك بدء الاستخدام، مع أنّه يمكنك أيضًا البرمجة وتحميلها على جهازك يدويًا.
بالنسبة إلى تطبيق Node.js، كنت بحاجة إلى ثلاث وحدات node، بالإضافة إلى التبعيات التالية:
eddystone-beacon
parse-color
johnny-five
يُثبِّت الإصدار الأول تلقائيًا noble
، وهي وحدة العقدة
التي أستخدمها للتحدث عبر تقنية "طاقة البلوتوث المنخفضة".
يظهر ملف package.json
للمشروع على النحو التالي:
{
"name": "edison-webbluetooth-demo-server",
"version": "1.0.0",
"main": "main.js",
"engines": {
"node": ">=0.10.0"
},
"dependencies": {
"eddystone-beacon": "^1.0.5",
"johnny-five": "^0.9.30",
"parse-color": "^1.0.0"
}
}
الإعلان عن الموقع الإلكتروني
بدءًا من الإصدار 49، يتيح متصفّح Chrome على Android ميزة "الويب المادي" التي تسمح لمتصفّح Chrome بالاطّلاع على عناوين URL التي تبثّها الأجهزة المحيطة به. هناك بعض المتطلبات التي يجب أن يكون المطوّر على دراية بها، مثل الحاجة إلى أن تكون المواقع الإلكترونية متاحة للجميع وأن تستخدم بروتوكول HTTPS.
يفرض بروتوكول Eddystone حدًا أقصى لحجم عناوين URL يبلغ 18 بايت. لذا، لكي يعمل عنوان URL لتطبيقي التجريبي (https://webbt-sensor-hub.appspot.com/)، عليّ استخدام خدمة تقصير عناوين URL.
إنّ بث عنوان URL أمر بسيط للغاية. ما عليك سوى استيراد
المكتبات المطلوبة
واستخدام بعض الدوالّ. تتمثل إحدى طرق إجراء ذلك في الاتصال برقم advertiseUrl
عندما تكون شريحة BLE مفعَّلة:
var beacon = require("eddystone-beacon");
var bleno = require('eddystone-beacon/node_modules/bleno');
bleno.on('stateChange', function(state) {
if (state === 'poweredOn') {
beacon.advertiseUrl("https://goo.gl/9FomQC", {name: 'Edison'});
}
}
لا يمكن أن يكون الأمر أسهل من ذلك. يمكنك الاطّلاع في الصورة أدناه على أنّ Chrome يعثر على الجهاز بشكل جيد.


التواصل مع أداة الاستشعار/المشغِّل
نستخدم Johnny-Five* للتواصل مع فريقنا بشأن التحسينات. يحتوي Johnny-Five على واجهة برمجة تطبيقات رائعة للتواصل مع TMP36 الاستشعار.
يمكنك العثور أدناه على الرمز البرمجي البسيط للحصول على إشعارات بتغييرات درجة الحرارة، بالإضافة إلى ضبط لون مصباح LED الأوّلي.
var five = require("johnny-five");
var Edison = require("edison-io");
var board = new five.Board({
io: new Edison()
});
board.on("ready", function() {
// Johnny-Five's Led.RGB class can be initialized with
// an array of pin numbers in R, G, B order.
// Reference: http://johnny-five.io/api/led.rgb/#parameters
var led = new five.Led.RGB([ 3, 5, 6 ]);
// Johnny-Five's Thermometer class provides a built-in
// controller definition for the TMP36 sensor. The controller
// handles computing a Celsius (also Fahrenheit & Kelvin) from
// a raw analog input value.
// Reference: http://johnny-five.io/api/thermometer/
var temp = new five.Thermometer({
controller: "TMP36",
pin: "A0",
});
temp.on("change", function() {
temperatureCharacteristic.valueChange(this.celsius);
});
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
});
يمكنك تجاهل متغيّرات *Characteristic
المذكورة أعلاه في الوقت الحالي، وسيتم تحديدها في القسم التالي عن التفاعل مع البلوتوث.
كما قد تلاحظ في إنشاء عنصر Thermometer، أتواصل مع
TMP36 عبر منفذ A0
التناظري. يتم توصيل أرجل الجهد الكهربي في الكاثود الملوّن للإضاءة LED بالدبابيس الرقمية 3 و5 و6، والتي تُعدّ بدورها دبابيس التحكّم في عرض النبضة (PWM) على لوحة التوسيع الخاصة بوحدة Arduino في Edison.

التحدّث عبر البلوتوث
لا يمكن أن يكون التحدّث إلى البلوتوث أسهل من ذلك الذي يقدّمه لك noble
.
في المثال التالي، ننشئ سمتَين لخدمة Bluetooth المنخفضة الطاقة: واحدة لمصباح LED وأخرى لجهاز استشعار درجة الحرارة. يسمح لنا الخيار الأول بقراءة لون مصباح LED الحالي وضبط لون جديد. يسمح لنا هذا الأخير بالاشتراك في أحداث تغيُّر درجة الحرارة.
باستخدام noble
، يمكنك إنشاء سمة بسهولة. ما عليك سوى تحديد كيفية تواصل السمة وتحديد معرّف UUID. تشمل خيارات
التواصل القراءة أو الكتابة أو الإشعار أو أي مجموعة من هذه الخيارات.
وأسهل طريقة لإجراء ذلك هي إنشاء عنصر جديد والاستفادة من ميزات
bleno.Characteristic
.
يظهر عنصر السمة الناتج على النحو التالي:
var TemperatureCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0a',
properties: ['read', 'notify'],
value: null
});
this._lastValue = 0;
this._total = 0;
this._samples = 0;
this._onChange = null;
};
util.inherits(TemperatureCharacteristic, bleno.Characteristic);
نُخزّن قيمة درجة الحرارة الحالية في المتغيّر this._lastValue
. نحتاج إلى إضافة طريقة onReadRequest
وتشفير القيمة
لكي تعمل عملية "القراءة".
TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(8);
data.writeDoubleLE(this._lastValue, 0);
callback(this.RESULT_SUCCESS, data);
};
بالنسبة إلى "الإشعار"، نحتاج إلى إضافة طريقة للتعامل مع الاشتراكات والإلغاء. في الأساس، نحفظ طلب معاودة الاتصال. عندما يكون لدينا سبب جديد لدرجة الحرارة نريد إرساله، نُجري بعد ذلك مكالمة برمجية لتلك القيمة الجديدة (المشفَّرة كما هو موضّح أعلاه).
TemperatureCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) {
console.log("Subscribed to temperature change.");
this._onChange = updateValueCallback;
this._lastValue = undefined;
};
TemperatureCharacteristic.prototype.onUnsubscribe = function() {
console.log("Unsubscribed to temperature change.");
this._onChange = null;
};
بما أنّ القيم يمكن أن تتغيّر قليلاً، علينا تخفيف القيم التي نحصل عليها من أداة استشعار TMP36. اخترت ببساطة احتساب متوسط 100 عيّنة وإرسال آخر الأخبار فقط عند تغيُّر درجة الحرارة بقيمة درجة واحدة على الأقل.
TemperatureCharacteristic.prototype.valueChange = function(value) {
this._total += value;
this._samples++;
if (this._samples < NO_SAMPLES) {
return;
}
var newValue = Math.round(this._total / NO_SAMPLES);
this._total = 0;
this._samples = 0;
if (this._lastValue && Math.abs(this._lastValue - newValue) < 1) {
return;
}
this._lastValue = newValue;
console.log(newValue);
var data = new Buffer(8);
data.writeDoubleLE(newValue, 0);
if (this._onChange) {
this._onChange(data);
}
};
كان ذلك جهاز استشعار الحرارة. مصابيح LED الملونة هي أبسط. يظهر العنصر وطريقة read أدناه. تم ضبط السمة للسماح بعمليات "القراءة" و "الكتابة" ، ولديها معرّف UUID مختلف عن سمة درجة الحرارة.
var ColorCharacteristic = function() {
bleno.Characteristic.call(this, {
uuid: 'fc0b',
properties: ['read', 'write'],
value: null
});
this._value = 'ffffff';
this._led = null;
};
util.inherits(ColorCharacteristic, bleno.Characteristic);
ColorCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(this._value);
callback(this.RESULT_SUCCESS, data);
};
للتحكّم في مصباح LED من العنصر، أضِف عضوًا في
this._led
أستخدمه لتخزين عنصر
مصباح LED في Johnny-Five. ضبطنا أيضًا لون مصباح LED على قيمة
التلقائية (الأبيض، ويُعرف أيضًا باسم #ffffff
).
board.on("ready", function() {
...
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
...
}
تتلقّى طريقة write سلسلة (تمامًا مثل إرسال read لسلسلة)، والتي يمكن أن تتألف من رمز لون CSS (على سبيل المثال، أسماء CSS
مثل rebeccapurple
أو الرموز الست عشرية مثل #ff00bb
). أستخدِم وحدة node
تُسمى parse-color
للحصول دائمًا على القيمة الست عشرية التي يتوقّعها Johnny-Five.
ColorCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) {
var value = parse(data.toString('utf8')).hex;
if (!value) {
callback(this.RESULT_SUCCESS);
return;
}
this._value = value;
console.log(value);
if (this._led) {
this._led.color(this._value);
}
callback(this.RESULT_SUCCESS);
};
لن تعمل كل الإجراءات السابقة إذا لم نُدرِج وحدة bleno.
لن تعمل أداة eddystone-beacon
مع bleno ما لم تستخدم إصدار noble
الموزَّع معها. لحسن الحظ، يمكنك إجراء ذلك بسهولة:
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
ما علينا الآن سوى أن يعلن عن جهازنا (معرّف UUID) و خصائصه (معرّفات UUID الأخرى).
bleno.on('advertisingStart', function(error) {
...
bleno.setServices([
new bleno.PrimaryService({
uuid: 'fc00',
characteristics: [
temperatureCharacteristic, colorCharacteristic
]
})
]);
});
إنشاء تطبيق الويب الخاص بالعميل
بدون الخوض في الكثير من التفاصيل حول كيفية عمل أجزاء تطبيق العميل غير المرتبطة بالبلوتوث، يمكننا توضيح واجهة مستخدم متجاوبة تم إنشاؤها في Polymer* كمثال. يظهر التطبيق الناتج أدناه:


يعرض الجانب الأيمن إصدارًا سابقًا يعرض سجلّ أخطاء بسيطًا أضفته لتسهيل عملية التطوير.
تسهِّل تقنية Web Bluetooth التواصل مع الأجهزة التي تتضمّن تقنية طاقة البلوتوث المنخفضة، لذا لنلقِ نظرة على نسخة مبسّطة من رمز الاتصال. إذا لم تكن تعرف آلية عمل الوعود، اطّلِع على هذا المرجع قبل المتابعة.
يتضمن الاتصال بجهاز يتضمّن بلوتوث سلسلة من الوعود.
أولاً، نفلتر البيانات حسب الجهاز (UUID: FC00
، الاسم: Edison
). يؤدي ذلك إلى
عرض مربّع حوار للسماح للمستخدم باختيار الجهاز وفقًا للفلتر. بعد ذلك، نتصل بخدمة GATT ونحصل على
الخدمة الأساسية والسمات المرتبطة بها، ثم نقرأ
القيم ونُعدّ طلبات استدعاء الإشعارات.
لا يعمل الإصدار المبسّط من الرمز البرمجي أدناه إلا مع أحدث إصدار من Web Bluetooth API، وبالتالي يتطلّب Chrome Dev (M49) على Android.
navigator.bluetooth.requestDevice({
filters: [{ name: 'Edison' }],
optionalServices: [0xFC00]
})
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService(0xFC00))
.then(service => {
let p1 = () => service.getCharacteristic(0xFC0B)
.then(characteristic => {
this.colorLedCharacteristic = characteristic;
return this.readLedColor();
});
let p2 = () => service.getCharacteristic(0xFC0A)
.then(characteristic => {
characteristic.addEventListener(
'characteristicvaluechanged', this.onTemperatureChange);
return characteristic.startNotifications();
});
return p1().then(p2);
})
.catch(err => {
// Catch any error.
})
.then(() => {
// Connection fully established, unless there was an error above.
});
إنّ قراءة سلسلة وكتابتها من DataView
/ ArrayBuffer
(ما يستخدمه
WebBluetooth API) هي عملية سهلة تمامًا مثل استخدام Buffer
من جانب
Node.js. ما عليك سوى استخدام TextEncoder
وTextDecoder
:
readLedColor: function() {
return this.colorLedCharacteristic.readValue()
.then(data => {
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let decoder = new TextDecoder("utf-8");
let decodedString = decoder.decode(data);
document.querySelector('#color').value = decodedString;
});
},
writeLedColor: function() {
let encoder = new TextEncoder("utf-8");
let value = document.querySelector('#color').value;
let encodedString = encoder.encode(value.toLowerCase());
return this.colorLedCharacteristic.writeValue(encodedString);
},
من السهل أيضًا التعامل مع الحدث characteristicvaluechanged
لجهاز استشعار
درجة الحرارة:
onTemperatureChange: function(event) {
let data = event.target.value;
// In Chrome 50+, a DataView is returned instead of an ArrayBuffer.
data = data.buffer ? data : new DataView(data);
let temperature = data.getFloat64(0, /*littleEndian=*/ true);
document.querySelector('#temp').innerHTML = temperature.toFixed(0);
},
ملخّص
هذا كل شيء يا رفاق. كما ترى، فإنّ التواصل مع تقنية البلوتوث منخفضة الطاقة باستخدام Web Bluetooth من جهة العميل وNode.js من جهة Edison أمر سهل للغاية وفعّال جدًا.
باستخدام ميزتَي "الشبكة المادية" و"البلوتوث على الويب"، يعثر Chrome على الجهاز ويسمح للمستخدم بالاتصال به بسهولة بدون تثبيت تطبيقات نادرة الاستخدام قد لا يريدها المستخدم، والتي قد يتم تحديثها من حين لآخر.
عرض توضيحي
يمكنك تجربة التطبيق العميل للحصول على الإلهام بشأن كيفية إنشاء تطبيقات الويب الخاصة بك للاتصال بأجهزة إنترنت الأشياء المخصّصة.
رمز مصدر
يتوفّر رمز المصدر هنا. يُرجى عدم التردد في الإبلاغ عن المشاكل أو إرسال الإصلاحات.
رسم
إذا كنت من المغامرين حقًا وتريد تكرار ما فعلته، يُرجى الرجوع إلى رسم لوحة التجارب وجهاز Edison أدناه: