ایجاد یک دستگاه اینترنت اشیا با قابلیت وب با اینتل ادیسون

کنت کریستینسن
Kenneth Christiansen

اینترنت اشیا این روزها بر سر زبان ها افتاده است و برنامه نویسان و برنامه نویسانی مانند من را بسیار هیجان زده می کند. هیچ چیز جالب تر از این نیست که اختراعات خود را زنده کنید و بتوانید با آنها صحبت کنید!

اما دستگاه‌های اینترنت اشیا که برنامه‌هایی را نصب می‌کنند که به ندرت از آنها استفاده می‌کنید، می‌توانند آزاردهنده باشند، بنابراین ما از فناوری‌های وب آینده مانند Physical Web و Web Bluetooth بهره می‌بریم تا دستگاه‌های IoT را بصری‌تر و کمتر مداخله کنیم.

درخواست مشتری

وب و اینترنت اشیا، تطبیقی ​​برای بودن

هنوز موانع زیادی وجود دارد که باید قبل از اینکه اینترنت اشیا به موفقیت بزرگی دست پیدا کند، باید بر آنها غلبه کرد. یکی از موانع شرکت‌ها و محصولاتی است که افراد را ملزم می‌کنند تا برای هر دستگاهی که خریداری می‌کنند برنامه‌هایی را نصب کنند و تلفن‌های کاربران را با برنامه‌هایی که به ندرت استفاده می‌کنند شلوغ می‌کنند.

به همین دلیل، ما در مورد پروژه Physical Web بسیار هیجان‌زده هستیم، که به دستگاه‌ها اجازه می‌دهد URL را به یک وب‌سایت آنلاین به روشی غیر سرزده پخش کنند. در ترکیب با فناوری‌های نوظهور وب مانند Web Bluetooth ، Web USB و Web NFC ، سایت‌ها می‌توانند مستقیماً به دستگاه متصل شوند یا حداقل روش صحیح انجام این کار را توضیح دهند.

اگرچه در این مقاله عمدتاً بر روی بلوتوث وب تمرکز می‌کنیم، برخی موارد استفاده ممکن است برای Web NFC یا Web USB مناسب‌تر باشند. برای مثال، اگر به دلایل امنیتی نیاز به اتصال فیزیکی دارید، Web USB ترجیح داده می شود.

این وب سایت همچنین می تواند به عنوان یک برنامه وب پیشرو (PWA) عمل کند. ما خوانندگان را تشویق می کنیم تا توضیحات گوگل را در مورد PWA ها بررسی کنند. PWA ها سایت هایی هستند که تجربه کاربری شبیه به برنامه و واکنش گرا دارند، می توانند به صورت آفلاین کار کنند و می توانند به صفحه اصلی دستگاه اضافه شوند.

به عنوان اثبات مفهوم، من در حال ساخت یک دستگاه کوچک با استفاده از برد Intel® Edison Arduino بوده ام. این دستگاه دارای یک سنسور دما (TMP36) و همچنین یک محرک (کاتد LED رنگی) است. شماتیک های این دستگاه را می توانید در انتهای این مقاله بیابید.

تخته نان.

اینتل ادیسون محصول جالبی است زیرا می تواند یک توزیع کامل لینوکس* را اجرا کند. بنابراین من به راحتی می توانم آن را با استفاده از Node.js برنامه ریزی کنم. نصب‌کننده به شما امکان می‌دهد Intel* XDK را نصب کنید که شروع به کار را آسان می‌کند، اگرچه می‌توانید به صورت دستی نیز برنامه‌نویسی و در دستگاه خود آپلود کنید.

برای برنامه Node.js من به سه ماژول گره و همچنین وابستگی های آنها نیاز داشتم:

  • 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 از Physical Web پشتیبانی می‌کند، که به کروم اجازه می‌دهد URLهایی را که توسط دستگاه‌های اطراف پخش می‌شوند، ببیند. برخی الزامات وجود دارد که توسعه دهنده باید از آنها آگاه باشد، مانند نیاز به سایت ها برای دسترسی عمومی و استفاده از HTTPS.

