17 構(gòu)建基于CoAP協(xié)議的物聯(lián)網(wǎng)系統(tǒng)

2018-02-24 15:53 更新

構(gòu)建基于CoAP協(xié)議的物聯(lián)網(wǎng)系統(tǒng)

17.1 CoAP: 嵌入式系統(tǒng)的REST

引自維基百科上的介紹,用的是谷歌翻譯。。。

受約束的應(yīng)用協(xié)議(COAP)是一種軟件協(xié)議旨在以非常簡單的電子設(shè)備,使他們能夠在互聯(lián)網(wǎng)上進(jìn)行交互式通信中使用。它特別針對小型低功率傳感器,開關(guān),閥門和需要被控制或監(jiān)督遠(yuǎn)程,通過標(biāo)準(zhǔn)的Internet網(wǎng)絡(luò)類似的組件。 COAP是一個(gè)應(yīng)用層協(xié)議,該協(xié)議是用于在資源受限的網(wǎng)絡(luò)連接設(shè)備,例如無線傳感器網(wǎng)絡(luò)節(jié)點(diǎn)使用。 COAP被設(shè)計(jì)為容易地轉(zhuǎn)換為HTTP與Web簡化集成,同時(shí)也能滿足特殊的要求,例如多播支持,非常低的開銷,和簡單性。多播,低開銷,以及簡單性是因特網(wǎng)極其重要物聯(lián)網(wǎng)(IOT)和機(jī)器對機(jī)器(M2M)設(shè)備,這往往是積重難返,有太多的內(nèi)存和電源,比傳統(tǒng)的互聯(lián)網(wǎng)設(shè)備有。因此,效率是非常重要的。 COAP可以在支持UDP或UDP的模擬大多數(shù)設(shè)備上運(yùn)行。

簡單地來說,CoAP是簡化了HTTP協(xié)議的RESTful API,因而也只提供了REST的四個(gè)方法,即PUT,GET,POST和DELETE。對于微小的資源受限,在資源受限的通信的IP的網(wǎng)絡(luò),HTTP不是一種可行的選擇。它占用了太多的資源和太多的帶寬。而對于物聯(lián)網(wǎng)這種嵌入式設(shè)備來說,關(guān)于資源與帶寬,是我們需要優(yōu)先考慮的內(nèi)容。

  • CoAP采用了二進(jìn)制報(bào)頭,而不是文本報(bào)頭(text header)
  • CoAP降低了頭的可用選項(xiàng)的數(shù)量。
  • CoAP減少了一些HTTP的方法
  • CoAP可以支持檢測裝置

17.2 CoAP 命令行工具

為了測試測試我們的代碼是否是正確工作,我們需要一個(gè)CoAP的命令行工具。目前有兩個(gè)不錯(cuò)的工具可以使用。

  • CoAP-cli,一個(gè)基于NodeJS的CoAP命令行工具,其核心是基于Node-CoAP庫。
  • libcooap,一個(gè)用C寫的CoAP命令行工具。
  • FireFox Copper, 一個(gè)Firefox的插件。

17.2.1 Node CoAP CLI

安裝命令如下

 npm install coap-cli -g

17.2.1.1 CoAP命令行

在coap-cli中,一共有四個(gè)方法。分別表示REST的四種不同的方式:

Commands:

get                    performs a GET request
put                    performs a PUT request
post                   performs a POST request
delete                 performs a DELETE request

在這里,我們用coap://vs0.inf.ethz.ch/來作一個(gè)簡單的測試

coap get coap://vs0.inf.ethz.ch/
(2.05)  ************************************************************
I-D

測試一下現(xiàn)在的最小的物聯(lián)網(wǎng)系統(tǒng)CoAP版

coap get coap://iot-coap.phodal.com/id/1
(2.05)  [{"id":1,"value":"is id 1","sensors1":19,"sensors2":20}]

17.2.2 libcoap

17.2.2.1 mac os libcoap安裝

Mac OS下可以直接用

brew install libcoap

17.2.2.2 Ubuntu libcoap安裝

