Tạo thiết bị IoT hỗ trợ web nhờ Intel Edison

Kenneth Christiansen
Kenneth Christiansen

Ngày nay, Internet của vạn vật đã trở nên gần gũi với mọi người và điều này khiến những người mày mò và lập trình viên như tôi rất thích thú. Không có gì thú vị hơn việc trực tiếp trò chuyện với các phát minh của mình!

Tuy nhiên, các thiết bị IoT cài đặt những ứng dụng mà bạn hiếm khi dùng có thể gây khó chịu. Vì vậy, chúng tôi đã tận dụng các công nghệ web sắp tới (chẳng hạn như Web trong cuộc sống và Bluetooth trên web) để giúp các thiết bị IoT trở nên trực quan hơn và ít bị xâm phạm hơn.

Ứng dụng

Web và IoT, sự kết hợp hoàn hảo

Vẫn còn rất nhiều trở ngại cần vượt qua trước khi Internet của vạn vật có thể trở thành thành công lớn. Một trở ngại là các công ty và sản phẩm yêu cầu người dùng cài đặt ứng dụng cho mỗi thiết bị mà họ mua, làm rối điện thoại của người dùng với vô số ứng dụng mà họ hiếm dùng.

Vì lý do này, chúng tôi rất hào hứng với dự án Web trong cuộc sống. Dự án này cho phép các thiết bị truyền URL đến một trang web trực tuyến theo cách không xâm phạm. Khi kết hợp với các công nghệ web mới nổi như Bluetooth cho web, USB webNFC trên web, các trang web có thể kết nối trực tiếp với thiết bị hoặc ít nhất là giải thích cách thực hiện đúng.

Mặc dù bài viết này chủ yếu tập trung vào Bluetooth cho web, nhưng một số trường hợp sử dụng có thể phù hợp hơn với Web NFC hoặc Web USB. Ví dụ: USB web sẽ được ưu tiên nếu bạn cần kết nối vật lý vì lý do bảo mật.

Trang web cũng có thể hoạt động dưới dạng một Ứng dụng web tiến bộ (PWA). Độc giả nên xem nội dung giải thích của Google về PWA. PWA là các trang web có trải nghiệm người dùng thích ứng giống như ứng dụng, có thể hoạt động khi không có mạng và có thể được thêm vào màn hình chính của thiết bị.

Để minh chứng cho khái niệm này, tôi đã xây dựng một thiết bị nhỏ bằng cách sử dụng bảng đột phá Intel® Edison Arduino. Thiết bị này có một cảm biến nhiệt độ (TMP36) và bộ truyền động (cathode đèn LED có màu). Bạn có thể xem sơ đồ cho thiết bị này ở cuối bài viết này.

Bảng mạch khung.

Intel Edison là một sản phẩm thú vị vì nó có thể chạy bản phân phối Linux* hoàn chỉnh. Do đó, tôi có thể dễ dàng lập trình bằng Node.js. Trình cài đặt cho phép bạn cài đặt Intel* XDK, giúp bạn dễ dàng bắt đầu, mặc dù bạn cũng có thể lập trình và tải lên thiết bị của mình theo cách thủ công.

Đối với ứng dụng Node.js, tôi yêu cầu 3 mô-đun nút, cũng như các phần phụ thuộc của các mô-đun đó:

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

Mô-đun nút trước đây sẽ tự động cài đặt noble, là mô-đun nút mà tôi dùng để trò chuyện qua Bluetooth năng lượng thấp.

Tệp package.json cho dự án sẽ có dạng như sau:

{
    "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"
    }
}

Thông báo về trang web

Kể từ phiên bản 49, Chrome trên Android hỗ trợ Web trong cuộc sống, cho phép Chrome xem các URL đang được các thiết bị xung quanh truyền phát. Có một số yêu cầu mà nhà phát triển phải nắm được, chẳng hạn như các trang web cần có khả năng truy cập công khai và sử dụng HTTPS.

