本記事では、温度の情報を 液晶ディスプレイ (LCD) に表示する、コンテキスト・コンシューマ型の FIWARE 対応 IoT デバイス を作成します。温度は、Orion Context Broker に保持されている WeatherObserved 型エンティティの temperature 属性から取得します。その方法は、定期的に値を取得するポーリングのような同期方式ではなく、温度のコンテキスト情報の更新をサブスクライブすることで、温度に変化があった場合に通知を受ける、非同期方式です。
WeatherObserved 型のエンティティの情報を提供するデバイスは、以前の記事で作成したコンテキスト・プロデューサ型の FIWARE 対応 IoT デバイスを利用します。
Context Consumer (CC) とは
Context Consumer(CC)、つまり、コンテキストの消費者は、コンテキスト情報を利用する、コンテキスト・ベースのアプリケーションです。CC は、Context Broker (CB) にリクエストを送信し、コンテキスト情報を取得したり、特定のインタフェースを介して Context Producer (CP) を直接呼び出すことができます。CC が情報を取得するもう1つの方法は、特定の条件に一致するコンテキスト情報の更新をサブスクライブすることです。例えば、特定のエンティティのセットに関連する情報をサブスクリプションすることです。CC は、目的のためにサブスクリプションにコールバック操作を登録するので、CB は、このコールバック機能を呼び出すことによって、コンテキストに関連する更新について、CB から CC に通知します。
前提条件
次の環境が必要となります。
- FIWARE Orion が稼働する Linux サーバ環境
- Raspberry Pi (i2c, spi を有効化)
- 環境センサ Enviro for Raspberry Pi (Pimoroni)
- git, curl, jq コマンド
FIWARE Orion, WireCloud が稼働する環境の構築方法は、「WireCloud のセットアップ」の記事を参照ください。Raspberry Pi の種類は、Raspberry Pi Zero WH, 3, 4 等です。Raspberry Pi 4 を使用すると、FIWARE Orion と 環境情報を収集するプログラムを1台で稼働できます。環境センサの Enviro は、発売元の Pimoroni から購入するか、国内では秋月電子通商、スイッチサイエンス等から購入できるようです。
 
ソースコード
使用するソースコードは、Github の https://github.com/lets-fiware/lets-fiware.tutorials から入手できます。git コマンドで、リポジトリをクローンして、”raspberrypi/03.context-consumer” ディレクトリに移動してください。
git clone https://github.com/lets-fiware/lets-fiware.tutorials.git
cd ./lets-fiware.tutorials/raspberrypi/03.context-consumer
システム構成
Raspberry Pi で動作する、コンテキスト・コンシューマおよび、コンテキスト。プロデューサと、サーバで動作する Orion Context Broker です。
 