Ubuntu GNU/Linux下

17.2.2.3 Windows libcoap安裝

Windows 下

安裝完libcoap,我們可以直接用自帶的兩個(gè)命令

coap-client 
coap-server

1.用coap-server啟一個(gè)CoAP服務(wù)

coap-server

2.客戶端獲取數(shù)據(jù)

coap-client -m get coap://localhost

返回結(jié)果

v:1 t:0 tkl:0 c:1 id:37109
This is a test server made with libcoap (see http://libcoap.sf.net) 
Copyright (C) 2010--2013 Olaf Bergmann <bergmann@tzi.org>

17.2.3 Firefox Copper

為了能訪問coap://localhost/,于是我們便需要安裝一個(gè)Firefox并安裝一個(gè)名為Copper的插件。

  1. 下載地址:?https://addons.mozilla.org/en-US/firefox/addon/copper-270430/

  2. 作為測試我們同樣可以訪問?coap://vs0.inf.ethz.ch:5683/

17.3 CoAP Hello,World

接著我們便開始試試做一個(gè)簡單的CoAP協(xié)議的應(yīng)用:

這里用到的是一個(gè)Nodejs的擴(kuò)展Node-CoAP

node-coap is a client and server library for CoAP modelled after the http module.

Node-CoAP是一個(gè)客戶端和服務(wù)端的庫用于CoAP的模塊建模。創(chuàng)建一個(gè)package.json文件,添加這個(gè)庫

{
  "dependencies":{
    "coap": "0.7.2"
  }
}

接著執(zhí)行

npm install

就可以安裝好這個(gè)庫。如果遇到權(quán)限問題,請用

sudo npm install

接著,創(chuàng)建這樣一個(gè)app.js

const coap        = require('coap')
    , server  = coap.createServer()

server.on('request', function(req, res) {
  res.end('Hello ' + req.url.split('/')[1] + '\n')
})

server.listen(function() {
  console.log('server started')
})

執(zhí)行

node app.js

便可以在瀏覽器上訪問了,因?yàn)楝F(xiàn)在什么也沒有,所以什么也不會(huì)返回。

接著下來再創(chuàng)建一個(gè)client端的js,并運(yùn)行之

const coap  = require('coap') 
    , req   = coap.request('coap://localhost/World')

req.on('response', function(res) {
  res.pipe(process.stdout)
})

req.end()

就可以在console上輸出

Hello World

也就達(dá)到了我們的目的,用CoAP協(xié)議創(chuàng)建一個(gè)服務(wù),接著我們應(yīng)該用它創(chuàng)建更多的東西,如產(chǎn)生JSON數(shù)據(jù),以及RESTful。和HTTP版的最小物聯(lián)網(wǎng)系統(tǒng)一樣,CoAP版的最小物聯(lián)網(wǎng)系統(tǒng)也是要返回JSON的。

17.3 CoAP 數(shù)據(jù)庫查詢

17.3.1 Node Module

這說里NodeJS Module的意義是因?yàn)槲覀冃枰趧e的地方引用到db_helper這個(gè)庫,也就是下一小節(jié)要的講的內(nèi)容。

這樣我們就可以在server.js類似于這樣去引用這個(gè)js庫。

var DBHelper = require('./db_helper.js');
DBHelper.initDB();

而這樣調(diào)用的前提是我們需要去聲明這樣的module,為了方便地導(dǎo)出函數(shù)功能調(diào)用。

function DBHelper(){
}
DBHelper.initDB = function(){};
module.exports = DBHelper;

17.3.2 Node-Sqlite3

這次我們用的是SQLite3(你可以用MySQL,出于安全考慮用SQLite3,SQLite3產(chǎn)生的是一個(gè)文件)。一個(gè)簡單的initDB函數(shù)

var db = new sqlite3.Database(config["db_name"]);
var create_table = 'create table if not exists basic (' + config["db_table"] + ');';

db.serialize(function() {
    db.run(create_table);
    _.each(config["init_table"], function(insert_data) {
        db.run(insert_data);
    });
});
db.close();

