การสร้างอุปกรณ์ IoT ที่เปิดใช้เว็บได้ด้วย Intel Edison

Kenneth Christiansen
Kenneth Christiansen

อินเทอร์เน็ตของสรรพสิ่งเป็นหัวข้อที่ทุกคนพูดถึงกันในปัจจุบัน ซึ่งทำให้นักทดลองและโปรแกรมเมอร์อย่างเราตื่นเต้นมาก ไม่มีอะไรเจ๋งไปกว่าการทำให้สิ่งประดิษฐ์ของคุณมีชีวิตขึ้นมาและพูดคุยกับสิ่งประดิษฐ์เหล่านั้น

แต่อุปกรณ์ IoT ที่ติดตั้งแอปที่คุณไม่ค่อยได้ใช้อาจสร้างความรำคาญได้ เราจึงใช้ประโยชน์จากเทคโนโลยีเว็บที่กำลังจะเกิดขึ้น เช่น Physical Web และ Web Bluetooth เพื่อทำให้อุปกรณ์ IoT ใช้งานง่ายขึ้นและไม่รบกวน

แอปพลิเคชันไคลเอ็นต์

เว็บและ IoT ที่ทุกคนต้องมี

ยังคงมีอุปสรรคอีกมากมายที่ต้องฝ่าฟันก่อนที่อินเทอร์เน็ตออฟธิงส์จะประสบความสำเร็จอย่างยิ่งใหญ่ อุปสรรคหนึ่งคือบริษัทและผลิตภัณฑ์ ที่บังคับให้ผู้ใช้ติดตั้งแอปสำหรับอุปกรณ์แต่ละเครื่องที่ซื้อ ซึ่งทำให้โทรศัพท์ของผู้ใช้มีแอปมากมายที่ไม่ค่อยได้ใช้

ด้วยเหตุนี้ เราจึงตื่นเต้นมากกับโปรเจ็กต์ Physical Web ซึ่งช่วยให้อุปกรณ์สามารถออกอากาศ URL ไปยังเว็บไซต์ออนไลน์โดยไม่รบกวน เว็บไซต์สามารถเชื่อมต่อกับอุปกรณ์ได้โดยตรงหรืออย่างน้อยจะอธิบายวิธีการที่เหมาะสมในการจับคู่กับเทคโนโลยีเว็บใหม่ๆ เช่น เว็บบลูทูธ, Web USB และ Web NFC

แม้ว่าเราจะเน้นที่ Web Bluetooth เป็นหลักในบทความนี้ แต่ Use Case บางรายการอาจเหมาะกับ Web NFC หรือ Web USB มากกว่า เช่น แนะนำให้ใช้ Web USB หากคุณต้องใช้การเชื่อมต่อแบบใช้สายเพื่อเหตุผลด้านความปลอดภัย

เว็บไซต์ยังทำหน้าที่เป็น Progressive Web App (PWA) ได้อีกด้วย เราขอแนะนำให้ผู้อ่านอ่านคำอธิบายของ Google เกี่ยวกับ PWA PWA คือเว็บไซต์ที่มอบประสบการณ์การใช้งานที่ปรับเปลี่ยนตามอุปกรณ์และคล้ายกับแอปของผู้ใช้ สามารถทำงานแบบออฟไลน์และเพิ่มลงในหน้าจอหลักของอุปกรณ์ได้

เราได้สร้างอุปกรณ์ขนาดเล็กโดยใช้บอร์ด Breakout Arduino ของ Intel® Edison เพื่อพิสูจน์แนวคิด อุปกรณ์นี้มีเซ็นเซอร์อุณหภูมิ (TMP36) และแอคชูเอเตอร์ (แคโทด LED สี) สคีมาของอุปกรณ์นี้จะอยู่ในตอนท้ายของบทความนี้

แผงวงจรทดลอง

Intel Edison เป็นผลิตภัณฑ์ที่น่าสนใจเนื่องจากสามารถเรียกใช้ Linux* ได้อย่างเต็มรูปแบบ ดังนั้นฉันจึงเขียนโปรแกรมโดยใช้ Node.js ได้อย่างง่ายดาย โปรแกรมติดตั้งให้คุณติดตั้ง Intel* XDK ซึ่งจะช่วยให้คุณเริ่มต้นใช้งานได้อย่างง่ายดาย แต่คุณสามารถตั้งโปรแกรมและอัปโหลดไปยังอุปกรณ์ของคุณได้ด้วยตนเองเช่นกัน

สําหรับแอป Node.js ฉันต้องใช้โมดูล Node 3 รายการ รวมถึงข้อกําหนดต่อไปนี้

  • 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 เพื่อให้ URL ของแอปเดโมใช้งานได้ (https://webbt-sensor-hub.appspot.com/)

การออกอากาศ URL นั้นค่อนข้างง่าย คุณเพียงแค่ต้องนำเข้าไลบรารีที่จำเป็น และเรียกใช้ฟังก์ชัน 2-3 รายการ วิธีหนึ่งในการดำเนินการนี้คือเรียกใช้ 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 เปิดตัวบีคอน Physical Web ที่อยู่ใกล้เคียง
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) บนบอร์ด Breakout ของ Arduino สำหรับ Edison

บอร์ด Edison

การพูดคุยกับบลูทูธ

การสื่อสารกับบลูทูธนั้นง่ายกว่าที่เคยด้วย noble

ในตัวอย่างต่อไปนี้ เราสร้างลักษณะเฉพาะของบลูทูธพลังงานต่ำ 2 อย่าง คือแบบหนึ่งสำหรับไฟ 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 สีจะใช้งานง่ายกว่า ออบเจ็กต์และเมธอด "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 หรือรหัสเลขฐาน 16 เช่น #ff00bb) ฉันใช้โมดูลโหนดชื่อ parse-color เพื่อให้ได้ค่าเลขฐาน 16 ตามที่ 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 เวอร์ชันที่มาพร้อมกับ bleno โชคดีที่การดำเนินการนี้ทำได้ง่ายมาก เพียงทำดังนี้

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 และรับบริการหลักและลักษณะที่เชื่อมโยง จากนั้นอ่านค่าและตั้งค่าการเรียกกลับการแจ้งเตือน

โค้ดเวอร์ชันที่เรียบง่ายด้านล่างใช้ได้กับ 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 และ Breadboard ด้านล่าง

ภาพร่าง