יצירת מכשיר IoT מותאם לאינטרנט באמצעות Intel Edison

Kenneth Christiansen
Kenneth Christiansen

כיום, האינטרנט של הדברים נמצא על פיהם של כולנו, והוא מלהיב מאוד מתכנתים כמוני. שום דבר לא יותר מגניב מההמצאות שלכם לחיים והיכולת לדבר איתן!

אבל מכשירי IoT שמתקינים אפליקציות שמשמשות לעיתים רחוקות עלולים להטריד אותם, ולכן אנחנו מנצלים את היתרונות של טכנולוגיות אינטרנט חדשות כמו האינטרנט הפיזי וה-Bluetooth, כדי שמכשירי IoT יהיו יותר אינטואיטיביים ופחות פולשניים.

אפליקציית לקוח

האינטרנט ו-IoT,

עדיין יש הרבה מכשולים שצריך להתגבר עליהם כדי שהאינטרנט של הדברים יהיה הצלחה אדירה. אחד המכשולים הוא החברות והמוצרים שמחייבים אנשים להתקין אפליקציות בכל מכשיר שהם רוכשים, וגורמים לעומס על הטלפונים של המשתמשים במגוון אפליקציות שבהן הם משתמשים לעיתים רחוקות.

לכן אנחנו נרגשים מאוד מהפרויקט של האינטרנט הווירטופיזי, שמאפשר למכשירים לשדר כתובת URL לאתר באינטרנט באופן שלא פולשני. בשילוב עם טכנולוגיות אינטרנט מתפתחות כמו Bluetooth באינטרנט, Web USB ו-Web NFC, האתרים יכולים להתחבר ישירות למכשיר או לפחות להסביר מה הדרך הנכונה לעשות זאת.

במאמר הזה אנחנו מתמקדים בעיקר ב-Bluetooth באינטרנט, אבל תרחישים מסוימים עשויים להתאים יותר ל-Web NFC או Web USB. לדוגמה, עדיף להשתמש ב-USB באינטרנט אם אתם צריכים חיבור פיזי מטעמי אבטחה.

האתר יכול גם לשמש כ-Progressive Web App (PWA). אנחנו ממליצים לקוראים לקרוא את ההסבר של Google על אפליקציות PWA. אפליקציות PWA הן אתרים עם חוויית משתמש רספונסיבית, דמוית אפליקציה, שיכולים לפעול אופליין ואפשר להוסיף אותם למסך הבית של המכשיר.

בתור הוכחת היתכנות, בניתי מכשיר קטן באמצעות הלוח המשני של Intel® Edison Arduino. המכשיר מכיל חיישן טמפרטורה (TMP36) וגם מפעיל (קתודה LED צבעוני). בסוף המאמר תוכלו למצוא את הסכימות של המכשיר הזה.

מטריצת חיבורים.

Intel Edison הוא מוצר מעניין, כי הוא יכול להריץ הפצה מלאה של Linux*. לכן אפשר לתכנת אותו בקלות באמצעות Node.js. מנהל ההתקנה מאפשר להתקין את Intel* XDK שקל להתחיל, למרות שאפשר לתכנת ולהעלות למכשיר גם באופן ידני.

לאפליקציית Node.js שלי נדרשו שלושה מודולים של צמתים, וכן יחסי התלות שלהם:

  • eddystone-beacon
  • parse-color
  • johnny-five

הראשון מתקין באופן אוטומטי את noble, זהו מודול הצמתים שבו אני משתמש כדי לדבר באמצעות Bluetooth עם צריכת אנרגיה נמוכה.

קובץ 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.

שידור כתובת האתר הוא די פשוט. כל מה שצריך לעשות הוא לייבא את הספריות הנדרשות ולהפעיל כמה פונקציות. אחת מהדרכים לעשות זאת היא לקרוא ל-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 מכריז על איתותי Bluetooth של האינטרנט הווירטופיזי בקרבת מקום.
כתובת ה-URL של אפליקציית האינטרנט מפורטת.

תקשורת עם החיישן/המפעיל

אנחנו משתמשים ב-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 שלמעלה כרגע. הם יוגדרו בקטע מאוחר יותר לגבי ממשק עם Bluetooth.

כמו שאולי תראו ביצירת אובייקט המדחום, אני מדבר אל ה-TMP36 דרך היציאה האנלוגית A0. רגלי המתח בקטודת ה-LED הצבעונית מחוברות לפינים הדיגיטליים 3, 5 ו-6, שהם הפינים לאפנון רוחב הדופק (PWM) בלוח המשני של Edison Arduino.