サーバ環境の作成
docker-compose ファイル
Orion, WireCloud は、Docker コンテナを利用して、サーバ上にデプロイします。クローンしたリポジトリには、x86_64 (amd64) 用 と Raspberry Pi (aarch64) 用の dokcer-compose.yml ファイルがあります。環境に応じて、以下の設定を行います。
x86_64 (amd64)
ln -s docker-compose-arm64.yml docker-compose.yml
Raspberry Pi (aarch64)
ln -s docker-compose-aarch64.yml docker-compose.yml
Docker コンテナの起動
Linux サーバで、次のコマンドを使用して Docker コンテナを起動します。これで、Orion に関連する一連のコンテナが実行されます。
docker-compose up -d
環境変数の設定
Raspberry Pi で Orion にアクセスするための環境変数を設定します。コンテナが稼働するサーバの IP アドレスを 192.168.1.1 として説明しています。環境に合わせて、値を変更してください。
export ORION_URL=http://192.168.1.1:1026
正常性の確認
Raspberry Pi から Orion のバージョンを取得するクエリを実行して、正しくデプロイされたことを確認してください。
GET /version
次のコマンドを実行して、レスポンスが返ってくることを確認してください。
./00.version.sh
| 1 2 3 4 5 6 7 8 9 10 11 12 | { "orion" : {   "version" : "2.4.0-next",   "uptime" : "0 d, 0 h, 32 m, 41 s",   "git_hash" : "4f26834ca928e468b091729d93dabd22108a2690",   "compile_time" : "Tue Mar 31 15:41:02 UTC 2020",   "compiled_by" : "root",   "compiled_in" : "d99d1dbb4d9e",   "release_date" : "Tue Mar 31 15:41:02 UTC 2020",   "doc" : "https://fiware-orion.rtfd.io/" } } | 
コンテキスト・コンシューマ型の IoT デバイスの作成
先ほど説明した、サブスクリプションとノーティフィケーション (通知)を受信の仕組みを コンテキスト・コンシューマに実装することで、デバイスは、温度に変化があったときに、温度値を含む通知を受け取り、LCD に 温度値を表示できます。
プログラム全体
Pythonで記述されたプログラムで、サブスクリプションを作成する処理と、Orion からノーティフィケーションを受け取るエンドポイントを提供する Http サーバの処理から構成されています。
| 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 | #!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import sys import signal from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.parse import parse_qs, urlparse import requests import json import ST7735 from PIL import Image, ImageDraw, ImageFont from fonts.ttf import RobotoMedium as UserFont class ContextConsumerHandler(BaseHTTPRequestHandler):   def do_POST(self):     content_length = int(self.headers['content-length'])     payload = json.loads(self.rfile.read(content_length).decode('utf-8'))     if (debug):       print(payload)     temp = 'temp:{}'.format(payload['data'][0]['temperature']['value'])     size_x, size_y = draw.textsize(temp, font)     # Calculate text position     x = (WIDTH - size_x) / 2     y = (HEIGHT / 2) - (size_y / 2)     # Draw background rectangle and write text.     draw.rectangle((0, 0, 160, 80), back_colour)     draw.text((x, y), temp, font=font, fill=text_colour)     disp.display(img)      self.send_response(200)     self.end_headers() def handler(signum, frame):   sys.exit(0) if __name__ == '__main__':   signal.signal(signal.SIGTERM, handler)   signal.signal(signal.SIGINT, handler)   fiware_service = ''   fiware_servicepath = '/device'   orion_url = os.environ.get('ORION_URL', 'http://192.168.1.1:1026')   context_consumer_url = os.environ.get('CONTEXT_CONSUMER_URL', 'http://192.168.1.2:8080/')   device_id = 'urn:ngsi-ld:WeatherObserved:' + os.environ.get('DEVCIE_ID', 'sensor001')   debug = not not os.environ.get('DEBUG_FLAG', '')   # Create LCD class instance.   disp = ST7735.ST7735(     port=0,     cs=1,     dc=9,     backlight=12,     rotation=270,     spi_speed_hz=10000000   )   # Width and height to calculate text position.   WIDTH = disp.width   HEIGHT = disp.height   # New canvas to draw on.   img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))   draw = ImageDraw.Draw(img)   # Text settings.   font_size = 25   font = ImageFont.truetype(UserFont, font_size)   text_colour = (255, 255, 255)   back_colour = (0, 170, 170)   # Initialize display.   disp.begin()   # Create Subscription   subscription = {     "description": "A subscription to get info about sensor001",     "subject": {       "entities": [         {           "id": device_id,           "type": "WeatherObserved"         }       ],       "condition": {         "attrs": [           "temperature"         ]       }     },     "notification": {       "http": {         "url": context_consumer_url       },       "attrs": [         "temperature"       ]     }   }   if (debug):     print(subscription)   headers = {     'Fiware-Service': fiware_service,     'Fiware-ServicePath': fiware_servicepath,     'Content-Type': 'application/json; charset=utf-8'   }   url = orion_url + '/v2/subscriptions'   r = requests.post(url, data=json.dumps(subscription), headers=headers)    if r.status_code == 201:     print(r.headers)   else:     print(r.status_code)     print(r.text)      sys.exit(1)   print('Start Context Consumer')   address = ('0.0.0.0', 8080)   with HTTPServer(address, ContextConsumerHandler) as server:     server.serve_forever() | 
温度情報のサブスクライブ
プログラムでは、コンテキスト・コンシューマが温度の変化の通知を受けるために、Orion にサブスクライブのリクエストを行っています。
POST /v2/subscriptions
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 | 
コンテキスト・コンシューマの実行
Docker コンテナ内での実行
Docker コンテナ・イメージの取得
コンテキスト・コンシューマ用コンテナ・イメージを Docker Hubから取得します。次のコマンドで、fisuda/context-consumer01 という名前のコンテナ・イメージを取得できます。
./pull.sh
Docker コンテナのビルド
コンテキスト・コンシューマ用コンテナ・イメージをビルドする場合、次のコマンドを実行します。fisuda/context-consumer01 という名前のコンテナ・イメージが作成されます。
./build.sh
コンソールの出力
次のコマンドでコンソールの出力を確認できます。
./logs.sh
コンテナの停止
以下のコマンドで、コンテナを停止できます。
./stop.sh
Linux OS 上で直接実行
Raspberry Pi OS 上で直接実行したい場合や Raspberry Pi Zero WH で実行する場合、次のコマンドを実行します。
sudo apt -y install python3 python3-pip zlib1g-dev libjpeg-dev
pip3 install numpy RPI.GPIO ST7735 pillow paho-mqtt requests fonts font-roboto
python3 context-consumer.py
温度を LCD に表示
コンテキスト・プロデューサの起動
WeatherObserved 型のエンティティ “urn:ngsi-ld:WeatherObserved:sensor001” の温湿度、気圧の属性値を定期更新するため、Raspberry Pi でコンテキスト・プロデューサを起動します。起動方法は、「Raspberry Pi を使った FIWARE 対応 IoT デバイス の作成 – コンテキスト・プロデューサ」の記事を参考にしてください。
コンテキスト・コンシューマの起動
Raspberry Pi でコンテキスト・コンシューマのプログラムを実行するため、Docker コンテナを起動します。コンテキスト・ブローカーの Orion の IP アドレスが 192.168.1.1、コンテキスト・コンシューマのコンテナが稼働している Raspberry Pi の IP アドレスが、192.168.1.2 の場合、次の環境変数を設定します。
export ORION_URL=http://192.168.1.1:1026
export CONTEXT_PROVIDER_URL=http://192.168.1.2:8080
そして、run.sh コマンドでコンテナを起動します。
./run.sh
コンソール出力の確認
起動直後のコンソール出力
./log.h でコンソール出力を確認すると、次のような結果が表示されます。サブスクリプションを作成して、ノーティフィケーションを受け取る Context Consumer が起動したことが確認できます。また、Location 中の “5ed090e0b10dc3f8b36b3c98” が作成したサブスクリプションの ID です。サブスクリプションの情報取得や削除時に使用します。
| 1 2 3 | {'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.11.210:8080'}, 'attrs': ['temperature']}} {'Connection': 'Keep-Alive', 'Content-Length': '0', 'Location': '/v2/subscriptions/5ed090e0b10dc3f8b36b3c98', 'Fiware-Correlator': 'b5ec1ce4-a165-11ea-b93a-0242ac140004', 'Date': 'Fri, 29 May 2020 04:34:40 GMT'} Start Context Consumer | 
Orion からのノーティフィケーション
Orion からのノーティフィケーションを受けると、次のような情報が出力されます。これでノーティフィケーションを正常に受け取っていることを確認できます。
| 1 2 3 4 5 6 | {'subscriptionId': '5ed090e0b10dc3f8b36b3c98', 'data': [{'id': 'urn:ngsi-ld:WeatherObserved:sensor001', 'type': 'WeatherObserved', 'temperature': {'type': 'Number', 'value': '36.93', 'metadata': {}}}]} 192.168.1.1 - - [29/May/2020 00:01:01] "POST / HTTP/1.1" 200 - {'subscriptionId': '5ed090e0b10dc3f8b36b3c98', 'data': [{'id': 'urn:ngsi-ld:WeatherObserved:sensor001', 'type': 'WeatherObserved', 'temperature': {'type': 'Number', 'value': '36.94', 'metadata': {}}}]} 192.168.1.1 - - [29/May/2020 00:01:02] "POST / HTTP/1.1" 200 - {'subscriptionId': '5ed090e0b10dc3f8b36b3c98', 'data': [{'id': 'urn:ngsi-ld:WeatherObserved:sensor001', 'type': 'WeatherObserved', 'temperature': {'type': 'Number', 'value': '36.96', 'metadata': {}}}]} 192.168.1.1 - - [29/May/2020 00:01:03] "POST / HTTP/1.1" 200 - | 
LCD の表示
LCD には、次のように温度が表示されます。
 