首先從配置中讀取db_name,接著創(chuàng)建table,然后調(diào)用underscore的each方法,創(chuàng)建幾個(gè)數(shù)據(jù)。配置如下所示

config = {
    "db_name": "iot.db",
    "db_table": "id integer primary key, value text, sensors1 float, sensors2 float",
    "init_table":[
        "insert or replace into basic (id,value,sensors1,sensors2) VALUES (1, 'is id 1', 19, 20);",
        "insert or replace into basic (id,value,sensors1,sensors2) VALUES (2, 'is id 2', 20, 21);"
    ],
    "query_table":"select * from basic;"
};

而之前所提到的url查詢所做的事情便是

DBHelper.urlQueryData = function (url, callback) {
    var db = new sqlite3.Database("iot.db");
    var result = [];
    console.log("SELECT * FROM basic where " + url.split('/')[1] + "=" + url.split('/')[2]);
    db.all("SELECT * FROM basic where " + url.split('/')[1] + "=" + url.split('/')[2], function(err, rows) {
        db.close();
        callback(JSON.stringify(rows));
    });
};

將URL傳進(jìn)來,便解析這個(gè)參數(shù),接著再放到數(shù)據(jù)庫中查詢,再回調(diào)回結(jié)果。這樣我們就可以構(gòu)成之前所說的查詢功能,而我們所謂的post功能似乎也可以用同樣的方法加進(jìn)去。

17.3.3 查詢數(shù)據(jù)

簡單地記錄一下在IoT-CoAP中一次獲取數(shù)據(jù)地過程。

先看看在示例中的Get.js的代碼,這關(guān)乎在后面server端的代碼。

const coap       = require('coap')
     ,requestURI = 'coap://localhost/'
     ,url        = require('url').parse(requestURI + 'id/1/')
     ,req        = coap.request(url)
     ,bl         = require('bl');

req.setHeader("Accept", "application/json");
req.on('response', function(res) {
  res.pipe(bl(function(err, data) {
    var json = JSON.parse(data);
    console.log(json);
  }));

});
req.end();

const定義數(shù)據(jù)的方法,和我們在其他語言中有點(diǎn)像。只是這的const主要是為了程序的健壯型,減少程序出錯(cuò),當(dāng)然這不是javascript的用法。

我們構(gòu)建了一個(gè)請求的URL

coap://localhost/id/1/

我們對我們的請求添加了一個(gè)Header,內(nèi)容是Accept,值是'application/json'也就是JSON格式。接著,便是等待請求回來,再處理返回的內(nèi)容。

判斷請求的方法

在這里先把一些無關(guān)的代碼刪除掉,并保證其能工作,so,下面就是簡要的邏輯代碼。

var coap            = require('coap');
var server          = coap.createServer({});
var request_handler = require('./request_handler.js');

server.on('request', function(req, res) {
    switch(req.method){
        case "GET": request_handler.getHandler(req, res);
            break;
    }
});

server.listen(function() {
    console.log('server started');
});

創(chuàng)建一個(gè)CoAP服務(wù),判斷req.method,也就是請求的方法,如果是GET的話,就調(diào)用request_handler.getHandler(req, res)。而在getHandler里,判斷了下請求的Accept

request_helper.getHandler = function(req, res) {
    switch (req.headers['Accept']) {
        case "application/json":
            qh.returnJSON(req, res);
            break;
        case "application/xml":
            qh.returnXML(req, res);
            break;
    }
};

如果是json剛調(diào)用returnJSON,

Database與回調(diào)

而這里為了處理回調(diào)函數(shù)剛分為了兩部分

query_helper.returnJSON = function(req, res) {
    DBHelper.urlQueryData(req.url, function (result) {
        QueryData.returnJSON(result, res);
    });
};

而這里只是調(diào)用了

DBHelper.urlQueryData = function (url, callback) {
    var db = new sqlite3.Database(config["db_name"]);
    console.log("SELECT * FROM basic where " + url.split('/')[1] + "=" + url.split('/')[2]);
    db.all("SELECT * FROM basic where " + url.split('/')[1] + "=" + url.split('/')[2], function(err, rows) {
        db.close();
        callback(JSON.stringify(rows));
    });
};