לוח אדיסון

מתבצע דיבור אל Bluetooth

קל יותר לדבר עם Bluetooth מאשר עם 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);
};

עבור 'שליחת התראה', אנחנו צריכים להוסיף שיטה לטיפול במינויים ובביטול מינויים. בגדול, אנחנו פשוט מאחסנים קריאה חוזרת (callback). כשיש לנו סיבה חדשה לטמפרטורה שאנחנו רוצים לשלוח, אנחנו מכנים את הקריאה החוזרת (callback) עם הערך החדש (מקודד כמתואר למעלה).

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 הצבעונית פשוטה יותר. האובייקט וגם השיטה 'קריאה' מוצגים בהמשך. המאפיין מוגדר לאפשר פעולות 'קריאה' ו'כתיבה', ויש לו מזהה ייחודי אוניברסלי (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 שמשמש לאחסון האובייקט Johnny-Five LED. הגדרתי גם את הצבע של נורית ה-LED לערך ברירת המחדל (לבן, כלומר #ffffff).

board.on("ready", function() {
    ...
    colorCharacteristic._led = led;
    led.color(colorCharacteristic._value);
    led.intensity(30);
    ...
}

השיטה 'כתיבה' מקבלת מחרוזת (בדיוק כמו ש-'קריאה' שולחת מחרוזת), שיכולה לכלול קוד צבע CSS (לדוגמה: שמות CSS כמו rebeccapurple או קודים הקסדצימליים של #ff00bb). אני משתמש במודול צומת בשם parse-color כדי לקבל תמיד את הערך ההקסדצימלי שהוא מה ש-Jonny-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
        ]
        })
    ]);
});

יצירה של אפליקציית האינטרנט של הלקוח

בלי להיכשל יותר מדי פעמים בנוגע לאופן הפעולה של החלקים של אפליקציית הלקוח ללא Bluetooth, אפשר להדגים ממשק משתמש רספונסיבי שנוצר ב-Polymer*. האפליקציה שתתקבל מוצגת כאן:

אפליקציית לקוח בטלפון.
הודעת שגיאה.

בצד ימין מוצגת גרסה קודמת, שמציגה יומן שגיאות פשוט שהוספתי כדי להקל על הפיתוח.

עם Bluetooth באינטרנט קל לתקשר עם מכשירי Bluetooth עם צריכת אנרגיה נמוכה, אז נעבור לגרסה פשוטה של קוד החיבור שלי. לא יודעים איך פועלות ההבטחות? כדאי לקרוא את המאמר הזה לפני שממשיכים.

התחברות למכשיר Bluetooth כרוכה בשרשרת של הבטחות. תחילה אנחנו מסננים לפי המכשיר (UUID: FC00, שם: Edison). תוצג תיבת דו-שיח שבה המשתמש יכול לבחור את המכשיר שעבורו ניתנה המסנן. אחר כך אנחנו מתחברים לשירות GATT, מקבלים את השירות הראשי ואת המאפיינים המשויכים אליו. לאחר מכן אנחנו קוראים את הערכים ומגדירים קריאה חוזרת (callback) של התראות.

הגרסה הפשוטה של הקוד שלמטה פועלת רק עם הגרסה העדכנית ביותר של Web Bluetooth API, ולכן יש צורך ב-Chrome פיתוח (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 באמצעות Bluetooth Web בצד הלקוח ו-Node.js ב-Edison היא די קלה ועוצמתית.

באמצעות האינטרנט הווירטופיזי וה-Bluetooth באינטרנט, Chrome מוצא את המכשיר ומאפשר למשתמש להתחבר אליו בקלות בלי להתקין אפליקציות שנמצאות בשימוש נדיר, שייתכן שהמשתמש לא מעוניין בהן. האפליקציות האלו עשויות להתעדכן מעת לעת.

הדגמה (דמו)

תוכלו לנסות את הלקוח כדי לקבל השראה לגבי האופן שבו תוכלו ליצור אפליקציות אינטרנט משלכם כדי להתחבר למכשירים המותאמים אישית של Internet of Things.

קוד מקור

קוד המקור זמין כאן. אפשר לדווח על בעיות ולשלוח תיקונים.

מערכונים

אם אתם הרפתקנים ורוצים לשחזר את מה שעשיתי, קראו למטה את הרישום של אדיסון ומטריצת חיבורים:

מערכונים