Giao thức Eddystone có giới hạn kích thước 18 byte trên URL. Vì vậy, để URL cho ứng dụng minh hoạ hoạt động (https://webbt-sensor-hub.appspot.com/), tôi cần sử dụng trình rút ngắn URL.

Việc truyền phát URL khá đơn giản. Bạn chỉ cần nhập các thư viện bắt buộc và gọi một vài hàm. Có một cách để thực hiện việc này là gọi advertiseUrl khi chip BLE đang bật:

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

Điều đó thực sự không thể dễ dàng hơn. Bạn thấy trong hình ảnh dưới đây Chrome thấy thiết bị rất phù hợp.

Chrome thông báo các beacon Web trong cuộc sống lân cận.
URL của ứng dụng web sẽ được liệt kê.

Giao tiếp với cảm biến/bộ truyền động

Chúng tôi sử dụng Johnny-Five* để thảo luận về các tính năng nâng cao bảng điều khiển. Johnny-Five có một ý tưởng trừu tượng khi trò chuyện với cảm biến TMP36.

Dưới đây là mã đơn giản để nhận thông báo về sự thay đổi nhiệt độ cũng như đặt màu đèn LED ban đầu.

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

Bạn hiện có thể bỏ qua các biến *Characteristic ở trên; các biến này sẽ được xác định trong phần sau về khả năng giao tiếp với Bluetooth.

Như bạn có thể nhận thấy trong quá trình tạo bản sao của đối tượng Nhiệt kế, tôi sẽ giao tiếp với TMP36 qua cổng tương tự A0. Các chân điện áp trên cực âm LED màu được kết nối với các chân kỹ thuật số 3, 5 và 6. Các chân này là các chân điều chế độ rộng xung (PWM) trên bảng đột phá Edison Arduino.

Bảng Edison

Trò chuyện với Bluetooth

Việc trò chuyện với Bluetooth không thể dễ dàng hơn nhiều so với khi dùng noble.

Trong ví dụ sau, chúng tôi tạo 2 đặc điểm Bluetooth năng lượng thấp: một cho đèn LED và một cho cảm biến nhiệt độ. Tính năng trước cho phép chúng ta đọc màu đèn LED hiện tại và đặt màu mới. Chính sách sau cho phép chúng ta đăng ký nhận thông tin về các sự kiện thay đổi nhiệt độ.

Với noble, việc tạo một đặc điểm khá dễ dàng. Tất cả những gì bạn cần làm là xác định cách đặc điểm này giao tiếp và xác định mã nhận dạng duy nhất (UUID). Các lựa chọn giao tiếp là đọc, ghi, thông báo hoặc kết hợp các lựa chọn đó. Cách dễ nhất để thực hiện việc này là tạo một đối tượng mới và kế thừa từ bleno.Characteristic.

Đối tượng đặc điểm thu được sẽ có dạng như sau:

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

Chúng ta đang lưu trữ giá trị nhiệt độ hiện tại trong biến this._lastValue. Chúng ta cần thêm phương thức onReadRequest và mã hoá giá trị để lệnh "đọc" hoạt động.

TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
    var data = new Buffer(8);
    data.writeDoubleLE(this._lastValue, 0);
    callback(this.RESULT_SUCCESS, data);
};

Để "thông báo", chúng tôi cần thêm một phương thức để xử lý các gói thuê bao và huỷ đăng ký. Về cơ bản, chúng ta chỉ lưu trữ lệnh gọi lại. Khi có một lý do mới về nhiệt độ mà chúng ta muốn gửi, chúng ta sẽ gọi lệnh gọi lại đó với giá trị mới (được mã hoá như trên).

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

Vì các giá trị có thể dao động một chút, nên chúng ta cần làm mịn các giá trị nhận được từ cảm biến TMP36. Tôi đã chọn chỉ lấy trung bình 100 mẫu và chỉ gửi thông tin cập nhật khi nhiệt độ thay đổi ít nhất 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);
    }
};

Đó là cảm biến nhiệt độ. Đèn LED màu đơn giản hơn. Đối tượng cũng như phương thức "đọc" được hiển thị ở bên dưới. Đặc tính này được định cấu hình để cho phép các thao tác "đọc" và "ghi" và có mã nhận dạng duy nhất (UUID) khác với đặc điểm nhiệt độ.

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

