新しい IOTAgent の開発方法¶
インデックス¶
概要¶
このドキュメントの目的は、新しいIoT Agent を段階的に開発する方法を示すことです。そのために、簡単な考案した HTTP プロトコルを使用するで、curl
と nc
のような簡単なコマンドライン命令でテストすることができます。
プロトコル¶
考案したプロトコルは、Ultralight 2.0 から自由に適合されます。デバイスがアップデートを送信したい場合、デバイスは次のようにリクエストを送信します :
curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
ここで、
- i : デバイスID です
- k : デバイスのサービスの API キーです
- d : パイプで区切られたキー/値のペア ('|') から構成され、各ペアはコンマ (',') で区切られたデータペイロードです
要件¶
このチュートリアルでは、少なくとも Node.js v0.10 がマシンにインストールされ、動作していることを想定しています。また、セキュリティ・プロキシを使用せずに、Context Broker にアクセスすることも期待しています。
基本 IOTA¶
この第1章では、完全に機能するノース・バウンド API と サウス・バウンド API を使用しない IoT Agent を開発します。これは役に立たないように見え、実際にはそうですが、IOTA の作成における基本的なステップを示してくれます。
まず、Node プロジェクトを作成する必要があります。プロジェクトを保持するフォルダを作成し、次のコマンドを入力します :
npm init
package.json
プロジェクトのファイルが作成されます。次にプロジェクトのファイルに次の行を追加します :
"dependencies": {
"iotagent-node-lib": "*"
},
そして依存関係をインストールし、通常通り実行します :
npm install
最初のステップは、IOTA の動作を調整するために使用される設定ファイルを書き込むことです。内容は、同じフォルダ内の config-basic-example.js
ファイルからコピーすることができます。プロジェクトのルートフォルダに config.js
ファイルを作成します。Context Broker IP を ローカルの Context Broker に変更することを忘れないでください。
今、IOTAのコードを開始することができます。IOTA を開始するために必要な最小限のコードは次のとおりです :
var iotAgentLib = require('iotagent-node-lib'),
config = require('./config');
iotAgentLib.activate(config, function(error) {
if (error) {
console.log('There was an error activating the IOTA');
process.exit(1);
}
});
これで、IOTA を使用する準備が整いました。次のコマンドで実行します :
node index.js
ノース・バウンドのインターフェイスは完全に機能するはずです。つまり、デバイスの登録と設定の管理です。
アクティブ属性を持つ IOTA¶
前のセクションでは、ノース・バウンドのインターフェイスだけを公開する IOTA を作成しましたが、それは、教訓的な使用以外は、役に立ちません。このセクションでは、単純な サウス・バウンドのインターフェイスを作成します。サウス・バウンド API 自体の性質は、IoT Agent の作成プロセスとは関係がないことに注意することが重要です。各デバイス・プロトコルは独自のメカニズムを使用し、IoTA の開発者が開発に役立つライブラリを見つけることができます。この例では、Express をそのようなライブラリとして使用します。
プロジェクトに Express の依存関係を追加するために、次の行を package.json
の dependencies
セクションに追加します :
"express": "*",
require セクションはこのようになります。標準 http
モジュールも必要です :
var iotAgentLib = require('iotagent-node-lib'),
http = require('http'),
express = require('express'),
config = require('./config');
そして、いつものように、npm install
を使って依存関係をインストールしてください。私たちのコードに express
と http
の両方を要求する必要があります。
さて、私たちのコードで接続を受け入れるためには、まず express
を開始する必要があります。この目的を念頭に置いて、IOTA の初期化コードから呼び出される新しい関数 initSouthbound()
を作成します :
function initSouthbound(callback) {
southboundServer = {
server: null,
app: express(),
router: express.Router()
};
southboundServer.app.set('port', 8080);
southboundServer.app.set('host', '0.0.0.0');
southboundServer.router.get('/iot/d', manageULRequest);
southboundServer.server = http.createServer(southboundServer.app);
southboundServer.app.use('/', southboundServer.router);
southboundServer.server.listen(southboundServer.app.get('port'), southboundServer.app.get('host'), callback);
}
この Express コードは、ミドルウェア manageULRequest()
を使用して、着信するリクエストのターゲット・パス /iot/d
を処理する 8080 ポートをリスニングする HTTP サーバを設定します。このミドルウェアには、すべてのサウス・バウンドのロジックと、情報を Context Broker に進めるために必要なライブラリメソッドが含まれます。このミドルウェアのコードは次のようになります。
function manageULRequest(req, res, next) {
var values;
iotAgentLib.retrieveDevice(req.query.i, req.query.k, function(error, device) {
if (error) {
res.status(404).send({
message: 'Couldn\'t find the device: ' + JSON.stringify(error)
});
} else {
values = parseUl(req.query.d, device);
iotAgentLib.update(device.name, device.type, '', values, device, function(error) {
if (error) {
res.status(500).send({
message: 'Error updating the device'
});
} else {
res.status(200).send({
message: 'Device successfully updated'
});
}
});
}
});
}
このミドルウェアでは、データ・ペイロードを解析し、それを更新関数によって期待するデータ・オブジェクトで変換する関数 parseUl()
を使用しています。すなわち、NGSI 構文の属性配列です :
function parseUl(data, device) {
function findType(name) {
for (var i=0; i < device.active.length; i++) {
if (device.active[i].name === name) {
return device.active[i].type;
}
}
return null;
}
function createAttribute(element) {
var pair = element.split('|'),
attribute = {
name: pair[0],
value: pair[1],
type: findType(pair[0])
};
return attribute;
}
return data.split(",").map(createAttribute);
}
ここでは、UL ペイロード t|15,l|19.6
の関数戻り値の出力例を示します :
[
{
"name": "t",
"type": "celsius",
"value": "15"
},
{
"name": "l",
"type": "meters",
"value": "19.6"
}
]
最後に行うべきことは、IOTA 起動関数内の初期化関数を呼び出すことです。次の抜粋は、activate()
関数の変更を示しています :
iotAgentLib.activate(config, function(error) {
if (error) {
console.log('There was an error activating the IOTA');
process.exit(1);
} else {
initSouthbound(function (error) {
if (error) {
console.log('Could not initialize South bound API due to the following error: %s', error);
} else {
console.log('Both APIs started successfully');
}
});
}
});
デバッグに役立ついくつかのログがこのコードに追加されました。
IOTA が完成したら、最後にテストすることです。これを行うには、IOTA を起動して新しいデバイスをプロビジョニングします。プロビジョニングの例は examples/howtoProvisioning1.json
ファイルにあります。デバイスがプロビジョニングされたら、example コマンドを使用して新しい測定値を送信します :
curl -X GET 'http://127.0.0.1:8080/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
これで、デバイスの Context Broker エンティティで測定値を確認できるはずです。
遅延属性を持つ IOTA¶
以前の検討事項¶
IoT Agents は、また、デバイスが報告する代わりに、その測定値の1つの値について問い合わせされる可能性があります。そのためには、デバイスは何らかの種類のメッセージを受信できる必要があります。この場合、IOTA によって送られた値を見るために nc
で HTTP サーバをシミュレートします。また、測定値についてデバイスに問い合わせるためのプロトコル・リクエストの構文を決定する必要があります。分かりやすくするため、測定値のレポートに使用したのと同じ HTTP GET リクエストを使用しますが、データ。ペイロードではなくクエリする属性を示します。次のようなものです :
curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&q=t,l' -i
実際の実装では、サーバは、リクエストを適切なデバイスに送信するために、デバイスがリッスンしている URL とポートを知る必要があります。この例では、デバイスが localhost のポート 9999 をリスンしていると想定します。より複雑なケースでは、デバイスをアドレスにバインドするメカニズムは IOTA 固有のものになります。例えば、OMA Lightweight M2M IOTA はデバイス登録時にデバイスのアドレスを取得し、デバイス固有の情報を MongoDB のドキュメントに保存します。
読み書きの性質の遅延属性であるため、更新のために別の構文を宣言する必要があります。この構文は、サーバの更新に使用される構文を模倣します :
curl -X GET 'http://127.0.0.1:9999/iot/d?i=ULSensor&k=abc&d=t|15,l|19.6' -i
デバイスへの両方のタイプの呼び出しは、d
および q
属性の有無によって区別されます。
これらの呼び出しを行うには、HTTP リクエスト・ライブラリが必要です。この範囲では、mikeal/request
ライブラリが使用されます。これを行うには、次の require 文を初期化コードに追加します :
request = require('request');
request
依存関係を package.json
ファイルに追加します :
"dependencies": [
[...]
"request": "*",
]
require セクションは次のようになります :
var iotAgentLib = require('iotagent-node-lib'),
http = require('http'),
express = require('express'),
request = require('request'),
config = require('./config');
実装¶
QueryContext の実装¶
IOTA で遅延属性メカニズムを実装するために完了する主なステップは、コンテキスト・プロビジョニングのリクエストにハンドラを提供することです。この時点で、updateContext ハンドラと queryContext ハンドラの2つのハンドラを用意する必要があります。これを行うには、まずハンドラを定義する必要があります :
function queryContextHandler(id, type, service, subservice, attributes, callback) {
var options = {
url: 'http://127.0.0.1:9999/iot/d',
method: 'GET',
qs: {
q: attributes.join()
}
};
request(options, function (error, response, body) {
if (error) {
callback(error);
} else {
callback(null, createResponse(id, type, attributes, body));
}
});
}
queryContext リクエストが IOTA ノース・バウンド API に到着するたびに、queryContext ハンドラが呼び出されます。リクエストされたエンティティごとに 1回呼び出され、エンティティ ID と型がパラメータとして渡され、リクエストされた属性のリストが渡されます。この場合、ハンドラはこのパラメータを使用してデバイスへのリクエストを作成します。デバイスの結果が返されると、値は NGSI 属性形式で呼び出し側に返されます。
デバイスからのレスポンスを読み取り可能な形式でフォーマットするために、値を対応する属性にマップする createResponse()
関数を作成しました。この関数は、すべての属性の型が "string" であることを前提としています。実際のシナリオでは、IOTAがその属性の型を推測するために関連するデバイスを取得する必要があります。createResponse()
関数のコードを次に示します :
function createResponse(id, type, attributes, body) {
var values = body.split(','),
responses = [];
for (var i = 0; i < attributes.length; i++) {
responses.push({
name: attributes[i],
type: "string",
value: values[i]
});
}
return {
id: id,
type: type,
attributes: responses
};
}
UpdateContext の実装¶
function updateContextHandler(id, type, service, subservice, attributes, callback) {
var options = {
url: 'http://127.0.0.1:9999/iot/d',
method: 'GET',
qs: {
d: createQueryFromAttributes(attributes)
}
};
request(options, function (error, response, body) {
if (error) {
callback(error);
} else {
callback(null, {
id: id,
type: type,
attributes: attributes
});
}
});
}
updateContext ハンドラは、IOTA ノース・バウンド API に到着する変更リクエストを処理します。リクエストされたエンティティごとに 1回呼び出されます。単一のリクエストには複数のエンティティの更新が含まれることに注意してください。queryContext ハンドラで使用されるのと同じパラメータを使用します。唯一の違いは、属性配列の値です。属性オブジェクトのリストが含まれ、それぞれに名前、型、および値が含まれています。また、ハンドラは、更新された属性のリストを返すためにコールバックを使用する必要があります。
このハンドラでは、属性のNGSI表現をデバイスが期待するULタイプに変換する、createQueryFromAttributes()
というヘルパー関数を使用しました :
function createQueryFromAttributes(attributes) {
var query = "";
for (var i in attributes) {
query += attributes[i].name + '|' + attributes[i].value;
if (i != attributes.length -1) {
query += ',';
}
}
return query;
}
ハンドラ登録¶
両方のハンドラが定義されたら、IOTA に登録し、setup
関数に次のコードを追加する必要があります :
iotAgentLib.setDataUpdateHandler(updateContextHandler);
iotAgentLib.setDataQueryHandler(queryContextHandler);
IOTA テスト¶
これをテストするには、デバイスをシミュレートする HTTP サーバを作成する必要があります。これを行う最も簡単な方法は、netcat
を使用することです。それを起動するには、LinuxおよびMacの場合、コマンドラインから次のコマンドを実行します :
nc -l 9999
これにより、ポート 9999 でリッスンする単純な TCP サーバが開き、IOTA からのリクエストが表示されます。完全なワークフローが機能するように、そしてアプリケーション側でレスポンスを受け取るために、HTTP レスポンスを nc
コンソールに書き込む必要があります。ただし、テスト目的のためにはこれは必要ありません)。
netcat は簡単な接続性をテストするのに最適ですが、完全なシナリオを稼働させるにはちょっと複雑なものが必要です (少なくともあなたのレスポンスを信じられないほど速く送る必要はなくても)。そうするために、シンプルなエコー・サーバが作成され、その /iot/d
パスへのクエリに 42
が返されます。これを使用して、属性を1つずつテストすることができます。または、リクエストを受け入れてより複雑なレスポンスを与えるために変更できます。エコー・サーバのスクリプト を IoTAgent の同じフォルダにコピーします。echo.js が同じ依存関係を使用するためです。エコー・サーバを実行するには、次のコマンドを実行してください :
node echo.js
模擬サーバが起動されたら (nc またはエコー・サーバのいずれか)、次の手順を実行して実装をテストします :
1.2つの遅延属性を持つデバイスをプロビジョニングします。次のリクエストを例として使用できます :
POST /iot/devices HTTP/1.1
Host: localhost:4041
Content-Type: application/json
fiware-service: howtoserv
fiware-servicepath: /test
Cache-Control: no-cache
Postman-Token: 993ac66b-72da-9e96-ab46-779677a5896a
{
"devices": [
{
"device_id": "ULSensor",
"entity_name": "Sensor01",
"entity_type": "BasicULSensor",
"lazy": [
{
"name": "t",
"type": "celsius"
},
{
"name": "l",
"type": "meters"
}
],
"attributes": [
]
}
]
}
2.エンティティ属性の1つに対して、queryContext または updateContext を実行します。curl コマンドの NGSI クライアントを使用します。
POST /v1/queryContext HTTP/1.1
Host: localhost:1026
Content-Type: application/json
Accept: application/json
Fiware-Service: howtoserv
Fiware-ServicePath: /test
Cache-Control: no-cache
Postman-Token: 1dc568a1-5588-059c-fa9b-ff217a7d7aa2
{
"entities": [
{
"isPattern": "true",
"id": ".*",
"type": "BasicULSensor"
}
],
"attributes" : [
"l"]
}
3.nc コンソールで受信したリクエストが期待どおりであることを確認してください。
4.netcat を使用する場合は、適切な HTTP レスポンスでリクエストにレスポンスし、queryContext または updateContext リクエストの結果が期待されているかどうかを確認します。t と l 属性へのクエリに対する HTTP レスポンスの例は次のようになります :
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Length: 3
5,6
この同じレスポンスは、前者ではボディが読み取られなくても、テスト目的の更新とクエリの両方に使用できます。
構成管理¶
一部の IoT Agent では、エージェントに登録されているデバイスや設定を知っているか、新しいデバイスが登録されたときに何らかのアクションを実行するのが便利です。このすべての構成およびプロビジョニング・アクションは、プロビジョニング・ハンドラとプロビジョニング API の2つのメカニズムを使用して実行できます。
プロビジョニング・ハンドラ¶
ハンドラは、新しいデバイスまたは構成がプロビジョニングされるたびに IOTA が動作する方法を提供します。これは、外部サービスにデバイスを登録したり、デバイスに関する重要な情報を保存したり、新しい設定の場合は新しいポートでリッスンするために使用できます。私たちが開発している簡単な例では、新しいデバイスまたは構成がプロビジョニングされるたびに、受け取った情報を表示します。
プロビジョニング・ハンドラのワーキング・セットを使用するには、2つの手順を完了する必要があります。まず、ハンドラ自体を定義します。ここでは、設定ハンドラの定義を見ることができます :
function configurationHandler(configuration, callback) {
console.log('\n\n* REGISTERING A NEW CONFIGURATION:\n%s\n\n', JSON.stringify(configuration, null, 4));
callback(null, configuration);
}
これからわかるように、ハンドラはプロビジョニングされているデバイスや設定、コールバックを受信します。ハンドラは IOTA が正しく動作するためにコールバックを一度呼び出さなければなりません。エラーがパラメータとしてコールバックに渡された場合、プロビジョニングは中断されます。エラーがなければ、プロビジョニング・プロセスは続行されます。このメカニズムは、セキュリティ・メカニズムの実装、または IOTA へのデバイスのプロビジョニングのフィルタリングに使用できます。
同じ device
または configuration
オブジェクトがコールバックに渡されることにも注意してください。これにより、IOTA は、プロビジョニングの情報を追加または制限するために、ユーザによってプロビジョニングされた値の一部を変更することができます。この機能をテストするには、プロビジョニング・ハンドラを使用してプロビジョニング・デバイスのタイプの値を CertifiedType
に変更します。プロビジョニングで実行された検証プロセスの一部が反映されます :
function provisioningHandler(device, callback) {
console.log('\n\n* REGISTERING A NEW DEVICE:\n%s\n\n', JSON.stringify(device, null, 4));
device.type = 'CertifiedType';
callback(null, device);
}
ハンドラが定義されたら、新しいハンドラのセットを IOTAgent に登録する必要があります :
iotAgentLib.setConfigurationHandler(configurationHandler);
iotAgentLib.setProvisioningHandler(provisioningHandler);
これで、ノース・バウンド API にプロビジョニング・リクエストを送信することで実装をテストできます。新しいデバイスをプラットフォームにプロビジョニングし、プロビジョニングされたデバイスのリストをリクエストすると、プロビジョニングされたデバイスのタイプが CertifiedType
に変更されたことがわかります。