デバイスインテグレーション

デバイスをThings Cloudに統合するための基本的なライフサイクルは、コンセプトガイドのデバイスのインターフェースで説明されています。このセクションでは、このライフサイクルがRESTレベルでどのように実装されるかを説明します。 ライフサイクルは、スタートアップフェーズとサイクルフェーズの2つのフェーズで構成されます。

スタートアップフェーズでは、デバイスをThings Cloudに接続し、インベントリのデバイスデータを更新します。 また、オペレーションに必要なクリーンナップタスクも実行します。次の手順で構成されます:

Startup phase

サイクルフェーズへと続きます。 インベントリを継続的に更新し、メジャーメント、アラーム、イベントを書き込み、必要に応じてオペレーションを実行します。 これは、デバイスの電源が切れるまで実行される、デバイスの「メインループ」となります。 このループは次の手順で構成されます:

Cycle phase

データの参照モデルについてはリファレンスガイドのデバイス管理ライブラリセンサー・ライブラリ をご覧ください。

スタートアップフェーズ

ステップ 0: デバイスの認証情報をリクエストする

Things Cloudへのすべてのリクエストは認証される必要があり、デバイスからのリクエストも認証される必要があります。個々の認証情報をデバイスに割り当てる場合は、デバイス認証情報APIを使用して新しい認証情報を自動的に生成できます。これを行うには、最初の起動時にAPIを介してデバイスの認証情報をリクエストし、それ以降のリクエストに備えてデバイス上のローカルに保存します。

このプロセスは次のように動作します。

デバイス側の視点から見ると、これは単一のRESTのリクエストです:

POST /devicecontrol/deviceCredentials
Content-Type: application/vnd.com.nsn.cumulocity.deviceCredentials+json;ver=...
Authorization: Basic ...
{
  "id" : "0000000017b769d5"
}

デバイスはこのリクエストを繰り返し発行します。ユーザーがまだデバイスを登録して受け入れていない間、リクエストは「404 Not Found」を返します。デバイスが受け入れられると、次のレスポンスが返されます:

HTTP/1.1 200 OK
Content-Type: application/vnd.com.nsn.cumulocity.deviceCredentials+json;ver=...
Content-Length: ...
{
  "id" : "0000000017b769d5",
  "self" : "<<URL of new request>>",
  "tenantId" : "test",
  "username" : "device_0000000017b769d5",
  "password" : "3rasfst4swfa"
}

これで、デバイスはテナントID、ユーザー名、パスワードを使用してThings Cloudに接続できます。

ステップ 1: デバイスが登録済みかどうかを確認する

デバイスの固有IDは、デバイスをインベントリに登録するためにも使用されます。登録はIdentity APIを用いて行います。ID APIでは、各管理対象オブジェクトをタイプで識別される複数の識別子に関連付けることができます。たとえば、ハードウェアシリアル番号の場合は「c8y_Serial」、MACアドレスの場合は「c8y_MAC」、IMEIの場合は「c8y_IMEI」となります。

デバイスがすでに登録されているかどうかを確認するには、デバイス識別子とそのタイプを使用して、Identity APIのGET要求を使用します。次の例では、ハードウェアシリアル番号「0000000017b769d5」のRaspberry Piをチェックしています。

GET /identity/externalIds/c8y_Serial/raspi-0000000017b769d5 HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/vnd.com.nsn.cumulocity.externalId+json; charset=UTF-8; ver=0.9
...
{
    "externalId": "raspi-0000000017b769d5",
    "managedObject": {
        "id": "2480300",
        "self": "https://.../managedObjects/2480300"
    },
    "self": "https://.../identity/externalIds/c8y_Serial/raspi-0000000017b769d5",
    "type": "c8y_Serial"
}

MACアドレスはグローバルで固有であることが保証されていますが、ハードウェアのシリアル番号は異なるハードウェア間で重複する可能性があることに注意してください。したがって、上記の例では、シリアル番号の前に「raspiー」を付けています。

ここでは、デバイスはすでに登録されており、ステータスコード200が返されます。レスポンスでは、インベントリのデバイスへのURLが「managedObject.self」に返されます。このURLは、後でデバイスを操作するために使用することができます。

デバイスがまだ登録されていない場合は、ステータスコード404とエラーメッセージが返されます:

GET /identity/externalIds/c8y_Serial/raspi-0000000017b769d6 HTTP/1.1

HTTP/1.1 404 Not Found
Content-Type: application/vnd.com.nsn.cumulocity.error+json;charset=UTF-8;ver=0.9
...
{
    "error": "identity/Not Found",
    "info": "https://www.cumulocity.com/guides/reference-guide/#error_reporting",
    "message": "External id not found; external id = ID [type=c8y_Serial, value=raspi-0000000017b769d6]"
}