這里調(diào)用了node sqlite3去查詢對應(yīng)id的數(shù)據(jù),用回調(diào)處理了數(shù)據(jù)無法到外部的問題,而上面的returnJSON則只是返回最后的結(jié)果,code以及其他的內(nèi)容。

QueryData.returnJSON = function(result, res) {
    if (result.length == 2) {
        res.code = '4.04';
        res.end(JSON.stringify({
            error: "Not Found"
        }));
    } else {
        res.code = '2.05';
        res.end(result);
    }
};

當(dāng)resulst的結(jié)果為空時(shí),返回一個(gè)404,因?yàn)闆]有數(shù)據(jù)。這樣我們就構(gòu)成了整個(gè)的鏈,再一步步返回結(jié)果。

IoT-CoAP中我們使用到了一個(gè)Block2的東西,于是便整理相關(guān)的一些資料,作一個(gè)簡單的介紹,以及在代碼中的使用。

17.4 CoAP Block

CoAP是一個(gè)RESTful傳輸協(xié)議用于受限設(shè)備的節(jié)點(diǎn)和網(wǎng)絡(luò)?;镜腃oAP消息是一個(gè)不錯(cuò)的選擇對于小型載荷如

  • 溫度傳感器
  • 燈光開關(guān)
  • 樓宇自動(dòng)化設(shè)備

然而,有時(shí)我們的應(yīng)用需要傳輸更大的有效載荷,如——更新固件。與HTTP,TCP做繁重工作將大型有效載荷分成多個(gè)數(shù)據(jù)包,并確保他們所有到達(dá)并以正確的順序被處理。

CoAP是同UDP與DLTS一樣是基于數(shù)據(jù)報(bào)傳輸?shù)?,這限制了資源表示(resource representation)的最大大小,使得傳輸不需要太多的分割。雖然UDP支持通過IP分片傳輸更大的有效載荷,且僅限于64KiB,更重要的是,并沒有真正很好地約束應(yīng)用和網(wǎng)絡(luò)。

而不是依賴于IP分片,這種規(guī)范基本COAP了對“塊”選項(xiàng),用于傳輸信息從多個(gè)資源區(qū)塊的請求 - 響應(yīng)對。在許多重要的情況下,阻止使服務(wù)器能夠真正無狀態(tài):服務(wù)器可以處理每塊分開傳輸,而無需建立連接以前的數(shù)據(jù)塊傳輸?shù)钠渌?wù)器端內(nèi)存。

綜上所述,塊(Block)選項(xiàng)提供了傳送一個(gè)最小的在分塊的方式更大的陳述。

17.4.1 CoAP POST

看看在IoT CoAP中的post示例。

const coap     = require('coap')
      ,request  = coap.request
      ,bl       = require('bl')
      ,req = request({hostname: 'localhost',port:5683,pathname: '',method: 'POST'});

req.setOption('Block2',  [new Buffer('1'),new Buffer("'must'"), new Buffer('23'), new Buffer('12')]);
req.setHeader("Accept", "application/json");
req.on('response', function(res) {
    res.pipe(bl(function(err, data) {
        console.log(data);
        process.exit(0);
    }));

});

req.end();

Block2中一共有四個(gè)數(shù)據(jù),相應(yīng)的數(shù)據(jù)結(jié)果應(yīng)該是

{ name: 'Block2', value: <Buffer 31> }
{ name: 'Block2', value: <Buffer 27 6d 75 73 74 27> }
{ name: 'Block2', value: <Buffer 32 33> }
{ name: 'Block2', value: <Buffer 31 32> }

這是沒有解析的Block2,簡單地可以用

_.values(e).toString()

將結(jié)果轉(zhuǎn)換為

Block2,1 Block2,'must' Block2,23 Block2,12

接著按","分開,

_.values(e).toString().split(',')[1]

就有