Để điều khiển đèn LED từ vật thể, tôi thêm một thành phần this._led mà tôi sử dụng để lưu trữ đối tượng LED Johnny-Five. Tôi cũng đặt màu của đèn LED thành giá trị mặc định (màu trắng, còn gọi là #ffffff).

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

Phương thức "ghi" nhận được một chuỗi (giống như "đọc" sẽ gửi một chuỗi). Chuỗi này có thể bao gồm mã màu CSS (Ví dụ: tên CSS như rebeccapurple hoặc mã hex như #ff00bb). Tôi sử dụng mô-đun nút có tên là parse-color để luôn nhận được giá trị hex như những gì Johnny-Five mong đợi.

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

Tất cả các cách trên sẽ không hiệu quả nếu chúng ta không thêm mô-đun bleno. eddystone-beacon sẽ không hoạt động với bleno trừ phi bạn sử dụng phiên bản noble được phân phối cùng với bleno. Thật may là việc đó khá đơn giản:

var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');

Bây giờ, tất cả những gì chúng ta cần là quảng cáo cho thiết bị của mình (UUID) và các đặc điểm của thiết bị đó (các UUID khác)

bleno.on('advertisingStart', function(error) {
    ...
    bleno.setServices([
        new bleno.PrimaryService({
        uuid: 'fc00',
        characteristics: [
            temperatureCharacteristic, colorCharacteristic
        ]
        })
    ]);
});

Tạo ứng dụng web

Nếu không gặp phải quá nhiều thất bại về cách hoạt động của các phần không có Bluetooth của ứng dụng khách, chúng ta có thể minh hoạ giao diện người dùng thích ứng được tạo trong Polymer* làm ví dụ. Ứng dụng thu được sẽ có dạng như sau:

Ứng dụng trên điện thoại.
Thông báo lỗi.

Phần bên phải cho thấy một phiên bản cũ, cho thấy một nhật ký lỗi đơn giản mà tôi đã thêm vào để dễ dàng phát triển.

Bluetooth cho web giúp bạn dễ dàng giao tiếp với các thiết bị Bluetooth năng lượng thấp, vì vậy, hãy xem phiên bản đơn giản của mã kết nối. Nếu bạn không biết cách hoạt động của lời hứa, hãy xem tài nguyên này trước khi đọc thêm.

Việc kết nối với thiết bị Bluetooth bao gồm một chuỗi lời hứa. Trước tiên, chúng ta sẽ lọc thiết bị (UUID: FC00, tên: Edison). Thao tác này sẽ hiển thị hộp thoại để cho phép người dùng chọn thiết bị được cung cấp bộ lọc. Sau đó, chúng ta kết nối với dịch vụ GATT và lấy dịch vụ chính cũng như các đặc điểm liên quan. Sau đó, chúng ta đọc các giá trị và thiết lập lệnh gọi lại thông báo.

Phiên bản đơn giản của mã bên dưới chỉ hoạt động với API Web Bluetooth mới nhất, do đó yêu cầu Nhà phát triển Chrome (M49) trên 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.
});

Việc đọc và ghi một chuỗi từ DataView / ArrayBuffer (dữ liệu mà API WebBluetooth sử dụng) cũng dễ dàng như việc sử dụng Buffer ở phía Node.js. Chúng ta chỉ cần sử dụng TextEncoderTextDecoder:

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

Việc xử lý sự kiện characteristicvaluechanged cho cảm biến nhiệt độ cũng khá dễ dàng:

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

Tóm tắt

Vậy là xong rồi! Như bạn có thể thấy, việc giao tiếp với Bluetooth năng lượng thấp bằng Web Bluetooth ở phía máy khách và Node.js trên Edison khá dễ dàng và rất mạnh mẽ.

Bằng cách sử dụng Web trong cuộc sống và Bluetooth của web, Chrome tìm thấy thiết bị và cho phép người dùng dễ dàng kết nối với thiết bị mà không cần cài đặt các ứng dụng ít dùng mà người dùng có thể không muốn. Các ứng dụng này có thể cập nhật tuỳ từng thời điểm.

Bản minh hoạ

Bạn có thể dùng thử ứng dụng khách này để được lấy cảm hứng về cách tạo các ứng dụng web của riêng mình nhằm kết nối với các thiết bị Internet của vạn vật tuỳ chỉnh.

Mã nguồn

Mã nguồn có tại đây. Vui lòng báo cáo sự cố hoặc gửi bản vá.

Phác thảo

Nếu bạn thực sự thích phiêu lưu và muốn tái hiện những gì tôi đã làm, hãy tham khảo bản phác thảo của Edison và bảng mạch khung bên dưới:

Phác thảo