ステップ 2: インベントリにデバイスを作成する

上記のステップ1でデバイスを表すマネージドオブジェクトが存在しなかった場合、Things Cloud上にマネージドオブジェクトが作成されます。 マネージドオブジェクトは、デバイスとそのインスタンスとメタデータの両方を記述します。 インスタンスデータには、ハードウェアおよびソフトウェアの情報、シリアル番号、デバイス構成データが含まれます。 メタデータには、サポートされているオペレーションを含むデバイスの機能が記述されています。

マネージドオブジェクトを作成するには、インベントリAPIのマネージドオブジェクトコレクションに対してPOST要求を出します。 次の例では、Linuxエージェントを使用してRaspberry Piを作成しています:

POST /inventory/managedObjects HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
Accept: application/vnd.com.nsn.cumulocity.managedObject+json
...
{
    "name": "RaspPi BCM2708 0000000017b769d5",
    "type": "c8y_Linux",
    "c8y_IsDevice": {},
    "com_cumulocity_model_Agent": {},
    "c8y_SupportedOperations": [ "c8y_Restart", "c8y_Configuration", "c8y_Software", "c8y_Firmware" ],
    "c8y_Hardware": {
        "revision": "000e",
        "model": "RaspPi BCM2708",
        "serialNumber": "0000000017b769d5"
    },
    "c8y_Configuration": {
        "config": "#Fri Aug 30 09:13:56 BST 2013\nc8y.log.eventLevel=INFO\n..."
    },
    "c8y_Mobile": {
         "imei": "861145013087177",
        "cellId": "4904-A496",
        "iccid": "89490200000876620613"
    },
    "c8y_Firmware": {
        "name": "raspberrypi-bootloader",
        "version": "1.20130207-1"
    },
    "c8y_Software": {
        "pi-driver": "pi-driver-3.4.5.jar",
        "pi4j-gpio-extension": "pi4j-gpio-extension-0.0.5.jar",
        ...
    }
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json;charset=UTF-8;ver=0.9
...
{
    "id": "2480300",
    "lastUpdated": "2013-08-30T10:12:24.378+02:00",
    "name": "RaspPi BCM2708 0000000017b769d5",
    "owner": "admin",
    "self": "https://.../inventory/managedObjects/2480300",
    "type": "c8y_Linux",
    "c8y_IsDevice": {},
    ...
    "assetParents": {
        "references": [],
        "self": "https://.../inventory/managedObjects/2480300/assetParents"
    },
    "childAssets": {
        "references": [],
        "self": "https://.../inventory/managedObjects/2480300/childAssets"
    },
    "childDevices": {
        "references": [],
        "self": "https://.../inventory/managedObjects/2480300/childDevices"
    },
    "deviceParents": {
        "references": [],
        "self": "https://.../inventory/managedObjects/2480300/deviceParents"
    }
}

上記の例では、デバイスの多数のメタデータ項目が含まれています。

詳細はリファレンスガイドの デバイス管理ライブラリ をご覧ください。

デバイスが正常に作成された場合は、ステータスコード201が返されます。 例にあるように、元のリクエストに「Accept」ヘッダーが含まれる場合、今後のリクエストでオブジェクトを参照するためのIDとURLを含む、作成されたオブジェクト全体が返されます。 返されるオブジェクトには、子デバイスのコレクションへのリファレンスと、子をデバイスに追加するために使用できる子アセットも含まれます(下記参照)。

ステップ 3: デバイスを登録する

新しいデバイスを作成後、ステップ1で説明したように、そのデバイスを組み込み識別子と関連付けることができます。これにより、デバイスは次回の電源投入後にThings Cloudで検出できるようになります。

上記の例に続き、新しく作成したデバイス「2480300」をそのハードウェアのシリアル番号に関連付けます:

POST /identity/globalIds/2480300/externalIds HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.externalId+json
Accept: application/vnd.com.nsn.cumulocity.externalId+json
...
{
    "type" : "c8y_Serial",
    "externalId" : "raspi-0000000017b769d5"
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.externalId+json;charset=UTF-8;ver=0.9
...
{
    "externalId": "raspi-0000000017b769d5",
    "managedObject": {
        "id": "2480300",
        "self": "https://.../inventory/managedObjects/2480300"
    },
    "self": "https://.../identity/externalIds/c8y_Serial/raspi-0000000017b769d5",
    "type": "c8y_Serial"
}

ステップ 4: インベントリのデバイスを更新する

ステップ1で、デバイスが以前に登録されていると返された場合、インベントリのデバイスの表記が最新であるかを確認する必要があります。 そのために、PUT要求がインベントリのデバイスのURLへ送信されます。 変更可能なフラグメントのみが転送されることに注意してください。(フラグメントの詳細については、コンセプトガイドのThings Cloudドメインモデルをご覧ください。)

たとえば、、デバイスのハードウェア情報は通常変更されませんが、ソフトウェアのインストールが変更されている可能性があります。 したがって、デバイスの再起動後に、インベントリのソフトウェア情報を最新の状態にする必要があリます:

PUT /inventory/managedObjects/2480300 HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.managedObject+json
...
{
    "c8y_Software": {
        "pi-driver": "pi-driver-3.4.6.jar",
        "pi4j-gpio-extension": "pi4j-gpio-extension-0.0.5.jar"
    }
}   

HTTP/1.1 200 OK

エージェントからデバイス名を更新しないよう注意して下さい! エージェントは、インベントリで識別できるようにデバイスのデフォルト名を作成しますが、ユーザーがアセット管理の情報より、この名前の編集や更新ができるようにするべきです。

ステップ 5: 子デバイスを検出し、インベントリでそれらを作成または更新する

センサネットワークの複雑さに応じて、デバイスは、関連する子デバイスを有することができます。家庭のさまざまな部屋にさまざまなセンサーやコントロールを設置するホームオートメーションゲートウェイなどがその良い例です。子デバイスの基本登録方法は、子デバイスが通常エージェントインスタンスを実行しないというところまでは、メインデバイスの登録と同じです(したがって、 “com_cumulocity_model_Agent” フラグメントは省略されます)。 デバイスを子にリンクするには、オブジェクトの作成時に返された子デバイスのURLへ、POST要求を送信します(上記参照)。

たとえば、下記URLを持つ子デバイスが作成されたとします。 “https://…/inventory/managedObjects/2543801” このデバイスを親デバイスとリンクするには、次のコマンドを発行します。

POST /inventory/managedObjects/2480300/childDevices HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.managedObjectReference+json
{ "managedObject" : { "self" : "https://.../inventory/managedObjects/2543801" } } 

HTTP/1.1 201 Created

最後に、URLにDELETE要求を発行することによって、デバイスと参照を削除することができます。たとえば、作成したばかりの親デバイスから子デバイスへの参照を削除するには、次のコマンドを発行します:

DELETE /inventory/managedObjects/2480300/childDevices/2543801 HTTP/1.1

HTTP/1.1 204 No Content

この場合、インベントリのデバイス自体は削除されず、参照のみが削除されます。デバイスを削除するには、次のコマンドを発行します:

DELETE /inventory/managedObjects/2543801 HTTP/1.1

HTTP/1.1 204 No Content

このリクエストは、登録情報、メジャーメント、アラーム、イベント、オペレーションなど、デバイスに関連するすべてのデータを削除します。 通常、デバイスを自動的に削除することはお勧めできません。たとえば、デバイスの接続が一時的に失われただけの場合、通常そのデバイスに関連するすべての履歴情報を失いたくないでしょう。

オペレーションの操作

Things Cloudの各オペレーションは、実行フローを通じて循環されます。Things Cloudアプリケーションを介してオペレーションが作成されると、PENDING状態になります(つまり、実行のためにキューに入れられてはいても、まだ実行されていない状態です)。エージェントがオペレーションを選択して実行を開始すると、そのオペレーションにはThings Cloudで「EXECUTING」というマークが付けられます。その後、エージェントは、デバイスまたはその子デバイスに対してオペレーションを実行します(たとえば、デバイスを再起動したり、リレーを設定したりします)。その後、デバイスまたはその子デバイスの新しい状態を反映してインベントリを更新するでしょう (例:インベントリのリレーの現在の状態を更新します)。次に、エージェントはThings Cloud内のオペレーションを「SUCCESSFUL」または「FAILED」のいずれかとしてマークし、場合によってはエラーを表示します。

Operation status diagram

この実行フローの利点は、オフラインで一時的に通信範囲外にあるデバイスにも対応できることです。 また、ファームウェアのアップグレードなど、再起動が必要なオペレーションをデバイスが対応できるようになります。再起動後、デバイスは以前に実行した内容を知る必要があるため、すべての「EXECUTING」オペレーションを照会して成功したかどうかを確認する必要があります。また、キューに入れられた新しいオペレーションをリッスンする必要があります。

ステップ 6: オペレーションを終了し、サブスクライブする

まだステータスが「EXECUTING」であるオペレーションをクリーンナップするには、エージェントIDおよび状況別にオペレーションを照会します。 下記の例では、リクエストは次のようになります:

GET /devicecontrol/operations?agentId=2480300&status=EXECUTING HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/vnd.com.nsn.cumulocity.operationCollection+json;; charset=UTF-8; ver=0.9
...
{
    "next": "https://.../devicecontrol/operations?agentId=2480300&status=EXECUTING",
    "operations": [
        {
            "creationTime": "2013-08-29T19:49:15.239+02:00",
            "deviceId": "2480300",
            "id": "2593101",
            "self": "https://.../devicecontrol/operations/2480300",
            "status": "EXECUTING",
            "c8y_Restart": {
            }
        }
    ],
    "statistics": {
        "currentPage": 1,
        "pageSize": 2000
    },
    "self": "https://.../devicecontrol/operations?agentId=2480300&status=EXECUTING"
}

再起動はうまくいったようです–もとに戻りました。オペレーションを「SUCCESSFUL」に設定します。

PUT /devicecontrol/operations/2480300 HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.operation+json
{
    "status": "SUCCESSFUL"
}

HTTP/1.1 200 OK

次に、Things Cloudで作成された新しいオペレーションをリッスンします。 このメカニズムは、リファレンスガイドのリアルタイム通知で記述されており、標準Bayeuxプロトコルに基づいています。 まず、ハンドシェイクが必要です。 ハンドシェイクは、エージェントが通知用として対応しているプロトコルをThings Cloudに伝え、クライアントIDをエージェントに割り当てます。

POST /devicecontrol/notifications HTTP/1.1
Content-Type: application/json
...
[ {
    "id": "1",
    "supportedConnectionTypes": ["long-polling"],
    "channel": "/meta/handshake",
    "version": "1.0"
} ]

HTTP/1.1 200 OK
...
[ {
    "id": "1",
    "supportedConnectionTypes": ["websocket","long-polling"],
    "channel": "/meta/handshake",
    "version": "1.0",
    "clientId": "139jhm07u1dlry92fdl63rmq2c",
    "minimumVersion": "1.0",
    "successful": true
}]

その後、それぞれのデバイスのエージェントはオペレーションを実行する為の通知にサブスクライブする必要があります。 これは、デバイスのIDをサブスクリプションのチャネルとして使用するPOST要求で行います。 次の例では、Raspberry Piがエージェントを実行し、IDは2480300です:

POST /devicecontrol/notifications HTTP/1.1
Content-Type: application/json
...
[ {
    "id": "2",
    "channel": "/meta/subscribe",
    "subscription": "/2480300",
    "clientId":"139jhm07u1dlry92fdl63rmq2c"
}]

HTTP/1.1 200 OK
...
[ { 
    "id":"2",
    "channel": "/meta/subscribe",
    "subscription": "/2480300",
    "successful": true,
} ]

最後に、デバイスは接続され、オペレーションが送信されるのを待ちます。

POST /devicecontrol/notifications HTTP/1.1
Content-Type: application/json
...
[ {
    "id": "3",
    "connectionType": "long-polling",
    "channel": "/meta/connect",
    "clientId": "139jhm07u1dlry92fdl63rmq2c"
} ]

このリクエストは、オペレーションが通るまで保留されます。つまり、HTTPサーバーがすぐに応答できずとも、デバイスのオペレーションが行われるまで待機します(ロングポーリング)。

新しいオペレーションをサブスクライブする時、「PENDING」のオペレーションがある可能性に注意してください。これらを全て照会する必要があります。クエリとサブスクリプションの間のオペレーションが失われないよう、これらはサブスクリプション後に実行されます。この技術的な処理は、前述の「EXECUTING」操作と同じですが、代わりに「PENDING」を使用します。

GET /devicecontrol/operations?agentId=2480300&status=PENDING HTTP/1.1

サイクルフェーズ

ステップ 7: オペレーションを実行する

まず、エージェントのオペレーションがキューに入れられていると仮定します。これにより、上記のロングポーリングのリクエストがオペレーションで返されます。下記は、単一の構成オペレーションでのレスポンスの一例です:

HTTP/1.1 200 OK
...
[ 
    {
        "id": "139", 
        "data": {
            "creationTime":"2013-09-04T10:53:35.128+02:00",
            "deviceId": "2480300",
            "id": "2546600",
            "self": "https://.../devicecontrol/operations/2546600",
            "status": "PENDING",
            "description": "Configuration update",
            "c8y_Configuration": { "config": "#Wed Sep 04 10:54:06 CEST 2013\n..." }
        }, 
        "channel": "/2480300"
    }, {
        "id": "3",
        "successful": true,
        "channel": "/meta/connect"
    }
]

エージェントがオペレーションを取得すると、Things Cloud上ではPUT要求を使用して「EXECUTING」状態になります(上記の「FAILED」の例を参照)。 デバイスでオペレーションを実行し、Things Cloudインベントリで必要な更新を実行します。最後に、結果に応じてオペレーションは「SUCCESSFUL」または「FAILED」に設定されます。 そして、上記のように 「/devicecontrol/notifications」 に再接続し、次のオペレーションを待ちます。

キューに入れられたオペレーションが失われないように、デバイスは10秒以内にサーバーに再接続する必要があります。 これは、Things Cloudがリアルタイムのデータをバッファーする時間です。 間隔は、ハンドシェイク時に指定できます。

ステップ 8: インベントリを更新する

通常、デバイスのインベントリ内容は最新の状態を表すので、継続的な更新の対象になります。 例として、GPSチップを搭載したデバイスを考えてみましょう。このデバイスは、インベントリの現在地を最新の状態に保ちます。 同時に、位置の更新とイベントを報告して、位置の追跡を維持します。技術的にこのような更新は、ステップ4と同じリクエストで報告されます。

ステップ 9: メジャーメントを送信する

Things Cloudで新しいメジャーメントを作成するには、メジャーメントとともにPOST要求を発行します。 下記は、信号強度のメジャーメントを作成する例になります。

POST /measurement/measurements HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.measurement+json
...
{
    "source": { "id": "2480300" },
    "time": "2013-07-02T16:32:30.152+02:00",
    "type": "huawei_E3131SignalStrength",
    "c8y_SignalStrength": {
        "rssi": { "value": -53, "unit": "dBm" },
        "ber": { "value": 0.14, "unit": "%" } 
    }
}

HTTP/1.1 201 Created

ステップ 10: イベントを送信する

同様に、イベントにもPOST要求を使用します。 次の例は、GPSセンサーからの位置の更新を示しています。

POST /event/events HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.event+json
...
{
    "source": { "id": "1197500" },
    "text": "Location updated",
    "time": "2013-07-19T09:07:22.598+02:00",
    "type": "queclink_GV200LocationUpdate",
    "c8y_Position": {
        "alt": 73.9,
        "lng": 6.151782,
        "lat": 51.211971
    }
}

HTTP/1.1 201 Created

Things Cloudのすべてのデータタイプには、追加でフラグメントという形の任意の拡張を含むことができます。上記では、イベントに位置情報が含まれていますが、自己定義のフラグメントを追加することも可能です。

ステップ 11: アラームを送信する

アラームとは、解決するために人の介入を必要とするイベントを表します。 たとえば、デバイスのバッテリーが切れた場合、誰かがデバイスのバッテリーを交換する必要があります。 アラームの作成は、技術的にはイベントの作成と非常によく似ています。

POST /alarm/alarms HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
Accept: application/vnd.com.nsn.cumulocity.alarm+json
...
{
    "source": { "id": "10400" },
    "text": "Tracker lost power",
    "time": "2013-08-19T21:31:22.740+02:00",
    "type": "c8y_PowerAlarm",
    "status": "ACTIVE",
    "severity": "MAJOR",
}

HTTP/1.1 201 Created
Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
...
{
    "id": "214600",
    "self": "https://.../alarm/alarms/214600",
    ...
}

ただし、同じようなアラームがシステム内ですでに有効になっている場合は、デバイスのアラームを作成しない方がよいでしょう。多数のアラームを作成すると、ユーザーインタフェースがいっぱいになり、すべてのアラームを手動でクリアする必要が生じる場合があります。上の例は、Raspberry Piのアクティブアラームを見つけるためのものです。

GET /alarm/alarms?source=2480300&status=ACTIVE HTTP/1.1

イベントとは異なり、アラームは更新ができます。 問題が解決した場合(例:バッテリーが交換され、電源が復旧した場合)、対応するアラームが自動的にクリアされ、手動での作業が不要になります。これは、アラームのURLへのPUT要求によって実行できます。 上記のアラーム作成の例では、「Accept」ヘッダーを使用して、レスポンス内の新しいアラームのURLを取得しました。このURLを使用してアラームをクリアできます。

PUT /alarm/alarms/214600 HTTP/1.1
Content-Type: application/vnd.com.nsn.cumulocity.alarm+json
...
{
    "status": "CLEARED"
}

HTTP/1.1 200 OK

イベントの送信、あるいはアラームの発生のどちらを選ぶか判断ができない場合は、とりあえずイベントを発生させて、ユーザー自身にCEP ルール より、イベントをアラームへ変換するかどうか決定させるとよいでしょう。