Context Producer (CP)
コンテキスト・プロデューサは、コンテキスト情報を生成することができるアクターです。基本的なコンテキスト・プロデューサは、内部ロジックに従って1つ以上のコンテキスト属性に関するコンテキスト情報を自発的に更新します。コンテキスト・プロデューサ (CP) からコンテキスト・ブローカー (CB) への通信はプッシュ・モードです。
本記事では、IoT マイコンの ESP32 を使って、温度、湿度、気圧の情報を定期的に、FIWARE Orion にプッシュし、コンテキスト情報を更新するコンテキスト・プロデューサを紹介します。
BME280 のセットアップ
ESP32 に Bosch Sensortec GmbH 製の BME280 というセンサを接続し、温度、湿度、気圧の3つの環境情報を測定します。その情報を WiFi ネットワークを使って、FIWARE Orion にプッシュします。
ESP32 と BME280 の接続
BME280 のセンサ・モジュールに秋月電子通商の AE-BME280 を使用し、以下のように接続します。また、AE-BME280 の 通信方式を I2C に設定するため、3つジャンバー (J1, J2, J3) をすべてはんだジャンパーします。
BME280 | 接続 |
VDD | 3.3V に接続 |
GND | GND に接続 |
CSB | プルアップ (I2C モード, J3) |
SDA | ESP32 の GPIO4 に接続し, プルアップ(J1) |
SDO | プルアップ (I2C のアドレスが 0x77 になる) |
SCL | ESP32 の GPIO5 に接続し, プルアップ(J2) |
BME280 ソフトウェア・ライブラリ
Adafruti の BME280 ライブラリを使って、プログラムからセンサにアクセスします。Arduino IDE の ライブラリから以下のものをインストールします。
ライブラリ名 | ヘッダ・ファイル |
---|---|
Adafruit Unified Sensor | Adafruit_Sensor.h |
Adafruit BME280 libary | Adafruit_BME280.h |
プログラムの概要
プログラムのメインは、プログラムのスタート時に1回だけ実行される、setup() と、setup() の処理の後に、繰り返し実行される、loop() です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void setup() { Serial.begin(115200); Serial.println("Start"); connectWifi(); configTime(0, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); // UTC+0 setupBME280(); deleteEntity(orion, entityId); createEntity(orion, entityType, entityId); } void loop() { delay(5000); updateAttrs(orion, entityId); } |
setup()
setup() では各種初期化処理を行います。connectWifi() で WiFi ネットワークへの接続、configTime() で時刻を UTC に設定、setupBME280() で BME280 センサを初期化します。次に、deleteEntity() で作成するエンティティがすでに存在する場合は削除してから、createEntity() でエンティティを作成します。
loop()
一定間隔で、updateAttrs() を実行して、エンティティの属性を更新します。
WeatherObserved
BME280 から取得した温湿度と気圧の情報に、情報の取得時刻と位置情報を合わせて、 FIWARE データモデルの WeatherObserved の形式で、FIWARE 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 |
{ "type": "WeatherObserved", "id": "urn:ngsi-ld:WeatherObserved:sensor001", "dateObserved": { "type": "DateTime", "value": "2018-11-18T06:30:15Z" }, "temperature": { "type": "Number", "value": 24.83 }, "relativeHumidity": { "type": "Number", "value": 36.2002 }, "atmosphericPressure": { "type": "Number", "value": 1006.644 }, "location": { "type": "geo:json", "value": { "type": "Point", "coordinates": [ 139.7671, 35.68117 ] } } } |
deleteEntity()
作成するエンティティがすでに存在する場合、エンティティを削除します。エンティティの存在確認は以下で行います。
GET /v2/entities/urn:ngsi-ld:WeatherObserved:sensor001
HTTP のレスポンス・コードが 200 の場合、エンティティが存在するため、以下を実行して、エンティティを削除します。
DELETE /v2/entities/urn:ngsi-ld:WeatherObserved:sensor001
createEntity()
新たにエンティティを作成します。以下の POST を実行することで、エンティティの作成できますが、作成するエンティティの情報は、JSON 形式で POST の データとして送信する必要があります。
POST /v2/entities
ESP32 に JSON 形式のデータを作成するために、ArduinoJSON というライブラリを使用します。ライブラリはArduino IDE のライブラリ管理からインストールできます。ArduinoJSON を使用して、WeatherObserved の JSON 形式のデータ、以下のようにして作成することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <ArduinoJson.h> // ArduinoJson 5.13.3 const size_t bufferSize = JSON_ARRAY_SIZE(2) + 6*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(7) + 50; StaticJsonBuffer <bufferSize> jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["type"] = type; root["id"] = id; // dateObserved char buf[30]; JsonObject& dateObserved = root.createNestedObject("dateObserved"); dateObserved["type"] = "DateTime"; dateObserved["value"] = getDateTime(buf); // temperature JsonObject& temperature = root.createNestedObject("temperature"); temperature["type"] = "Number"; temperature["value"] = bme.readTemperature(); |
updateAttrs()
updateAttrs() では、以下を実行して、温湿度と気圧の情報を更新します。
PATCH /v2/entities/urn:ngsi-ld:WeatherObserved:sensor001/attrs
更新に使用するデータは以下のような内容です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
{ "dateObserved": { "type": "DateTime", "value": "2018-11-18T06:58:40Z" }, "temperature": { "type": "Number", "value": 24.96 }, "relativeHumidity": { "type": "Number", "value": 37.08203 }, "atmosphericPressure": { "type": "Number", "value": 1005.309 } } |
プログラム全体
プログラム全体は以下のような内容です。
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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
#include <WiFi.h> #include <HTTPClient.h> #include <time.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 #define JST (3600*9) const char essid[] = "your essid"; const char passphrase[] = "your passphrase"; char orion[] = "http://192.168.1.2:1026"; const char entityType[] = "WeatherObserved"; const char entityId[] = "urn:ngsi-ld:WeatherObserved:sensor001"; const float longitude = 139.767052; const float latitude = 35.681167; void setup() { Serial.begin(115200); Serial.println("Start"); connectWifi(); configTime(JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp"); setupBME280(); deleteEntity(orion, entityId); createEntity(orion, entityType, entityId); } void loop() { delay(5000); updateAttrs(orion, entityId); } 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); } } void deleteEntity(const char orion[], const char id[]) { HTTPClient http; int httpCode; char url[128]; sprintf(url, "%s/v2/entities/%s", orion, id); Serial.printf("GET %s\n", url); http.begin(url); httpCode = http.GET(); if (httpCode > 0) Serial.printf("httpCode: %d\n", httpCode); else Serial.printf("GET failed, error: %s\n", http.errorToString(httpCode).c_str()); if (httpCode == 200) { http.end(); http.begin(url); Serial.printf("DELETE %s\n", url); httpCode = http.sendRequest("DELETE"); if (httpCode > 0) Serial.printf("httpCode: %d\n", httpCode); else Serial.printf("DELETE failed, error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); } void createEntity(const char orion[], const char type[], const char id[]) { HTTPClient http; char url[128]; sprintf(url, "%s/v2/entities", orion); http.begin(url); http.addHeader("Content-Type", "application/json"); const size_t bufferSize = JSON_ARRAY_SIZE(2) + 6*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(7) + 50; StaticJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); root["type"] = type; root["id"] = id; // dateObserved char buf[30]; JsonObject& dateObserved = root.createNestedObject("dateObserved"); dateObserved["type"] = "DateTime"; dateObserved["value"] = getDateTime(buf); addAttribute(root); // location JsonObject& location = root.createNestedObject("location"); location["type"] = "geo:json"; JsonObject& location_value = location.createNestedObject("value"); location_value["type"] = "Point"; JsonArray& location_value_coordinates = location_value.createNestedArray("coordinates"); location_value_coordinates.add(longitude); location_value_coordinates.add(latitude); String postData = ""; root.printTo(postData); 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 updateAttrs(const char orion[], const char id[]) { HTTPClient http; char url[128]; sprintf(url, "%s/v2/entities/%s/attrs", orion, id); http.begin(url); http.addHeader("Content-Type", "application/json"); const size_t bufferSize = 4*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + 50; StaticJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); // dateObserved char buf[30]; JsonObject& dateObserved = root.createNestedObject("dateObserved"); dateObserved["type"] = "DateTime"; dateObserved["value"] = getDateTime(buf); addAttribute(root); String postData = ""; root.printTo(postData); Serial.printf("PATCH %s\n", url); Serial.println(postData); int httpCode = http.sendRequest("PATCH", postData); if(httpCode > 0) Serial.printf("httpCode: %d\n", httpCode); else Serial.printf("PATCH failed, error: %s\n", http.errorToString(httpCode).c_str()); http.end(); } void addAttribute(JsonObject& root) { // temperature JsonObject& temperature = root.createNestedObject("temperature"); temperature["type"] = "Number"; temperature["value"] = bme.readTemperature(); // relativeHumidity JsonObject& relativeHumidity = root.createNestedObject("relativeHumidity"); relativeHumidity["type"] = "Number"; relativeHumidity["value"] = bme.readHumidity(); // atmosphericPressure JsonObject& atmosphericPressure = root.createNestedObject("atmosphericPressure"); atmosphericPressure["type"] = "Number"; atmosphericPressure["value"] = bme.readPressure() / 100.0F; } char* getDateTime(char* buf) { time_t t = time(NULL); struct tm *tm = localtime(&t); sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d+0900", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return buf; } |
シリアル・コンソールの出力
プログラムを実行すると、シリアル・コンソールに以下のようなメッセージが出力され動作状況を確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Start ...................... Connected IP address : 192.168.1.3 GET http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001 httpCode: 200 DELETE http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001 httpCode: 204 POST http://192.168.1.2:1026/v2/entities {"type":"WeatherObserved","id":"urn:ngsi-ld:WeatherObserved:sensor001","dateObserved":{"type":"DateTime","value":"2018-11-18T07:07:00Z"},"temperature":{"type":"Number","value":25.23},"relativeHumidity":{"type":"Number","value":36.25391},"atmosphericPressure":{"type":"Number","value":1005.849},"location":{"type":"geo:json","value":{"type":"Point","coordinates":[139.7671,35.68117]}}} httpCode: 201 PATCH http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001/attrs {"dateObserved":{"type":"DateTime","value":"2018-11-18T07:07:07Z"},"temperature":{"type":"Number","value":25.18},"relativeHumidity":{"type":"Number","value":36.33398},"atmosphericPressure":{"type":"Number","value":1005.825}} httpCode: 204 PATCH http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001/attrs {"dateObserved":{"type":"DateTime","value":"2018-11-18T07:07:15Z"},"temperature":{"type":"Number","value":25.24},"relativeHumidity":{"type":"Number","value":36.34863},"atmosphericPressure":{"type":"Number","value":1005.898}} httpCode: 204 PATCH http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001/attrs {"dateObserved":{"type":"DateTime","value":"2018-11-18T07:07:23Z"},"temperature":{"type":"Number","value":25.2},"relativeHumidity":{"type":"Number","value":36.63965},"atmosphericPressure":{"type":"Number","value":1005.851}} httpCode: 204 |
Orion からエンティティを取得
Orion から、urn:ngsi-ld:WeatherObserved:sensor001 のコンテキスト情報を取得して、情報が更新されていることを確認できます。
GET /v2/entities/urn:ngsi-ld:WeatherObserved:sensor001
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 |
$ curl -sS http://192.168.1.2:1026/v2/entities/urn:ngsi-ld:WeatherObserved:sensor001 | jq . { "id": "urn:ngsi-ld:WeatherObserved:sensor001", "type": "WeatherObserved", "atmosphericPressure": { "type": "Number", "value": 1016.989, "metadata": {} }, "dateObserved": { "type": "DateTime", "value": "2018-11-18T07:08:31.00Z", "metadata": {} }, "location": { "type": "geo:json", "value": { "type": "Point", "coordinates": [ 139.7671, 35.68117 ] }, "metadata": {} }, "relativeHumidity": { "type": "Number", "value": 33.04883, "metadata": {} }, "temperature": { "type": "Number", "value": 25.25, "metadata": {} } } |