環境設定のクリーンアップ
サブスクリプションの一覧
Orion に対して、次のリクエストを実行すると、サブスクリプションの一覧が取得できます。サブスクリプションの実行状況も確認できます。
GET /v2/subscriptions
01.list-subscriptions.sh
| 1 2 3 | #!/bin/bash : ${ORION_URL:?Not found} curl -sS -H "Fiware-ServicePath: /device" "$ORION_URL/v2/subscriptions" | jq . | 
実行結果
実行すると、次のようなレスポンスが返り、温度のサブスクリプションを確認できます。
| 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 | [   {     "id": "5ed090e0b10dc3f8b36b3c98",     "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": 2601,       "lastNotification": "2020-05-29T00:39:12.00Z",       "attrs": [         "temperature"       ],       "onlyChangedAttrs": false,       "attrsFormat": "normalized",       "http": {         "url": "http://192.168.1.2:8080"       },       "lastFailure": "2020-05-29T00:34:40.00Z",       "lastFailureReason": "Couldn't connect to server",       "lastSuccess": "2020-05-29T00:39:12.00Z",       "lastSuccessCode": 200     }   } ] | 
サブスクリプションの削除
サブスクリプションの一覧で確認した、サブスクリプション ID を指定して、サブスクリプションを削除することができます。
DELETE /v2/subscriptions/{id}
上記の場合、サブスクリプション ID は、”5ed090e0b10dc3f8b36b3c98″ なので、この値を指定すると削除できます。サブスクリプションを削除すると、Orion からコンテキスト・コンシューマへの通知は行われず、LCD の温度値も更新されなくなります。
02.delete-subscription.sh
export SUB_ID=5ed090e0b10dc3f8b36b3c98
./02.delete-subscription.sh
| 1 2 3 4 | #!/bin/bash : ${ORION_URL:?Not found} : ${SUB_ID:?Not found} curl -isS -H "Fiware-ServicePath: /device" -X DELETE "$ORION_URL/v2/subscriptions/$SUB_ID" | 
結果
“204 No Content” のステータスが返却されると、サブスクリプションを正常に削除できています。
| 1 2 3 4 5 | HTTP/1.1 204 No Content Connection: Keep-Alive Content-Length: 0 Fiware-Correlator: 5e84884c-a16f-11ea-b45f-0242ac140004 Date: Fri, 29 May 2020 00:40:00 GMT | 
コンテキスト・コンシューマの停止
次のコマンドを実行することで、コンテキスト・コンシューマのコンテナを停止できます。コンテキスト・プロデューサも同じコマンドで停止できます。
./stop.sh


 
											 
							
							
							
															 
							
							
							
															 
										
					


