Context Provider (CPr)
コンテキスト・プロバイダ (CPr) は、コンテキスト・プロデューサ (CP) の特別な種類です。同期モードでオンデマンドでのコンテキスト情報を提供します。つまり、コンテキスト・ブローカーだけでなく、コンテキスト・コンシューマでさえも、コンテキスト情報を取得するためにコンテキスト・プロバイダを呼び出すことができます。コンテキスト・プロバイダは特定の呼び出しに対してコンテキスト・データのみを提供します。すべてのコンテキスト・プロバイダは、適切なアナウンスをコンテキスト・ブローカーに送信することによりその可用性と機能を登録し、コンテキスト・ブローカーおよびコンテキスト・コンシューマにコンテキスト情報を提供するためにインタフェースを公開します。
本記事では、IoT マイコンの ESP32 を使って、温湿度、気圧の情報を、コンテキスト・ブローカーやコンテキスト・コンシューマにコンテキスト情報を提供する、コンテキスト・プロバイダを紹介します。
プログラム概要
コンテキスト・プロバイダは、コンテキスト・ブローカーからコンテキスト情報取得のリクエストを受け付けるために、次のエンドポイントを提供します。
POST /v1/weatherObserved/queryContext
エンドポイントは、Arduino core for ESP32 の Webサーバのライブラリを利用して実現します。具体的には、setup() で、次のように Webサーバを作成します。 エンドポイントにアクセスがあると、queryContext() が呼び出されます。この中で、温湿度、気圧の情報をセンサーから取得し、これらをコンテキスト情報としてレスポンスします。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <WebServer.h> void setup() { Serial.begin(115200); Serial.println("Start"); setupBME280(); connectWifi(); server.on("/v1/weatherObserved/queryContext", queryContext); server.onNotFound(handleNotFound); server.begin(); } |
プログラム全体
プログラム全体は以下のような内容です。Arduino IDE から ESP32にこのプログラムを書き込んでから実行すると、ESP32はコンテキスト・プロバイダとして動作します。
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 |
#include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <Wire.h> #include <SPI.h> #include <Adafruit_Sensor.h> // Adafruit Unified Sensor #include <Adafruit_BME280.h> // Adafruit BME280 libary #include <ArduinoJson.h> // ArduinoJson 5.13.3 #define SEALEVELPRESSURE_HPA (1013.25) #define I2C_SDA 4 // BME280 SDA <---> ESP32 GPIO4 #define I2C_SCL 5 // BME280 SCL <---> ESP32 GPIO5 Adafruit_BME280 bme; // I2C char essid[] = "your essid"; char passphrase[] = "your passphrase"; const char entityType[] = "WeatherObserved"; const char entityId[] = "urn:ngsi-ld:WeatherObserved:sensor002"; WebServer server(80); void setup() { Serial.begin(115200); Serial.println("Start"); setupBME280(); connectWifi(); server.on("/v1/weatherObserved/queryContext", queryContext); server.onNotFound(handleNotFound); server.begin(); } void loop() { server.handleClient(); } void queryContext() { if (server.method() != HTTP_POST && server.args() != 1) { sendErrorMessage(400, "BadRequest", "service not found"); return; } String message = ""; for (uint8_t i=0; i<server.args(); i++){ message += server.argName(i) + ": " + server.arg(i) + "\n"; } Serial.print(message); StaticJsonBuffer<500> jsonBufferPost; JsonObject& rootPost = jsonBufferPost.parseObject(server.arg(0)); if (!rootPost.success()) { sendErrorMessage(400, "BadRequest", "post data is incorrect"); return; } JsonObject& entities = rootPost["entities"][0]; auto type = entities["type"].as<char*>(); if (type == NULL || strcmp(type, entityType) != 0) { sendErrorMessage(404, "Not found", "entity type"); return; } auto id = entities["id"].as<char*>(); if (id == NULL || strcmp(id, entityId) != 0) { sendErrorMessage(404, "Not found", "entity id"); return; } const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(3) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(2) + 3*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(4) + 50; StaticJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); JsonArray& contextResponses = root.createNestedArray("contextResponses"); JsonObject& contextResponses_0 = contextResponses.createNestedObject(); JsonObject& contextResponses_0_contextElement = contextResponses_0.createNestedObject("contextElement"); JsonArray& contextResponses_0_contextElement_attributes = contextResponses_0_contextElement.createNestedArray("attributes"); JsonArray& attributes = rootPost["attributes"]; for (int i = 0; i < attributes.size(); i++ ) { auto attr = attributes[i].as<char*>(); if (attr == NULL || !addAttributes(contextResponses_0_contextElement_attributes, attr)) { sendErrorMessage(404, "Not found", "attribute"); return; } } contextResponses_0_contextElement["id"] = entityId; contextResponses_0_contextElement["isPattern"] = "false"; contextResponses_0_contextElement["type"] = entityType; JsonObject& statusCode = contextResponses_0.createNestedObject("statusCode"); statusCode["code"] = "200"; statusCode["reasonPhrase"] = "OK"; String postData = ""; root.printTo(postData); Serial.println(postData); server.send(200, "application/json", postData); } bool addAttributes(JsonArray& jsonArray, const char *attrName) { if (!strcmp(attrName, "temperature")) { JsonObject& attr = jsonArray.createNestedObject(); attr["name"] = "temperature"; attr["type"] = "Number"; attr["value"] = bme.readTemperature(); } else if (!strcmp(attrName, "relativeHumidity")) { JsonObject& attr = jsonArray.createNestedObject(); attr["name"] = "relativeHumidity"; attr["type"] = "Number"; attr["value"] = bme.readHumidity(); } else if (!strcmp(attrName, "atmosphericPressure")) { JsonObject& attr = jsonArray.createNestedObject(); attr["name"] = "atmosphericPressure"; attr["type"] = "Number"; attr["value"] = bme.readPressure() / 100.0F; } else { return false; } return true; } 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 setupBME280() { Wire.begin(I2C_SDA, I2C_SCL); bool status = bme.begin(); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } } |
コンテキスト・プロバイダの登録
コンテキスト・ブローカーの FIWARE Orion から、このコンテキスト・プロバイダにアクセスできるように、コンテキスト・プロバイダの登録が必要です。
POST /v2/registrations
コンテキスト・ブローカーの Orion の IP アドレスが 192.168.1.2、コンテキスト・プロバイダ の ESP32 の IP アドレスが、192.168.1.3 の場合、次のようなクエリで登録できます。
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 |
curl -iX POST \ 'http://192.168.1.2:1026/v2/registrations' \ -H 'Content-Type: application/json' \ -d '{ "description": "Weather Context Source", "dataProvided": { "entities": [ { "id": "urn:ngsi-ld:WeatherObserved:sensor002", "type": "WeatherObserved" } ], "attrs": [ "temperature", "relativeHumidity", "atmosphericPressure" ] }, "provider": { "http": { "url": "http://192.168.1.3/v1/weatherObserved" }, "legacyForwarding": true } }' |
コンテキスト情報を取得
Orion にアクセスして、コンテキスト情報を取得します。次のクエリで取得できます。
GET /v2/entities/urn:ngsi-ld:WeatherObserved:sensor002
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ curl -sS -X GET 192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor002 | jq . { "id": "urn:ngsi-ld:WeatherObserved:sensor002", "type": "WeatherObserved", "temperature": { "type": "Number", "value": "23.74", "metadata": {} }, "relativeHumidity": { "type": "Number", "value": "26.87305", "metadata": {} }, "atmosphericPressure": { "type": "Number", "value": "1017.254", "metadata": {} } } |
シリアル・コンソールの出力
シリアル・コンソールの出力は次のとおりで、エンティティ情報取得のリクエスト毎に、Orion から コンテキスト・プロバイダへのリクエストと、コンテキスト・プロバイダから Orion へのレスポンスが表示されます。
1 2 3 4 5 6 7 8 |
Start .................... Connected IP address: 192.168.1.3 plain: {"entities":[{"type":"WeatherObserved","isPattern":"false","id":"urn:ngsi-ld:WeatherObserved:sensor002"}],"attributes":["temperature","relativeHumidity","atmosphericPressure"]} {"contextResponses":[{"contextElement":{"attributes":[{"name":"temperature","type":"Number","value":23.71},{"name":"relativeHumidity","type":"Number","value":26.97559},{"name":"atmosphericPressure","type":"Number","value":1017.233}],"id":"urn:ngsi-ld:WeatherObserved:sensor002","isPattern":"false","type":"WeatherObserved"},"statusCode":{"code":"200","reasonPhrase":"OK"}}]} plain: {"entities":[{"type":"WeatherObserved","isPattern":"false","id":"urn:ngsi-ld:WeatherObserved:sensor002"}],"attributes":["temperature","relativeHumidity","atmosphericPressure"]} {"contextResponses":[{"contextElement":{"attributes":[{"name":"temperature","type":"Number","value":23.74},{"name":"relativeHumidity","type":"Number","value":26.87305},{"name":"atmosphericPressure","type":"Number","value":1017.254}],"id":"urn:ngsi-ld:WeatherObserved:sensor002","isPattern":"false","type":"WeatherObserved"},"statusCode":{"code":"200","reasonPhrase":"OK"}}]} |