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

Kenneth Christiansen
Kenneth Christiansen

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

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

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

เว็บและ IoT เป็นการจับคู่ที่ตรงกัน

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

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

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

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

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

เบรดบอร์ด

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

สำหรับแอป Node.js ฉันต้องมีโมดูลโหนด 3 รายการ รวมถึงทรัพยากร Dependency ของโหนดดังกล่าวด้วย

  • 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 ซึ่งทำให้ 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 ประกาศบีคอน 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) บนบอร์ดแยกของ Edison Arduino

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

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

ข้อมูลประชากร

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

ซอร์สโค้ด

คุณสามารถดูซอร์สโค้ดได้ที่นี่ โปรดรายงานปัญหาหรือส่งแพตช์

สเก็ตช์

ถ้าคุณอยากลองอะไรใหม่ๆ และอยากทำซ้ำสิ่งที่ฉันทำ ให้ดูที่ภาพเอดิสันและภาพร่างจากแผงวงจรทดลองด้านล่างนี้

สเก็ตช์