Günümüzde herkes Nesnelerin İnterneti'nden bahsediyor. Bu da benim gibi meraklıları ve programcıları çok heyecanlandırıyor. Kendi icatlarınızı hayata geçirmek ve onlarla konuşabilmekten daha havalı bir şey yoktur.
Ancak nadiren kullandığınız uygulamaları yükleyen IoT cihazları can sıkıcı olabilir. Bu nedenle, IoT cihazlarını daha sezgisel ve daha az müdahaleci hale getirmek için Fiziksel Web ve Web Bluetooth gibi yeni web teknolojilerinden yararlanıyoruz.
Web ve IoT, mükemmel bir eşleşme
Nesnelerin İnterneti'nin büyük bir başarı elde edebilmesi için aşılanması gereken çok sayıda engel var. Kullanıcıların satın aldıkları her cihaza uygulama yüklemesini zorunlu kılan şirketler ve ürünler, kullanıcıların telefonlarını nadiren kullandıkları çok sayıda uygulamayla doldurarak bu engellere neden oluyor.
Bu nedenle, cihazların bir URL'yi internetteki bir web sitesine rahatsız edici olmayan bir şekilde yayınlamasına olanak tanıyan Fiziksel Web projesini heyecanla karşılıyoruz. Siteler, Web Bluetooth, Web USB ve Web NFC gibi yeni web teknolojileri ile birlikte doğrudan cihaza bağlanabilir veya en azından bunu yapmanın doğru yolunu açıklayabilir.
Bu makalede öncelikle Web Bluetooth'a odaklanıyoruz ancak bazı kullanım alanları Web NFC veya Web USB için daha uygun olabilir. Örneğin, güvenlik nedeniyle fiziksel bağlantıya ihtiyacınız varsa Web USB tercih edilir.
Web sitesi, progresif web uygulaması (PWA) olarak da kullanılabilir. Okuyucuları, Google'ın PWA'lar ile ilgili açıklamasını incelemelerini öneririz. PWA'lar, duyarlı ve uygulama benzeri bir kullanıcı deneyimi sunan, çevrimdışı çalışabilen ve cihazın ana ekranına eklenebilen sitelerdir.
Kavram kanıtı olarak Intel® Edison Arduino geliştirme kartını kullanarak küçük bir cihaz oluşturuyorum. Cihaz, bir sıcaklık sensörü (TMP36) ve bir aktüatör (renkli LED katodu) içerir. Bu cihazın şemalarını bu makalenin sonunda bulabilirsiniz.
Intel Edison, tam bir Linux* dağıtımı çalıştırabilmesi nedeniyle ilginç bir üründür. Bu nedenle, Node.js kullanarak kolayca programlayabiliyorum. Yükleyici, Intel* XDK'yı yüklemenize olanak tanır. Böylece ürünü kullanmaya kolayca başlayabilirsiniz. Bununla birlikte, cihazınızı manuel olarak da programlayabilir ve cihazınıza yükleyebilirsiniz.
Node.js uygulamam için üç düğüm modülünün yanı sıra bunların bağımlılıklarına da ihtiyacım vardı:
eddystone-beacon
parse-color
johnny-five
İlk yöntem, Bluetooth Düşük Enerji üzerinden iletişim kurmak için kullandığım düğüm modülü olan noble
'ü otomatik olarak yükler.
Projenin package.json
dosyası şu şekilde görünür:
{
"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"
}
}
Web sitesini duyurma
Android'deki Chrome, 49 sürümünden itibaren fiziksel web'i destekler. Bu sayede Chrome, çevresindeki cihazlar tarafından yayınlanan URL'leri görebilir. Geliştiricinin bilmesi gereken bazı koşullar vardır. Örneğin, sitelerin herkese açık olması ve HTTPS kullanması gerekir.
Eddystone protokolünde URL'ler için 18 baytlık bir boyut sınırı vardır. Bu nedenle, demo uygulamamın URL'sinin (https://webbt-sensor-hub.appspot.com/) çalışması için bir URL kısaltıcı kullanmam gerekiyor.
URL'yi yayınlamak oldukça basittir. Tek yapmanız gereken, gerekli kitaplıkları içe aktarmak ve birkaç işlevi çağırmaktır. Bunu yapmanın bir yolu, BLE çipi açıkken advertiseUrl
işlevini çağırmaktır:
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'});
}
}
Bu işlem çok kolay. Aşağıdaki resimde, Chrome'un cihazı sorunsuz bir şekilde bulduğu görülüyor.
Sensör/aktüatör ile iletişim kurma
Johnny-Five*'i kullanarak geliştirmeler için yönetim kurulumuzla iletişim kurarız. Johnny-Five, TMP36 sensörüyle konuşmak için güzel bir soyutlama sunar.
Sıcaklık değişikliklerinden haberdar olmak ve başlangıçtaki LED rengini ayarlamak için kullanabileceğiniz basit kodu aşağıda bulabilirsiniz.
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);
});
Yukarıdaki *Characteristic
değişkenlerini şimdilik göz ardı edebilirsiniz. Bu değişkenler, Bluetooth ile arayüz oluşturma hakkındaki sonraki bölümde açıklanacaktır.
Termometre nesnesinin örneklenmesinde de görebileceğiniz gibi, TMP36 ile analog A0
bağlantı noktası üzerinden iletişim kuruyorum. Renkli LED katodundaki voltaj bacakları, Edison Arduino geliştirme kartındaki darbe genişliği modülasyonu (PWM) pinleri olan 3, 5 ve 6 numaralı dijital pinlere bağlanır.
Bluetooth ile konuşma
Bluetooth ile konuşmak noble
ile olduğundan çok daha kolay.
Aşağıdaki örnekte, biri LED için diğeri sıcaklık sensörü için olmak üzere iki Bluetooth Düşük Enerji özelliği oluşturuyoruz. İlki, mevcut LED rengini okumamıza ve yeni bir renk ayarlamamıza olanak tanır. İkincisi, sıcaklık değişimi etkinliklerine abone olmamıza olanak tanır.
noble
ile özellik oluşturmak oldukça kolaydır. Tek yapmanız gereken, özelliğin nasıl iletişim kurduğunu ve bir UUID'yi tanımlamaktır. İletişim seçenekleri okuma, yazma, bildirim veya bunların herhangi bir kombinasyonudur.
Bunu yapmanın en kolay yolu, yeni bir nesne oluşturmak ve bleno.Characteristic
sınıfından devralmasıdır.
Elde edilen karakteristik nesne aşağıdaki gibi görünür:
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);
Mevcut sıcaklık değerini this._lastValue
değişkenine kaydediyoruz. "Okuma" işleminin çalışması için bir onReadRequest
yöntemi eklememiz ve değeri kodlamamız gerekir.
TemperatureCharacteristic.prototype.onReadRequest = function(offset, callback) {
var data = new Buffer(8);
data.writeDoubleLE(this._lastValue, 0);
callback(this.RESULT_SUCCESS, data);
};
"Bildir" için abonelikleri ve abonelikten çıkma işlemlerini yönetecek bir yöntem eklememiz gerekir. Temel olarak, geri çağırma işlevini depolarız. Göndermek istediğimiz yeni bir sıcaklık nedeni olduğunda, bu geri çağırmayı yeni değerle (yukarıda gösterildiği gibi) çağırırız.
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;
};
Değerler biraz dalgalanabileceğinden TMP36 sensöründen elde ettiğimiz değerleri düzeltmemiz gerekir. 100 örneğin ortalamasını almayı ve yalnızca sıcaklık en az 1 derece değiştiğinde güncelleme göndermeyi tercih ettim.
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);
}
};
Bu, sıcaklık sensöründen gelen bir sinyal. Renkli LED ise daha basittir. Nesne ve "read" yöntemi aşağıda gösterilmiştir. Özellik, "okuma" ve "yazma" işlemlerine izin verecek şekilde yapılandırılır ve sıcaklık özelliğinden farklı bir UUID'ye sahiptir.
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);
};
Nesnedeki LED'i kontrol etmek için Johnny-Five LED nesnesini depolamak için kullandığım bir this._led
üyesi ekliyorum. Ayrıca LED'in rengini varsayılan değerine (beyaz, yani #ffffff
) ayarlıyorum.
board.on("ready", function() {
...
colorCharacteristic._led = led;
led.color(colorCharacteristic._value);
led.intensity(30);
...
}
"write" yöntemi, bir CSS renk kodundan oluşabilecek bir dize alır (tıpkı "read" yönteminin bir dize göndermesi gibi) (ör. rebeccapurple
gibi CSS adları veya #ff00bb
gibi onaltılık kodlar). Johnny-Five'in beklediği onaltılık değeri her zaman almak için parse-color adlı bir düğüm modülü kullanıyorum.
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 modülü eklenmezse yukarıdakilerin hiçbiri çalışmaz.
eddystone-beacon
, birlikte dağıtılan noble
sürümünü kullanmadığınız sürece bleno ile çalışmaz. Neyse ki bunu yapmak oldukça basit:
var bleno = require('eddystone-beacon/node_modules/bleno');
var util = require('util');
Şimdi tek ihtiyacımız, cihazımızın (UUID) ve özelliklerinin (diğer UUID'ler) reklamını yapması
bleno.on('advertisingStart', function(error) {
...
bleno.setServices([
new bleno.PrimaryService({
uuid: 'fc00',
characteristics: [
temperatureCharacteristic, colorCharacteristic
]
})
]);
});
İstemci web uygulamasını oluşturma
İstemci uygulamasının Bluetooth dışındaki bölümlerinin işleyişiyle ilgili çok fazla ayrıntıya girmeden, örnek olarak Polymer*'de oluşturulmuş duyarlı bir kullanıcı arayüzünü gösterebiliriz. Sonuçta oluşturulan uygulama aşağıda gösterilmiştir:
Sağ tarafta, geliştirmeyi kolaylaştırmak için eklediğim basit bir hata günlüğünün gösterildiği eski bir sürüm yer alıyor.
Web Bluetooth, Bluetooth Düşük Enerji cihazlarıyla iletişim kurmayı kolaylaştırır. Bu nedenle, bağlantı kodumun basitleştirilmiş bir versiyonuna göz atalım. Vaatlerin işleyiş şeklini bilmiyorsanız okumaya devam etmeden önce bu kaynağa göz atın.
Bir Bluetooth cihazına bağlanmak, bir dizi vaat içerir.
Öncelikle cihazı (UUID: FC00
, ad: Edison
) filtreleriz. Bu işlem, kullanıcının filtreye göre cihazı seçmesine olanak tanıyan bir iletişim kutusu görüntüler. Ardından GATT hizmetine bağlanıp birincil hizmeti ve ilişkili özellikleri alırız. Ardından değerleri okur ve bildirim geri çağırma işlevlerini ayarlarız.
Aşağıdaki kodumuzun basitleştirilmiş sürümü yalnızca en son Web Bluetooth API ile çalıştığından Android'de Chrome Dev (M49) ile çalışır.
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.
});
Bir DataView
/ ArrayBuffer
öğesinden (WebBluetooth API'sinin kullandığı) dize okumak ve yazmak, Node.js tarafında Buffer
kullanmak kadar kolaydır. Yalnızca TextEncoder
ve TextDecoder
kullanmanız yeterlidir:
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);
},
Sıcaklık sensörü için characteristicvaluechanged
etkinliğini işlemek de oldukça kolaydır:
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);
},
Özet
Bu kadardı. Gördüğünüz gibi, istemci tarafında Web Bluetooth'u ve Edison'da Node.js'yi kullanarak Bluetooth Düşük Enerji ile iletişim kurmak oldukça kolay ve çok güçlüdür.
Chrome, Fiziksel Web ve Web Bluetooth'u kullanarak cihazı bulur ve kullanıcının istemeyebileceği ve zaman zaman güncellenebilen, nadiren kullanılan uygulamaları yüklemeden cihaza kolayca bağlanmasına olanak tanır.
Demo
Özel Nesnelerin İnterneti cihazlarınıza bağlanmak için kendi web uygulamalarınızı nasıl oluşturabileceğiniz konusunda ilham almak için istemciyi deneyebilirsiniz.
Kaynak kodu
Kaynak kodu burada bulabilirsiniz. Sorunları bildirebilir veya düzeltme gönderebilirsiniz.
Çizim
Gerçekten maceracıysanız ve yaptığımı tekrarlamak istiyorsanız aşağıdaki Edison ve breadboard taslağına bakın: