إنشاء جهاز إنترنت الأشياء (IoT) يمكن استخدامه على الويب باستخدام Intel Edison

Kenneth Christiansen
Kenneth Christiansen

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

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

تطبيق العميل

الويب وإنترنت الأشياء، ثنائي مثالي

لا تزال هناك الكثير من العقبات التي يجب التغلب عليها قبل أن يحقّق إنترنت�الأشياء نجاحًا كبيرًا. تتمثل إحدى العقبات في الشركات والمنتجات التي تتطلب من الأشخاص تثبيت تطبيقات لكل جهاز يشترونه، ما يؤدي إلى ازدحام هواتف المستخدمين بالعديد من التطبيقات التي يستخدمونها نادرًا.

ولهذا السبب، نحن متحمسون للغاية بشأن مشروع الشبكة المادية التي تتيح للأجهزة بث عنوان URL إلى موقع إلكتروني على الإنترنت بطريقة غير تطفلية. وباستخدام تقنيات الويب الناشئة، مثل Web Bluetooth و Web USB و Web NFC، يمكن للمواقع الإلكترونية الاتصال بالجهاز مباشرةً أو على الأقل توضيح الطريقة المناسبة للقيام بذلك.

على الرغم من أنّنا نركّز في هذه المقالة على Web Bluetooth بشكل أساسي، قد تكون بعض حالات الاستخدام مناسبة بشكل أفضل لاستخدام Web NFC أو Web USB. على سبيل المثال، يُفضَّل استخدام Web USB إذا كنت بحاجة إلى اتصال فعلي لأسباب تتعلّق بالحماية.

يمكن أن يعمل الموقع الإلكتروني أيضًا كتطبيق ويب تقدّمي (PWA). ننصحك بالاطّلاع على شرح Google لتطبيقات الويب المتقدّمة. تطبيقات الويب التقدّمية (PWA) هي مواقع إلكترونية تقدّم تجربة مستخدم متجاوبة تشبه التطبيق ويمكنها العمل بلا اتصال بالإنترنت ويمكن إضافتها إلى الشاشة الرئيسية للجهاز.

كدليل على صحة الفكرة، كنت أُنشئ جهازًا صغيرًا باستخدام لوحة 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 حدًا أقصى لحجم (18 بايت) على عناوين URL. لذا لكي يعمل عنوان 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 يعثر على الجهاز بشكل جيد.

يُعلِن Chrome عن إشارات الشبكة المادية المجاورة.
أن يكون عنوان URL لتطبيق الويب مُدرَجًا

الاتصال بجهاز الاستشعار/المشغِّل

نحن نستعين بـ 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 المذكورة أعلاه في الوقت الحالي، وسيتم تحديدها في القسم التالي عن التفاعل مع البلوتوث.

كما قد تلاحظ في إنشاء مثيل كائن "ميزان الحرارة"، أتحدث إلى TMP36 عبر منفذ A0 التناظري. يتم توصيل أرجل الجهد الكهربي في الكاثود الملوّن للإضاءة LED بالدبابيس الرقمية 3 و5 و6، والتي تُعدّ بدورها دبابيس التحكّم في عرض النبضة (PWM) على لوحة اختبار برمجة Arduino في Edison.

لوحة 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 أدناه. يتم ضبط الخاصية للسماح بإجراءات "القراءة" و"الكتابة" ولديها معرّف فريد عالمي مختلف عن خاصية درجة الحرارة.

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 أدناه:

رسم