پروتکل Eddystone دارای محدودیت اندازه 18 بایت در URL ها است. بنابراین برای اینکه URL برنامه آزمایشی من کار کند ( https://webbt-sensor-hub.appspot.com/ )، باید از کوتاه کننده URL استفاده کنم.

پخش URL بسیار ساده است. تنها کاری که برای انجام آن نیاز دارید، کتابخانه های مورد نیاز را وارد کرده و چند تابع را فراخوانی کنید. یکی از راه‌های انجام این کار این است که وقتی تراشه BLE روشن است، با advertiseUrl تماس بگیرید:

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

این واقعا نمی تواند ساده تر باشد. در تصویر زیر مشاهده می کنید که کروم دستگاه را به خوبی پیدا می کند.

کروم فانوس‌های وب فیزیکی نزدیک را معرفی می‌کند.
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 . اینها در بخش بعدی در مورد رابط با بلوتوث تعریف خواهند شد.

همانطور که ممکن است در نمونه شیء Thermometer متوجه شوید، من از طریق درگاه آنالوگ A0 با TMP36 صحبت می کنم. پایه های ولتاژ روی کاتد LED رنگی به پین ​​های دیجیتال 3، 5 و 6 متصل می شوند، که اتفاقاً پایه های مدولاسیون عرض پالس (PWM) در برد برک آوت ادیسون آردوینو هستند.

برد ادیسون

صحبت کردن با بلوتوث

صحبت کردن با بلوتوث نمی تواند بسیار ساده تر از آن چیزی که با noble است باشد.

در مثال زیر، ما دو ویژگی کم مصرف بلوتوث ایجاد می کنیم: یکی برای 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 نمونه را بگیرم و فقط زمانی که دما حداقل 1 درجه تغییر کند به روز رسانی ارسال کنم.

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 اضافه می کنم که از آن برای ذخیره شی LED Johnny-Five استفاده می کنم. من همچنین رنگ LED را روی مقدار پیش فرض آن تنظیم کردم (سفید، با نام مستعار #ffffff ).

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

متد «نوشتن» یک رشته دریافت می‌کند (درست مانند «خواندن» که یک رشته را ارسال می‌کند)، که می‌تواند از یک کد رنگی CSS تشکیل شده باشد (به عنوان مثال: نام‌های CSS مانند rebeccapurple یا کدهای هگز مانند #ff00bb ). من از یک ماژول گره به نام parse-color استفاده می کنم تا همیشه مقدار هگز را بدست بیاورم که همان چیزی است که جانی فایو انتظار دارد.

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 * را به عنوان مثال نشان دهیم. برنامه به دست آمده در زیر نشان داده شده است:

برنامه مشتری روی تلفن
پیغام خطا

سمت راست نسخه قبلی را نشان می دهد، که یک گزارش خطای ساده را نشان می دهد که من برای سهولت توسعه اضافه کردم.

بلوتوث وب برقراری ارتباط با دستگاه‌های کم‌انرژی بلوتوث را آسان می‌کند، بنابراین اجازه دهید نسخه ساده‌شده کد اتصال من را بررسی کنیم. اگر نمی دانید وعده ها چگونه کار می کنند، قبل از مطالعه بیشتر این منبع را بررسی کنید.

اتصال به یک دستگاه بلوتوث شامل زنجیره ای از وعده ها است. ابتدا دستگاه را فیلتر می کنیم (UUID: FC00 ، نام: Edison ). این یک دیالوگ را نمایش می دهد تا به کاربر اجازه دهد دستگاهی را که فیلتر داده شده را انتخاب کند. سپس به سرویس GATT متصل می شویم و سرویس اولیه و ویژگی های مرتبط را دریافت می کنیم و سپس مقادیر را می خوانیم و تماس های اعلان را تنظیم می کنیم.

نسخه ساده شده کد ما در زیر فقط با آخرین 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);
},

خلاصه

این بود مردمی! همانطور که می بینید، برقراری ارتباط با بلوتوث کم انرژی با استفاده از بلوتوث وب در سمت مشتری و Node.js در ادیسون بسیار آسان و بسیار قدرتمند است.

با استفاده از وب فیزیکی و بلوتوث وب، Chrome دستگاه را پیدا می‌کند و به کاربر این امکان را می‌دهد تا بدون نصب برنامه‌هایی که به ندرت استفاده می‌شوند و ممکن است کاربر نخواسته و ممکن است هر از چند گاهی به‌روزرسانی شوند، به راحتی به آن متصل شود.

نسخه ی نمایشی

می‌توانید از مشتری الهام بگیرد که چگونه می‌توانید برنامه‌های وب خود را برای اتصال به دستگاه‌های سفارشی اینترنت اشیا ایجاد کنید.

کد منبع

کد منبع در اینجا موجود است. به راحتی می توانید مشکلات را گزارش کنید یا وصله ارسال کنید.

طرح

اگر واقعاً ماجراجو هستید و می‌خواهید کاری را که من انجام داده‌ام تکرار کنید، به طرح ادیسون و برد برد زیر مراجعه کنید:

طرح