[ '1', '\'must\'', '23', '12' ]

便可以很愉快地將其post到數(shù)據(jù)庫中了,

在做IoT-CoAP的過程中只支持JSON,查閱CoAP的草稿時(shí)發(fā)現(xiàn)支持了諸多的Content Types。

17.4.2 CoAP Content Types

以下文字來自谷歌翻譯:

互聯(lián)網(wǎng)媒體類型是通過HTTP字符串標(biāo)識(shí),如“application/xml”。該字符串是由一個(gè)頂層的類型“applicaion”和子類型的“XML”。為了盡量減少使用這些類型的媒體類型來表示的開銷消息有效載荷,COAP定義一個(gè)標(biāo)識(shí)符編碼方案互聯(lián)網(wǎng)媒體類型的子集。預(yù)計(jì)這桌將可擴(kuò)展標(biāo)識(shí)符的值的IANA維護(hù)。內(nèi)容類型選項(xiàng)被格式化為一個(gè)8位無符號(hào)整數(shù)。初始映射到一個(gè)合適的互聯(lián)網(wǎng)媒體類型標(biāo)識(shí)符表所示。復(fù)合型高層次類型(multipart和不支持消息)。標(biāo)識(shí)符值是從201-255保留的特定于供應(yīng)商的,應(yīng)用程序特定的或?qū)嶒?yàn)使用和不由IANA。

下面是HTTP的標(biāo)識(shí)符及類型

|

Internet media type

|

Identifier

text/plain (UTF-8)

|

0

text/xml (UTF-8)

|

1

text/csv (UTF-8)

|

2

text/html (UTF-8)

|

3

image/gif

|

21

image/jpeg

|

22

image/png

|

23

image/tiff

|

24

audio/raw

|

25

video/raw

|

26

application/link-format [I-D.ietf-core-link-format]

|

40

application/xml

|

41

application/octet-stream

|

42

application/rdf+xml

|

43

application/soap+xml

|

44

application/atom+xml

|

45

application/xmpp+xml

|

46

application/exi

|

47

application/x-bxml

|

48

application/fastinfoset

|

49

application/soap+fastinfoset

|

50

application/json

|

51

|

而在CoAP中只有簡單地幾個(gè)

|

Media type

|

Encoding

|

Id.

|

Reference

text/plain;

|

-

|

0

|

[RFC2046][RFC3676][RFC5147]

charset=utf-8

application/

|

-

|

40

|

[RFC6690]

link-format

application/xml

|

-

|

41

|

[RFC3023]

application/

|

-

|

42

|

[RFC2045][RFC2046]

octet-stream

application/exi

|

-

|

47

|

[EXIMIME]

application/json

|

-

|

50

|

[RFC4627]

|

簡單地說就是:

諸如application/json的Content Types在CoAP中應(yīng)該是50。如上表所示的結(jié)果是其對應(yīng)的結(jié)果,這樣的話可以減少傳遞的信息量。

17.5 CoAP JSON

于是在一開始的時(shí)候首先支持的便是"application/json"這樣的類型。

首先判斷請求的header

request_helper.getHandler = function(req, res) {
    switch (req.headers['Accept']) {
        case "application/json":
            qh.returnJSON(req, res);
            break;
        case "application/xml":
            qh.returnXML(req, res);
            break;
    }
};

再轉(zhuǎn)至相應(yīng)的函數(shù)處理,而判斷的依據(jù)則是Accept是不是"application/json"。

registerFormat('text/plain', 0)
registerFormat('application/link-format', 40)
registerFormat('application/xml', 41)
registerFormat('application/octet-stream', 42)
registerFormat('application/exi', 47)
registerFormat('application/json', 50)

對應(yīng)地我們需要在一發(fā)出請求的時(shí)候設(shè)置好Accept,要不就沒有辦法返回我們需要的結(jié)果。

req.setHeader("Accept", "application/json");

返回JSON

在給IoT CoAP添加了JSON支持之后,變得非常有意思,至少我們可以獲得我們想要的結(jié)果。在上一篇中我們介紹了一些常用的工具——CoAP 命令行工具集

