إنشاء جهاز إنترنت الأشياء (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 لتطبيقات الويب المتقدّمة. تطبيقات الويب التقدّمية هي مواقع إلكترونية توفّر تجربة مستخدم سريعة الاستجابة تشبه تجربة استخدام التطبيقات، ويمكنها العمل بلا إنترنت ويمكن إضافتها إلى الشاشة الرئيسية للجهاز.

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

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

كما قد تلاحظ في إنشاء عنصر Thermometer، أتواصل مع 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 أدناه. تم ضبط السمة للسماح بعمليات "القراءة" و "الكتابة" ، ولديها معرّف 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');

ما علينا الآن سوى أن يُعلِن عن جهازنا (المعرّف الفريد العالمي) و خصائصه (المعرّفات الفريدة العالمية الأخرى).

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);
},

ملخّص

هذا كل شيء. كما ترون، فإن الاتصال بـ Bluetooth Low Energy باستخدام Web Bluetooth من جهة العميل وNode.js على Edison سهل جدًا وقوي جدًا.

باستخدام ميزتَي "الشبكة المادية" و"البلوتوث على الويب"، يعثر Chrome على الجهاز ويسمح للمستخدم بالاتصال به بسهولة بدون تثبيت تطبيقات نادرة الاستخدام قد لا يريدها المستخدم، والتي قد يتم تحديثها من حين لآخر.

عرض توضيحي

يمكنك تجربة الإصدار المخصّص للعملاء للحصول على الإلهام بشأن كيفية إنشاء تطبيقات الويب الخاصة بك للربط بأجهزة إنترنت الأشياء المخصّصة.

رمز مصدر

يتوفّر رمز المصدر هنا. يُرجى عدم التردد في الإبلاغ عن المشاكل أو إرسال الإصلاحات.

رسم

إذا كنت من المغامرين حقًا وتريد تكرار ما فعلته، يُرجى الرجوع إلى رسم لوحة التجارب وجهاز Edison أدناه:

رسم