Context Consumer (CC) とは
Context Consumer(CC)、つまり、コンテキストの消費者は、コンテキスト情報を利用するエンティティで、コンテキスト・ベースのアプリケーションです。CC は、Context Broker (CB) にリクエストを送信し、コンテキスト情報を取得したり、特定のインタフェースを介して Context Producer (CP) を直接呼び出すことができます。CC が情報を取得するもう1つの方法は、特定の条件に一致するコンテキスト情報の更新をサブスクライブすることです。例えば、特定のエンティティのセットに関連する情報をサブスクリプションするなどです。CC は、目的のためにサブスクリプションにコールバック操作を登録するので、CB は、このコールバック機能を呼び出すことによって、コンテキストに関連する更新について、CB に CC に通知します。
本記事では、温度の情報を LCD (液晶ディスプレイ) に表示する、コンテキスト・コンシューマ型の FIWARE 対応 IoT デバイス を作成します。温度は、Orion Context Broker に保持されている WeatherObserved 型エンティティの temperature 属性から取得します。その方法は、定期的に取得するポーリングのような同期方式ではなく、温度のコンテキスト情報の更新をサブスクライブすることで、温度に変化があった場合に通知を受ける、非同期方式です。
WeatherObserved 型のエンティティの情報を提供する、デバイスには、以前の記事で作成したコンテキスト・プロデューサ型の FIWARE 対応 IoT デバイスを利用します。
温度情報のサブスクライブ
IoT デバイスが温度の変化の通知を受けるために、Orion にサブスクライブのリクエストを行います。
Orion へのサブスクライブのリクエスト
WeatherObserved 型のエンティティである、urn:ngsi-ld:WeatherObserved:sensor001 の temperature 属性値が変化したときに、Orion が http://192.168.1.2/ に通知を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#!/bin/sh curl -v 192.168.1.1:1026/v2/subscriptions -s -S -H 'Content-Type: application/json' -d @- <<EOF { "description": "A subscription to get info about sensor001", "subject": { "entities": [ { "id": "urn:ngsi-ld:WeatherObserved:sensor001", "type": "WeatherObserved" } ], "condition": { "attrs": [ "temperature" ] } }, "notification": { "http": { "url": "http://192.168.1.2/" }, "attrs": [ "temperature" ] }, "expires": "2040-01-01T14:00:00.00Z", "throttling": 1 } EOF |
Orion からデバイスへの通知
サブスクライブしたエンティティの温度に変化があった場合、デバイスは、Orion から通知を受け取ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "description": "A subscription to get info about sensor001", "subject": { "entities": [ { "id": "urn:ngsi-ld:WeatherObserved:sensor001", "type": "WeatherObserved" } ], "condition": { "attrs": [ "temperature" ] } }, "notification": { "http": { "url": "http://192.168.1.2/" }, "attrs": [ "temperature" ] } } |
コンテキスト・コンシューマ型の IoT デバイスの作成
先ほど説明した、サブスクリプションとノーティフィケーション (通知)を受信の仕組みを IoT デバイスに実装することで、デバイスは、温度に変化があったときに、温度値を含む通知を受け取り、LCD に 温度値を表示できます。サブスクリプションは、createSubscription() 関数で、受信したノーティフィケーションの処理は、accumulator() 関数で行っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
/** * context-consumer-01.ino * * Context consumer * * Copyright 2018 Kazuhito Suda * * Created on: December 9, 2018 * */ #include <WiFi.h> #include <HTTPClient.h> #include <WebServer.h> #include <ArduinoJson.h> #include <Wire.h> char essid[] = "your essid"; char passphrase[] = "your passphrase"; char orion[] = "http://192.168.1.1:1026"; const char entityType[] = "WeatherObserved"; const char entityId[] = "urn:ngsi-ld:WeatherObserved:sensor001"; byte ST7032i = 0x3E; // LCD WebServer server(80); void setup() { Serial.begin(115200); Serial.println("Start"); connectWifi(); server.on("/", accumulator); server.onNotFound(handleNotFound); server.begin(); Wire.begin(); initLCD(); createSubscription(); } void loop() { server.handleClient(); } void accumulator() { if (server.method() == HTTP_POST && server.args() == 1) { const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(0) + JSON_OBJECT_SIZE(2) + 2*JSON_OBJECT_SIZE(3) + 170; StaticJsonBuffer <bufferSize> jsonBuffer; JsonObject& root = jsonBuffer.parseObject(server.arg(0)); const char* subscriptionId = root["subscriptionId"]; JsonObject& data0 = root["data"][0]; const char* data0_id = data0["id"]; const char* data0_type = data0["type"]; JsonObject& data0_temperature = data0["temperature"]; const char* data0_temperature_type = data0_temperature["type"]; float data0_temperature_value = data0_temperature["value"]; Serial.printf("%s %s %s %2.2f\n", subscriptionId, data0_id, data0_type, data0_temperature_value); char buff[9]; sprintf(buff, "%2.2f", data0_temperature_value); printLCD(buff); server.send(200); } else { sendErrorMessage(400, "BadRequest", "service not found"); } } void createSubscription() { HTTPClient http; char url[128]; sprintf(url, "%s/v2/subscriptions", orion); http.begin(url); http.addHeader("Content-Type", "application/json"); // payload const size_t bufferSize = 3*JSON_ARRAY_SIZE(1) + 2*JSON_OBJECT_SIZE(1) + 3*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(5); StaticJsonBuffer <bufferSize> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["description"] = "A subscription to get info about sensor001"; JsonObject& subject = root.createNestedObject("subject"); JsonArray& subject_entities = subject.createNestedArray("entities"); JsonObject& subject_entities_0 = subject_entities.createNestedObject(); subject_entities_0["id"] = entityId; subject_entities_0["type"] = entityType; JsonObject& subject_condition = subject.createNestedObject("condition"); JsonArray& subject_condition_attrs = subject_condition.createNestedArray("attrs"); subject_condition_attrs.add("temperature"); JsonObject& notification = root.createNestedObject("notification"); JsonObject& notification_http = notification.createNestedObject("http"); notification_http["url"] = "http://" + WiFi.localIP().toString() + "/"; JsonArray& notification_attrs = notification.createNestedArray("attrs"); notification_attrs.add("temperature"); root["expires"] = "2040-01-01T14:00:00.00Z"; root["throttling"] = 1; String postData = ""; root.printTo(postData); Serial.println("Subscription : "); Serial.printf("POST %s\n", url); Serial.println(postData); int httpCode = http.POST(postData); if(httpCode > 0) Serial.printf("httpCode: %d\n", httpCode); else Serial.printf("POST failed, error: %s\n", http.errorToString(httpCode).c_str()); http.end(); } void handleNotFound() { sendErrorMessage(400, "BadRequest", "service not found"); } void sendErrorMessage(int status, String err, String description) { String message = "{\"error\":\"" + err + "\",\"description\":\"" + description + "\"}"; server.send(status, "application/json", message); } void connectWifi() { WiFi.mode(WIFI_STA); WiFi.begin(essid, passphrase); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(100); } Serial.println(""); Serial.println("Connected"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void initLCD() { byte cmds[] = {0x38, 0x39, 0x14, 0x73, 0x56, 0x6c, 0x38, 0x01, 0x0c}; char fiware[] = "FIWARE "; for (int i = 0; i < sizeof(cmds); i++) { writeCmd(cmds[i]); } for (int i = 0; i < sizeof(fiware); i++) { writeData(fiware[i]); } writeCmd(0xc0); for (int i = 0; i < 8; i++) { writeData(' '); } writeCmd(0x0C); } void printLCD(char str[]) { writeCmd(0xC0); for (int i = 0; i < strlen(str); i++) { writeData(str[i]); } } void writeData(byte data) { delay(1); Wire.beginTransmission(ST7032i); Wire.write(0x40); Wire.write(data); Wire.endTransmission(); } void writeCmd(byte cmd) { delay(30); Wire.beginTransmission(ST7032i); Wire.write(0x00); Wire.write(cmd); Wire.endTransmission(); } |
IoT デバイスを起動
IoT デバイスを起動して、プログラムの実行が開始すると、Orion にサブスクリプションをリクエストし、Orion からの通知を待ちます。Orion から通知がある度に、LCD に温度値が更新されます。
参考情報 : LCD について
LCD には、秋月電子通商の AE-AQM0802 という、I2C接続小型 LCD モジュール (8×2行) ピッチ変換キットを利用しました。液晶のコントローラ IC に ST7032 を利用していました。ST7032 を使用する LCD モジュールであれば、I2C のアドレスに注意すれば、プログラムはそのまま使えると思われます。
シリアル・コンソールの出力
シリアル・コンソールの出力は、次のようなものです。直後に、Orion へサブスクリプションを行った後、Orion からの通知がある度に、通知の内容を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Start .................... Connected IP address: 192.168.1.2 Subscription : POST http://192.168.1.1:1026/v2/subscriptions {"description":"A subscription to get info about sensor001","subject":{"entities":[{"id":"urn:ngsi-ld:WeatherObserved:sensor001","type":"WeatherObserved"}],"condition":{"attrs":["temperature"]}},"notification":{"http":{"url":"http://192.168.1.2/"},"attrs":["temperature"]}} httpCode: 201 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.55 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.59 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.61 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.64 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.61 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.63 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.65 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.69 5c15aeea998dcaffc9c14ddf urn:ngsi-ld:WeatherObserved:sensor001 WeatherObserved 21.67 |
サブスクリプション
サブスクリプションの一覧
Orion に対して、次のリクエストを実行すると、サブスクリプションの一覧が取得できます。サブスクリプションの実行状況も確認できます。
実行すると、次のようなレスポンスが返り、温度のサブスクリプションを確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[ { "id": "5c15aeea998dcaffc9c14ddf", "description": "A subscription to get info about sensor001", "status": "active", "subject": { "entities": [ { "id": "urn:ngsi-ld:WeatherObserved:sensor001", "type": "WeatherObserved" } ], "condition": { "attrs": [ "temperature" ] } }, "notification": { "timesSent": 327, "lastNotification": "2018-12-09T07:14:15.00Z", "attrs": [ "temperature" ], "attrsFormat": "normalized", "http": { "url": "http://192.168.1.2/" }, "lastSuccess": "2018-12-09T07:14:16.00Z" } } ] |
サブスクリプションの削除
サブスクリプションの一覧で確認した、サブスクリプション ID を指定して、サブスクリプションを削除することができます。
上記の場合、サブスクリプション ID は、”5c15aeea998dcaffc9c14ddf” なので、これを指定すると削除できます。サブスクリプションを削除すると、Orion から デバイスへの通知が行われず、LCD の温度値の更新も行われなくなります。
1 |
curl -X DELETE 192.168.1.1:1026/v2/subscriptions/5c15aeea998dcaffc9c14ddf |