CoAP客戶端代碼

開始之前我們需要有一個(gè)客戶端代碼,以便我們的服務(wù)端可以返回正確的數(shù)據(jù)并解析

var coap = require('coap');
var requestURI = 'coap://localhost/';
var url = require('url').parse(requestURI + 'id/1/');
console.log("Request URL: " + url.href);
var req = coap.request(url);
var bl = require('bl');

req.setHeader("Accept", "application/json");
req.on('response', function(res) {
  res.pipe(bl(function(err, data) {
    var json = JSON.parse(data);
    console.log(json);
  }));

});

req.end();

代碼有點(diǎn)長內(nèi)容也有點(diǎn)多,但是核心是這句話:

req.setHeader("Accept", "application/json");

這樣的話,我們只需要在我們的服務(wù)端一判斷,

if(req.headers['Accept'] == 'application/json') {
     //do something
 };

這樣就可以返回?cái)?shù)據(jù)了

CoAP Server端代碼

Server端的代碼比較簡單,判斷一下

if (req.headers['Accept'] == 'application/json') {
        parse_url(req.url, function(result){
            res.end(result);
        });
        res.code = '2.05';
    }

請求的是否是JSON格式,再返回一個(gè)205,也就是Content,只是這時(shí)設(shè)計(jì)是請求一個(gè)URL返回對應(yīng)的數(shù)據(jù)。如

coap://localhost/id/1/

這時(shí)應(yīng)該請求的是ID為1的數(shù)據(jù),即

[ { id: 1, value: 'is id 1', sensors1: 19, sensors2: 20 }]

而parse_url只是從數(shù)據(jù)庫從讀取相應(yīng)的數(shù)據(jù)。

function parse_url(url ,callback) {
    var db = new sqlite3.Database(config["db_name"]);
    var result = [];
    db.all("SELECT * FROM basic;", function(err, rows) {
        callback(JSON.stringify(rows));
    })
}

并且全部都顯示出來,設(shè)計(jì)得真是有點(diǎn)不行,不過現(xiàn)在已經(jīng)差不多了。

17.6 使用IoT-CoAP構(gòu)建物聯(lián)網(wǎng)

(注意:windows系統(tǒng)npm install失敗時(shí),需要自己建立一個(gè)C:\Documents and Settings[USERNAME]\Application Data\npm 文件)

npm install iot-coap

1.新建index.js

注意: 如果已經(jīng)存在一個(gè)index.js文件,請將下面內(nèi)容添加到文件末尾(create index.js, and add)

var iotcoap         = require('iot-coap');

iotcoap.run();
iotcoap.rest.run();

注意:在db配置可以選擇mongodb和sqlite3,替換所需要的數(shù)據(jù)庫即可。(you can choice db on iot.js with 'sqlite' or 'mongodb')

2.創(chuàng)建iot.js

exports.config  = {
    "db_name": "iot.db",
    "mongodb_name": "iot",
    "mongodb_documents": "iot",
    "db": "mongodb",
    "table_name": "basic",
    "keys":[
        "id",
        "value",
        "sensors1",
        "sensors2"
    ],
    "db_table": "id integer primary key, value text, sensors1 float, sensors2 float",
    "mongodb_init":[
        {
            id: 1,
            value: "is id 1",
            sensors1: 19,
            sensors2: 20
        },
        {
            id: 2,
            value: "is id 2",
            sensors1: 20,
            sensors2: 21
        }
    ],
    "init_table":[
        "insert or replace into basic (id,value,sensors1,sensors2) VALUES (1, 'is id 1', 19, 20);",
        "insert or replace into basic (id,value,sensors1,sensors2) VALUES (2, 'is id 2', 20, 21);"
    ],
    "query_table":"select * from basic;",
    "rest_url": "/id/:id",
    "rest_post_url": "/",
    "rest_port": 8848
};

3.運(yùn)行(run)

node index.js

show:

coap listening at coap://0.0.0.0:5683
restify listening at http://0.0.0.0:8848
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)