EdgeX 徹底ガイド

# 第1章： EdgeX とは

## 1.1. EdgeX の概要
 EdgeX は、オープンソースのエッジコンピューティングプラットフォームであり、IoT システムを構築するために必要な機能を提供します。EdgeX は、IoT デバイスからのデータ収集、処理、そして IoT システム全体の管理を行うことができます。また、様々な IoT デバイスの接続や制御が容易に行えるように設計されています。これにより、異なるプロトコルや通信方式を持つデバイスを簡単に接続し、一元的な管理が可能となります。

<left><img width="250px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/d344378c-e573-01c2-8948-60d838892a1c.png">

## 1.2. EdgeX の特徴
### 1.2.1. デバイス管理
 EdgeX は、様々な IoT デバイスの接続や制御が容易に行えるように設計されています。EdgeX は、異なるプロトコルや通信方式を持つデバイスを簡単に接続し、一元的な管理が可能となります。EdgeX は、様々な IoT デバイスに対応するための柔軟なデバイスアダプターを提供します。これにより、異なる IoT デバイスに対して一元的なデバイス管理を行うことができます。

### 1.2.2. データ管理
 EdgeX は、IoT デバイスから収集したデータの処理や蓄積を行い、必要に応じてクラウド上に送信することができます。データのリアルタイム処理を可能にすることで、より迅速な判断や対応ができるようになっています、また、データの整形や加工を自由自在に行うことができます。EdgeX は、データのフィルタリング、集約、変換などの機能を提供します。

### 1.2.3. セキュリティ管理
 EdgeX は、セキュリティ面にも重点が置かれており、デバイスの認証や暗号化通信、アクセス制御などを提供しています。これにより、IoT システムの安全性を確保することができます。EdgeX は、IoT デバイスと IoT システム間で、暗号化通信を行ったり、IoT デバイスへのアクセス制御を行うことができます。

## 1.3. EdgeX の利用シーン
### 1.3.1. 工場自動化
 EdgeX は、工場自動化のような産業分野においても利用されます。工場内の機器や設備を監視し、リアルタイムでデータを収集することができます。収集したデータを分析することで、設備のメンテナンスや製品の品質管理などを効率化することができます。また、EdgeX は、生産ラインの柔軟性を高めるための機能も提供しています。これにより、新しい製品の生産を迅速に対応することができます。

### 1.3.2. スマートホーム
 EdgeX は、スマートホーム分野でも利用されます。スマートホームでは、家電や照明、セキュリティシステムなどが互いに連携し、快適で安全な生活を提供します。EdgeX は、スマートホームに必要な機能を提供し、異なるデバイスの連携や制御を簡単に行うことができます。また、データのリアルタイム処理を行うことにより、快適な環境を実現することができます。

### 1.3.3. 都市インフラ管理
 EdgeX は、交通システムや公共施設、防災システムといった都市インフラ管理においても利用されます。EdgeX は、これらのシステムに必要な機能を提供し、リアルタイムでデータを収集することができます。収集したデータを分析することで、都市の効率性や安全性を向上させることができます。また、EdgeX は、異なるシステム間の連携を容易にすることができます。これにより、都市のインフラシステム全体を一元的に管理することができます。

# 第2章： EdgeX の基本思想とアーキテクチャ

## 2.1. EdgeX の基本思想

- EdgeX は、ハードウェアやOS、ディストリビューション、デプロイメント、プロトコル、センサーに関して、**プラットフォームに依存しない**こと。
- EdgeX は**非常に柔軟**であること。
    - プラットフォームのいかなる部分においても、他のマイクロサービスまたはソフトウェアコンポーネントによってアップグレード、リプレース、または増強される可能性がある。
    - デバイスの性能やユースケースに応じて、サービスをスケールアップ/ダウンできるようにする。
- EdgeX は「リファレンス実装」サービスを提供すべきだが、**ベストオブブリードによるソリューションを奨励**する。
- EdgeX は、**ストア&フォワード**機能を提供する必要があります（直接ネットワーク接続できない遠隔のエッジシステムをサポートするため）。
- EdgeX は、次のような課題に対処するために、**エッジ寄りの「インテリジェンス」**をサポートし、促進する必要がある。
    - アクチュエーションレイテンシーの懸念
    - 帯域幅とストレージの懸念
    - リモートで操作する懸念
- EdgeX は、**デバイス/センサーの現地デプロイメントをサポート**する必要がある。
- EdgeX は、**安全で簡単に管理できる**こと

## 2.2. EdgeX のアーキテクチャ
 EdgeX のアーキテクチャは、デバイスサービス、コアサービス、サポートサービス、アプリケーションサービス、セキュリティ、管理の6つの主要な要素で構成されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/e191698b-41fd-e33c-b6b6-97257da093cb.png)

### 2.2.1. デバイスサービス <South サイド>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/6d227ff5-f217-9efa-7cd1-5c6909dacb9f.png" width="500px"/>

デバイスサービスは、「モノ」つまりはセンサーやデバイスを、EdgeX の他の部分に接続します。

デバイスサービスは、警報システム、家庭やオフィスビルの冷暖房システム、照明、産業機械、灌漑システム、ドローン、自動化された鉄道システム、自動化された工場などの「モノ」と対話するエッジコネクタです。

デバイスサービスが管理するデバイスとしては、物理デバイス以外にも、別のゲートウェイ（およびそのゲートウェイ配下のすべてのデバイス）、デバイスマネージャ、EdgeX のデバイスまたはデバイスのコレクションとして動作するデバイスアグリゲータといったものも含まれます。

デバイスサービスは、Modbus、BACnet、MQTTなどのプロトコルを介して、デバイスやセンサー、アクチュエーター、その他の IoT デバイスと通信を行います。デバイスサービスは、IoT デバイスが生成および通信するデータを共通の EdgeX のデータ構造に変換し、その変換データをコアサービスや、EdgeX の他のマイクロサービスに送ります。

### 2.2.2. コアサービス
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/c6961977-e572-8a99-e335-8009a850d048.png" width="500px"/>

コアサービスは、EdgeX の機能の「中核」となるサービスです。

コアサービスは、以下のマイクロサービスにより構成されています：

- **Core data**： South サイドのオブジェクトから収集したデータの永続化リポジトリと関連する管理サービス。
- **Command**： North サイドから South サイドへの制御リクエストを容易にするサービス。
- **Metadata**： EdgeX に接続されているオブジェクトに関するメタデータのリポジトリと関連する管理サービス。メタデータは、新しいデバイスをプロビジョニングし、所有しているデバイスサービスとペアリングする機能を提供する。
- **Registory & Configuration**： 他の EdgeX マイクロサービスに、EdgeX 内の関連サービスに関する情報とマイクロサービス構成プロパティ（初期化値のリポジトリなど）を提供する。

### 2.2.3. サポートサービス
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/eb220e24-62de-cdb5-9c67-4de051837f13.png" width="500px"/>

サポートサービスは、エッジ分析（ローカル分析とも呼ばれる）を含む幅広いマイクロサービスを包含しています。
スケジューラやデータのクリーンアップ（ EdgeX ではスクラビングとも呼ばれる）といったアプリケーションの処理は、サポートサービスによって実行されます。

サポートサービスには以下のものがあります：

- **Rules Engine**： エッジ分析サービスで、EdgeX インスタンスが収集したセンサーデータを基に、if 条件処理を実行する。このサービスは、ユースケースに合わせた分析機能に置き換えたり、補強したりすることができる。
- **Scheduler**： EdgeX の内部タイマーで、EdgeX サービスのオペレーションを実行ことができます。設定した時刻になると、REST 経由で EdgeX サービスの API URL を呼び出して、処理を開始します。一例として、Scheduler サービスは、定期的に Core Data API を呼び出し、その後、EdgeX から正常にエクスポートされた古いセンシング　Event　をクリーンアップしたりします。
- **Alerts and Notification**： EdgeX のサービスのアラートや通知を、他のシステムや EdgeX インスタンスを監視する人に対して送信するための機能を提供します。

### 2.2.4. アプリケーションサービス <North サイド>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/907a4ceb-8dfb-1734-c883-16f93f892112.png" width="500px"/>

アプリケーションサービスは、EdgeX からセンシングしたデータを抽出、処理/変換して、任意のエンドポイントやプロセスに送信します。EdgeX では、主要なクラウドプロバイダー（Amazon IoT Hub、Google IoT Core、Azure IoT Hub、IBM Watson IoT など）やMQTTトピック、HTTP RESTエンドポイントにデータを送信するアプリケーションサービスなどを提供しています。

アプリケーションサービスは、「ファンクション・パイプライン」という考え方に基づきます。ファンクション・パイプラインとは、メッセージ（この場合は EdgeX の Event メッセージ）を指定された順序で処理するファンクションの集合体です。

1. パイプラインの最初のファンクションは、トリガーです。トリガーは、ファンクション・パイプラインの実行を開始します。トリガーは、例えば、メッセージキューに受信するメッセージのようなものです。
1. その後、各機能がメッセージに反応します。一般的な機能としては、フィルタリング、変換（XML や JSON への変換など）、圧縮、暗号化機能などがあります。
1. メッセージがすべてのファンクションを通過し、シンクに設定されると、ファンクション・パイプラインは終了します。シンクの一例としては、「結果のメッセージをMQTTトピックに入れ、Azure や AWS に送信する」等があります。

### 2.2.5. セキュリティ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/f3988b0b-f2df-10ba-55a0-4630d172d641.png" width="300px"/>

セキュリティコンポーネントは、EdgeX によって管理されるデバイスやセンサー、その他の IoT デバイスのデータや制御を保護します。

セキュリティコンポーネントとしては、主に以下の2つがあります。

- **Secret Store**: 他のサービスで使用される DB の接続パスワードや、クラウドに接続するためのトークンといった EdgeX の機密を保持するための安全な保存場所として使用されるセキュリティストアです。
- **Reverse Proxy**: リバースプロキシ機能であり、EdgeX REST リソースへのアクセスを制限し、アクセス制御関連の処理を実行します。

### 2.2.6. マネジメント
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/041ecf2d-9676-c337-fe63-dbd4dee1b0a8.png" width="300px"/>

マネジメントコンポーネントは、外部システムが、EdgeX のサービスを開始/停止/再起動を行なったり、サービス状態を取得したり、サービスに関するメトリック (メモリ使用量など) を取得したりするための接続先として機能します。

# 第3章： EdgeX の仕組み
## 3.1. センサーデータ収集
EdgeX の主な処理内容としては、センサーやデバイスからデータを収集し、そのデータを North サイドのアプリケーションやシステムで利用できるようにすることです。
データは、デバイスサービスがプロトコルを介して各デバイスから収集し、データを EdgeX の Event オブジェクトに変換します。

デバイスサービスは、次のいずれかを実行します：

1. Event　オブジェクトをメッセージバス （Redis Streams または MQTT） に配置します。メッセージバス上の　Event　メッセージのサブスクライバは、Application Service、Core Data、またはその両方です（以下の Step 1.1 を参照）。
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/99fa1286-ac8e-77b4-ff83-ddca18a6d686.png" width="350px"/>
  
2. Event　オブジェクトを REST 通信で Core Data サービスに送信します （Step 1.2 を参照）。
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/a45e6ecd-dc04-cf2f-f7e2-77a2067b794a.png" width="350px"/>
  
Core Data が　（メッセージバスまたは REST 経由で）　Event　を受信すると、センサーデータをローカルのエッジデータベースに永続化します。EdgeX では、永続化ストアとして　Redis　を使用しています。他のデータベースも使用できるよう抽象化されています。永続化は必須ではなく、無効にすることもできます。
エッジ側でデータを永続化する理由としては、主に以下の2つがあります。

- エッジノードは常に接続されているわけではありません。接続されていない間、センサーデータは、接続が回復したタイミングでNorth-boundで送信できるように保存しておく必要があります。これは、ストア＆フォワード機能と呼ばれています。
- 場合によっては、センサーデータの解析では、トレンドを理解し、履歴に基づいて正しい判断を下すために、履歴を遡る必要があります。
  例えば、センサーが「今、摂氏22度」と報告した場合、冷暖房システムの調整を決定する前に、10分前の気温を知りたいと思うかもしれません。10分前の気温が摂氏30度だった場合、10分前に行った室温を下げる調整で部屋を冷やすのに十分だったと判断することができます。

Core Data は、デバイスサービスから REST 経由で　Event　オブジェクトを受信すると、センサーデータの　Event　をアプリケーションサービス宛のメッセージトピックに載せます。メッセージングインフラストラクチャとして、デフォルトで Redis Pub/Sub が使用されています （Step 2）。Core Data とアプリケーションサービス間のメッセージング基盤として、MQTT や ZMQ を使用することも可能です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/0951f5d6-8e86-7268-a69f-4ac5a91ee66c.png" width="350px"/>

アプリケーションサービスは、必要に応じてデータを変換し、エンドポイントにデータをプッシュします。また、エンドポイントに送信する前に、Event　に対してフィルタリング、エンリッチ、圧縮、暗号化、その他の機能を実行することができます （Step 3）。エンドポイントは、HTTP/S エンドポイント、MQTT トピック、クラウドシステム（クラウドトピック）などが考えられます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/23a9e4e0-8872-93bb-d074-8d92ada17e78.png" width="350px"/>

## 3.2. エッジ分析と動作
エッジコンピューティングにおいて、単にセンサーデータを収集することは、EdgeX のようなエッジプラットフォームとしての役割の一部でしかありません。
エッジプラットフォームには、以下のような重要な役割もあります。

- 受信したセンサーデータをエッジ分析する
    - エッジ分析とは、エッジで収集されたセンサーデータの評価を行い、その結果に基づいてアクションを起こす処理のことで、ローカル分析とも呼ばれる。

ローカル分析が重要な理由としては、以下の2つが挙げられます。

- 一部の意思決定では、センサーが収集したデータが企業やクラウドシステムにフィードバックされ、応答が返されるのを待つ余裕がありません。
- エッジシステムの中には、常に企業やクラウドに接続されているわけではなく、断続的に接続されるものが存在します。

ローカル分析により、システムはある程度の期間、独立して動作することができます。

EdgeX は、エッジから収集したデータに対してローカルで動作するように構築されています。つまり、Event　はローカル分析で処理され、センサー/デバイスのアクションをトリガーするために使用することができます。

アプリケーションサービスが North サイドのクラウドシステムやアプリケーションで消費するデータを準備するように、アプリケーションサービスは EdgeX のEvent　（およびそれが含むセンサーデータ）を処理し、任意の分析パッケージに取得できます （Step 4を参照）。デフォルトでは、EdgeX にはシンプルな Rules Engine が搭載されています（EdgeX のデフォルトの Rules Engine は、オープンソースの Rules Engine である eKuiper で、現在は LF Edge の姉妹プロジェクトになっています）。独自の分析パッケージ （または ML エージェント） は、ローカルの Rules Engine を置き換えたり補強したりすることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/6900b6b6-b8ed-ecd5-7fad-92e4864ce14d.png" width="450px"/>

分析パッケージは、センサーの Event データを探索し、デバイスの作動をトリガーする決定を下すことができます。例えば、エンジンの圧力測定値が 60 PSI より大きいかどうかをチェックすることができる。このようなルールが真であると判断された場合、分析パッケージはコアコマンドサービスを呼び出して、何らかの制御可能なデバイスの「バルブを開く」などのアクションをトリガーします （Step 5を参照）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/3c457097-4e53-bedc-268d-ec2e40450c71.png" width="300px"/>

コアサービスの Command は、作動要求を取得し、その要求に応じてどのデバイスに作動する必要があるかを決定し、所有するデバイスサービスを呼び出して作動を実行します （Step 6 を参照）。コア・コマンドは、開発者が作動前に追加のセキュリティ対策やチェックを行うことを可能にします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/509e2362-d799-65fc-feee-4191fa7cf9a8.png" width="400px"/>

デバイスサービスは、作動の要求を受け取り、それをプロトコル固有の要求に変換し、その要求を目的のデバイスに転送する （Step 7 を参照）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/70b2f731-e965-a70f-afca-104a25c54bf1.png" width="350px"/>

# 第4章： EdgeX のセキュリティ

## 4.1. EdgeX のセキュリティの課題
### 4.1.1. データプライバシーの保護
EdgeX におけるデータの取り扱いに関しては、データプライバシー保護が最も重要な課題の一つです。EdgeX が扱うデータには、顧客情報やビジネスデータ、機密情報などが含まれることがあります。これらのデータが不正な手に渡ってしまうと、企業やユーザーにとって大きな損害につながります。そのため、EdgeX におけるデータプライバシー保護の実現は、重要な要素となります。

### 4.1.2. デバイスセキュリティの確保
EdgeX は、IoT デバイスやセンサーといった様々なデバイスと通信するため、これらのデバイスのセキュリティも重要な課題の一つです。IoT デバイスは、多くの場合、ネットワークに直接接続され、脆弱性があります。これらのデバイスが攻撃を受けた場合、悪意のあるユーザーによって EdgeX にアクセスされる可能性があります。そのため、デバイスセキュリティの確保は、EdgeX セキュリティの重要な要素の一つです。

## 4.2. EdgeX のセキュリティ対策
### 4.2.1. データの暗号化
EdgeX においては、データの暗号化がセキュリティ対策の一つとして考えられます。データの暗号化を行うことで、不正アクセスによるデータの漏洩や改ざんを防ぐことができます。EdgeX では、TLS プロトコルを使用して、エンドツーエンドの暗号化通信を実現することができます。また、データストレージやデータ転送においても、暗号化技術を利用することができます。

### 4.2.2. 認証・認可機能の実装
認証とは、ユーザーがシステムにアクセスするために、自分自身を識別するプロセスです。認証が成功した場合、ユーザーはシステムにアクセスできます。一方、認可は、認証済みのユーザーに対して、どのような操作が許可されるかを決定するプロセスです。EdgeX では、ユーザーの認証と認可を実現するために、認証と認可の機能が用意されています。

認証機能は、ユーザーがシステムにアクセスする前に、ユーザーが登録されているかどうかを確認します。EdgeX では、OAuth2 認証がサポートされています。OAuth2 認証は、他のアプリケーションからのアクセスを認証するためのプロトコルです。OAuth2 は、様々なアプリケーションで使用されているため、広く採用されています。

認可機能は、認証済みのユーザーに対して、どのような操作が許可されるかを決定します。EdgeX では、ロールベースのアクセス制御 （RBAC） がサポートされています。RBAC は、ユーザーに対して役割を割り当て、役割に基づいてユーザーが実行できる操作を決定することで、アクセス制御を実現します。

## 4.3. EdgeX のセキュリティベストプラクティス
### 4.3.1. 最小限の特権原則
EdgeX を実行するデバイスやシステムには、最小限の特権原則を適用することが推奨されます。最小限の特権原則は、ユーザーに必要な最低限のアクセス許可しか与えず、セキュリティを高めるために使用されます。

### 4.3.2. 常に最新のセキュリティアップデートを適用する
セキュリティアップデートは、既知の脆弱性や脅威に対処するためにリリースされます。したがって、EdgeX を使用する場合は、最新のセキュリティアップデートを適用することが重要です。これは、システムが可能な限りセキュアであることを確認するために必要な措置の一つです。

アップデートを行う際には、すべての依存関係と互換性があることを確認する必要があります。アップデートの実行前には、開発者やシステム管理者が必要な作業を適切に実施し、アップデートの影響を事前に評価しておく必要があります。

### 4.3.3. セキュリティポリシーを明確にする
EdgeX を使用する際には、セキュリティポリシーを明確にすることが重要です。セキュリティポリシーには、以下の項目が含まれます。

- パスワードポリシー：パスワードの強度要件やパスワードの期限について定める必要があります。
- アカウント管理ポリシー：アカウントの作成、削除、変更について定める必要があります。
- ログポリシー：ログの取得、保存、保護について定める必要があります。
- アクセス制御ポリシー：システムへのアクセスについて定める必要があります。

これらのポリシーを適用することで、EdgeX のセキュリティを確保し、データやシステムを保護することができます。また、開発者やシステム管理者がセキュリティポリシーを理解し、実行することも重要です。

# 第5章： EdgeX のモニタリングとデバッグ

## 5.1. EdgeX のモニタリング手法
### 5.1.1. ログ監視
EdgeX では、ログを監視することで、アプリケーションの実行中に発生するエラーや問題を特定することができます。ログ監視は、アプリケーションの稼働状況を把握する上で重要な手段の一つです。

EdgeX のログは、様々な場所に保存されます。例えば、アプリケーションの標準出力や標準エラー出力、システムログ、またはアプリケーション自身が指定したファイルなどです。ログの内容は、アプリケーションの状態や実行中の処理の情報、エラーメッセージ、スタックトレースなどが含まれます。

ログ監視のためには、ログを収集するためのツールやサービスを利用することができます。例えば、ELK スタック（Elasticsearch、Logstash、Kibana）や Fluentd などのログ収集・分析ツールが利用されます。これらのツールを使用することで、ログをリアルタイムで監視し、問題が発生した場合には迅速に対応することができます。

### 5.1.2. メトリクス監視
EdgeX では、メトリクスを監視することで、アプリケーションのパフォーマンスや利用状況を把握することができます。メトリクスは、CPU 使用率やメモリ使用量、データの処理速度、デバイスの接続状況など、さまざまな項目に関する数値データです。

メトリクス監視には、Prometheus などのメトリクス収集・監視ツールを使用することができます。これらのツールを使用することで、アプリケーションの稼働状況をリアルタイムで監視し、問題が発生した場合には迅速に対応することができます。また、メトリクスデータを可視化することで、アプリケーションのパフォーマンスや利用状況を詳細に分析することができます。

## 5.2. EdgeX のデバッグ手法
### 5.2.1. ログデバッグ
ログデバッグは、アプリケーションが生成するログを使用して問題を特定するプロセスです。EdgeX は、ログ出力のために標準の Java ロギング API （SLF4J） を使用します。ログレベルを変更することにより、ログの詳細度を調整できます。EdgeX のログは、主に3つのカテゴリに分けることができます。

- フレームワークログ
- アプリケーションログ
- デバイスサービスログ

フレームワークログには、EdgeX の内部動作に関する情報が含まれます。アプリケーションログには、開発者がアプリケーションに追加したログ情報が含まれます。デバイスサービスログには、特定のデバイスサービスに関する情報が含まれます。

ログデバッグには、ログレベルを適切に設定し、ログ出力を解析することが含まれます。ログレベルには、TRACE、DEBUG、INFO、WARN、ERROR、FATAL の6つのレベルがあります。

ログ出力の解析には、ログファイルを読み取ることが必要です。EdgeX は、標準出力とログファイルの2種類のログ出力をサポートしています。ログファイルの場所は、EdgeX の設定ファイルで指定できます。

### 5.2.2. トレースデバッグ
トレースデバッグは、アプリケーションの特定の動作に対して詳細な情報を提供するデバッグ方法です。トレースデバッグを使用すると、アプリケーションの動作についてより深く理解し、特定の問題を解決するための洞察力を得ることができます。

EdgeX のトレースデバッグ機能は、アプリケーションで発生した Event の詳細なログを出力することで、アプリケーションの動作を追跡します。これにより、アプリケーション内で何が起こっているのかを把握し、問題の特定や解決に役立ちます。

トレースデバッグの実装方法は、アプリケーション内で特定のポイントにログメッセージを挿入することです。ログメッセージには、その時点でのアプリケーションの状態や処理内容などが含まれます。これらのログメッセージは、アプリケーションの動作を把握するために使用できます。

EdgeX は、アプリケーション内の任意の場所にログメッセージを挿入できるようになっています。また、ログメッセージを出力する方法も柔軟で、コンソールやファイルに出力することができます。ログメッセージは、アプリケーションのデバッグに必要な情報を提供するために、必要に応じて詳細に設定できます。

トレースデバッグを使用することで、アプリケーションの問題を特定することができます。しかし、トレースデバッグはアプリケーションに追加の負荷を与えるため、常に有効にすることは推奨されません。問題が発生した場合に限り、トレースデバッグを有効にして使用することをお勧めします。

## 5.3. EdgeX のトラブルシューティング
### 5.3.1. データフローのトラブルシューティング
データフローの問題をトラブルシューティングするためには、EdgeX のログを調査することが重要です。EdgeX は、実行時に詳細なログ情報を提供し、この情報を利用することで、データフロー内の問題を特定することができます。特に、データフロー内のコンポーネントの出力と入力のログ情報を調べることで、問題を特定できます。問題が特定できたら、その原因を修正する必要があります。修正ができない場合は、コンポーネントを変更する必要があります。

### 5.3.2. デバイス接続のトラブルシューティング
デバイス接続の問題をトラブルシューティングするためには、デバイス自体が正常に動作しているかどうかを確認する必要があります。デバイスが正常に動作している場合、次に接続に問題があるかどうかを確認します。これを確認するために、デバイスの接続設定を調べます。また、接続に問題がある場合は、ネットワークや接続プロトコルの問題があるかどうかを調べる必要があります。ネットワークや接続プロトコルに問題がある場合は、それらを修正する必要があります。

デバイス接続の問題を特定するためには、EdgeX のログを調査することが重要です。デバイス接続のログ情報を調べることで、接続に関する詳細な情報を入手することができます。これにより、接続の問題を特定することができます。特に、エラーメッセージを調べることで、問題の原因を特定できます。問題が特定できたら、その原因を修正する必要があります。修正ができない場合は、別のデバイスを使用する必要があります。

# 第6章： EdgeX の拡張性とカスタマイズ

## 6.1. EdgeX の拡張性
EdgeX は、多様なデバイスとデータソースからデータを収集し、処理することができる柔軟性と拡張性に優れた IoT エッジコンピューティングプラットフォームです。EdgeX は、新しいデバイスやデータフローの追加を容易にする豊富なAPIとプラグインアーキテクチャを提供しています。

### 6.1.1. 新しいデバイスの追加
EdgeX には、新しいデバイスを追加するための豊富な API とプラグインアーキテクチャが備わっています。EdgeX は、多くのプロトコルに対応しており、新しいプロトコルのサポートを追加することも可能です。また、新しいデバイスドライバを開発することにより、新しいデバイスのサポートを追加することもできます。

### 6.1.2. 新しいデータフローの追加
EdgeX には、新しいデータフローを追加するための豊富な API とプラグインアーキテクチャが備わっています。EdgeX は、データストリームの処理や分析のために豊富なプラグインを提供しています。これにより、新しいデータソースのサポートを追加することができます。

## 6.2. EdgeX のカスタマイズ
EdgeX は、カスタマイズ可能な柔軟なアーキテクチャを持っています。EdgeX のアーキテクチャは、マイクロサービスとして構築されており、サービスのカスタマイズや新しいマイクロサービスの追加が可能です。

### 6.2.1. サービスのカスタマイズ
EdgeX は、様々なサービスを提供することができます。これらのサービスは、モジュール化されており、個々に変更することができます。これにより、ユーザーは独自のサービスを作成することができます。

サービスのカスタマイズには、サービスコードの変更が必要です。サービスコードは、Go、Java、C++ などのプログラミング言語で書かれていることが一般的です。サービスをカスタマイズする場合は、コードの変更が必要であるため、プログラミングスキルが必要となります。

また、サービスのカスタマイズを行う前に、EdgeX のアーキテクチャに精通している必要があります。特に、どのサービスを変更する必要があるか、および変更が他のサービスに与える影響を理解する必要があります。

サービスのカスタマイズには、一般的に、次の手順が含まれます。

1. EdgeX のサービスアーキテクチャについて理解する
1. カスタマイズするサービスを選択する
1. サービスコードを変更する
1. サービスを再ビルドする
1. 変更内容をテストする

サービスをカスタマイズする際には、互換性にも注意する必要があります。新しいサービスが既存のサービスと互換性がない場合、EdgeX の全体的なパフォーマンスに影響を与える可能性があります。

### 6.2.2. マイクロサービスの追加
EdgeX は、アプリケーションで必要な機能を提供するマイクロサービスの組み合わせで構成されます。EdgeX アプリケーションを構築する際、既存のマイクロサービスを利用することもできますが、必要に応じて新しいマイクロサービスを追加することもできます。

新しいマイクロサービスを追加する場合は、いくつかのポイントに留意する必要があります。まず、そのマイクロサービスがアプリケーション全体に影響を与えることがないように設計する必要があります。また、必要な場合は、マイクロサービスの相互接続を考慮する必要があります。これには、サービス間通信のプロトコル、API、およびメッセージングシステムが含まれます。

新しいマイクロサービスを追加するには、Go 言語で書かれたエッジサービスを実装する必要があります。これにより、EdgeX の各種プラグインと通信するための必要な API が提供されます。マイクロサービスは、Docker コンテナとしてビルドし、EdgeX アプリケーション内にデプロイされます。

新しいマイクロサービスを追加する場合、EdgeX コミュニティによって提供されるドキュメントを参照することをお勧めします。これにより、開発プロセスがスムーズに進むだけでなく、既存のコードを活用することができます。また、ドキュメントを参照することにより、新しいマイクロサービスが EdgeX の設計原則に準拠していることを確認することもできます。

## 6.3. EdgeX の拡張性とカスタマイズの注意点
EdgeX をカスタマイズしたり、拡張したりする場合には、以下の注意点があります。

### 6.3.1. 互換性の維持
EdgeX の拡張やカスタマイズを行う場合、新たに追加されたコンポーネントやサービスが、既存のシステムとの互換性を保てることが重要です。

新しいコンポーネントやサービスが既存のシステムに対して互換性がない場合、問題が発生する可能性があります。たとえば、アップグレード後に古いコンポーネントが正常に動作しなくなったり、システム全体で予期せぬ問題が発生したりすることがあります。

そのため、拡張やカスタマイズを行う場合には、既存のシステムとの互換性を確認することが必要です。また、拡張やカスタマイズ後にも、引き続き互換性を確認することが重要です。

### 6.3.2. セキュリティの確保
EdgeX をカスタマイズする場合には、セキュリティに対する十分な配慮が必要です。

カスタムコードやサードパーティ製品を追加することによって、システムに新たな脆弱性が導入される可能性があります。このため、セキュリティに対する注意を払い、新しいコンポーネントやサービスを安全に追加する必要があります。

また、カスタマイズ後も定期的なセキュリティ監査やアップデートが必要となります。セキュリティに対する配慮を怠ることは、重大なセキュリティ上の問題を引き起こす可能性があるため、注意が必要です。

# 第7章： EdgeX のインストールと設定

## 7.1. EdgeX のインストール
### 7.1.1. Docker Composeでのインストール
Docker Compose を使用すれば、複数の Docker コンテナを一気に構築することができます。

#### 7.1.1.1. Docker Compose ファイルの取得
edge-compose リポジトリにアクセスし、使用するブランチを選択します。
各ブランチ毎にシステムアーキテクチャや構成要素毎に、複数の Docker Compose ファイルが用意されているので、用途に合わせたファイルを取得します。
一例として levski ブランチの Docker Compose ファイルの一覧を以下に示します。

- `docker-compose.yml`: セキュリティサービスを含む。
- `docker-compose-arm64.yml`: x86用で、セキュリティサービスを含む。
- `docker-compose-with-app-sample.yml`: セキュリティサービスおよびサンプルアプリケーションを含む。
- `docker-compose-with-app-sample-arm64.yml`: arm64用で、セキュリティサービスおよびサンプルアプリケーションを含む。
- `docker-compose-no-secty.yml`: セキュリティサービスを含まない。
- `docker-compose-no-secty-arm64.yml`: arm64用で、セキュリティサービスを含まない。
- `docker-compose-no-secty-with-app-sample.yml`: app-sampleで、セキュリティサービスを含まない。
- `docker-compose-no-secty-with-app-sample-arm64.yml`: arm64用で、サンプルアプリケーションを含み、セキュリティサービスは含まない。
- `docker-compose-portainer.yml`: Portainer UI拡張機能の定義 (x86またはARMで使用)

今回は、`wget`を使用して、levski ブランチの Docker Compose を取得します。

```bash
$ wget https://raw.githubusercontent.com/edgexfoundry/edgex-compose/levski/docker-compose-no-secty-with-app-sample-arm64.yml -O docker-compose.yml
```

今回使用する `docker-compose-no-secty-with-app-sample-arm64.yml` の場合、中身は以下のようになっています。

**ネットワーク設定:**
ブリッジを使用して EdgeX のネットワークを構築します。
```yaml
networks:
  edgex-network:
  driver: bridge
```
**サービス設定:**
各サービスごとに詳細設定を行なっています。
（以下は、簡略的に第2階層まで表記）
```yaml
services:
 app-service-rules:
 app-service-sample:
 command:
 consul:
 data:
 database:
 device-rest:
 device-virtual:
 metadata:
 notifications:
 rulesengine:
 scheduler:
 system:
 ui:
```

**バージョン情報:**
EdgeX のバージョン番号を指定しています。
```yaml
version: '3.7'
```
**ボリューム設定:**
EdgeX で使用するボリュームを定義しています。
```yaml
volumes:
 consul-config: {}
 consul-data: {}
 db-data: {}
 kuiper-data: {}
```

#### 7.1.1.2. Docker Compose によるコンテナの起動
次に、Docker Compose でコンテナを立ち上げるには、以下のコマンドを使用します。

```bash
$ docker-compose up -d
Creating edgex-ui-go         ... done
Creating edgex-core-consul ... done
Creating edgex-redis    ... done
Creating edgex-kuiper        ... done
Creating edgex-support-scheduler   ... done
Creating edgex-support-notifications ... done
Creating edgex-core-metadata     ... done
Creating edgex-core-command     ... done
Creating edgex-core-data       ... done
Creating edgex-app-sample      ... done
Creating edgex-device-rest      ... done
Creating edgex-app-rules-engine   ... done
Creating edgex-device-virtual    ... done
Creating edgex-sys-mgmt-agent    ... done
```

これにより、EdgeX が Docker コンテナ内で実行されるようになります。

#### 7.1.1.3. EdgeX の状態確認
##### Docker コンテナ
EdgeX の Docker コンテナの状態が全て `Up` となっていることを確認します。
```
$ docker ps
CONTAINER ID   IMAGE                                               COMMAND                  CREATED        STATUS        PORTS                                                                        NAMES
503a20b1768a   edgexfoundry/sys-mgmt-agent-arm64:2.3.0             "/sys-mgmt-agent -cp…"   35 hours ago   Up 35 hours   127.0.0.1:58890->58890/tcp                                                   edgex-sys-mgmt-agent
5f9feb037e35   edgexfoundry/app-service-configurable-arm64:2.3.0   "/app-service-config…"   35 hours ago   Up 35 hours   48095/tcp, 127.0.0.1:59701->59701/tcp                                        edgex-app-rules-engine
6e75ba8a971c   edgexfoundry/device-virtual-arm64:2.3.0             "/device-virtual --c…"   35 hours ago   Up 35 hours   127.0.0.1:59900->59900/tcp                                                   edgex-device-virtual
b94313306a97   edgexfoundry/device-rest-arm64:2.3.0                "/device-rest --cp=c…"   35 hours ago   Up 35 hours   127.0.0.1:59986->59986/tcp                                                   edgex-device-rest
f42c49d293a3   edgexfoundry/app-service-configurable-arm64:2.3.0   "/app-service-config…"   35 hours ago   Up 8 hours    48095/tcp, 127.0.0.1:59700->59700/tcp                                        edgex-app-sample
52f826ebfe54   edgexfoundry/core-data-arm64:2.3.0                  "/core-data -cp=cons…"   35 hours ago   Up 35 hours   127.0.0.1:5563->5563/tcp, 127.0.0.1:59880->59880/tcp                         edgex-core-data
f165239a0e0e   edgexfoundry/core-command-arm64:2.3.0               "/core-command -cp=c…"   35 hours ago   Up 35 hours   127.0.0.1:59882->59882/tcp                                                   edgex-core-command
92c421463b40   edgexfoundry/core-metadata-arm64:2.3.0              "/core-metadata -cp=…"   35 hours ago   Up 35 hours   127.0.0.1:59881->59881/tcp                                                   edgex-core-metadata
b28f5095c4f5   lfedge/ekuiper:1.7.1-alpine                         "/usr/bin/docker-ent…"   35 hours ago   Up 35 hours   9081/tcp, 20498/tcp, 127.0.0.1:59720->59720/tcp                              edgex-kuiper
0d9a78aded7d   edgexfoundry/support-scheduler-arm64:2.3.0          "/support-scheduler …"   35 hours ago   Up 35 hours   127.0.0.1:59861->59861/tcp                                                   edgex-support-scheduler
08d5ad8fc327   edgexfoundry/support-notifications-arm64:2.3.0      "/support-notificati…"   35 hours ago   Up 35 hours   127.0.0.1:59860->59860/tcp                                                   edgex-support-notifications
3c424ffb15d5   consul:1.13.2                                       "docker-entrypoint.s…"   35 hours ago   Up 35 hours   8300-8302/tcp, 8301-8302/udp, 8600/tcp, 8600/udp, 127.0.0.1:8500->8500/tcp   edgex-core-consul
889aab20b583   edgexfoundry/edgex-ui-arm64:2.3.0                   "./edgex-ui-server -…"   35 hours ago   Up 35 hours   0.0.0.0:4000->4000/tcp                                                       edgex-ui-go
9188ec53b282   redis:7.0.5-alpine                                  "docker-entrypoint.s…"   35 hours ago   Up 35 hours   127.0.0.1:6379->6379/tcp                                                     edgex-redis
```

##### Web UI
Web ブラウザから `http://localhost:4000/` にアクセスすると、EdgeX の UI (edgex-ui-go) を確認することができます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/f9471dbb-f693-fa3e-830c-a78e202f839c.png"/>

また、Webブラウザから `http://localhost:8500/` にアクセスすると、EdgeX の UI (edgex-core-consul) を確認することができます。
![スクリーンショット 2023-04-04 7.40.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/2eaa9be3-22cd-ceb0-3e37-7ad5ab01edd8.png)

## 7.2. EdgeX の設定
### 7.2.1. 接続済みデバイス
EdgeX は、テストと開発に役立つ仮想デバイスサービスを提供します。これは、多数のデバイスをシミュレートし、それぞれがさまざまなタイプのデータを構成可能なパラメーター内でランダムに生成します。
たとえば、Random-Integer-Device はランダムな整数を生成します。
Random-Integer-Device で送信された Event レコードを Core Data サービスに照会することで、仮想デバイスの値が送信されていることを確認することができます。

```bash
$　curl http://localhost:59880/api/v2/event/device/name/Random-Integer-Device | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "totalCount": 11936,
  "events": [
    {
      "apiVersion": "v2",
      "id": "82f05165-39d8-4b17-9795-247b0be9c885",
      "deviceName": "Random-Integer-Device",
      "profileName": "Random-Integer-Device",
      "sourceName": "Int8",
      "origin": 1680687809391670000,
      "readings": [
        {
          "id": "61d35516-eb21-4181-b2ea-5b82e42f43a9",
          "origin": 1680687809391670000,
          "deviceName": "Random-Integer-Device",
          "resourceName": "Int8",
          "profileName": "Random-Integer-Device",
          "valueType": "Int8",
          "value": "75"
        }
      ]
    },
    ### 省略
    {
      "apiVersion": "v2",
      "id": "18d104b4-417a-4018-8fd2-de2c5aad6208",
      "deviceName": "Random-Integer-Device",
      "profileName": "Random-Integer-Device",
      "sourceName": "Int16",
      "origin": 1680687749447560200,
      "readings": [
        {
          "id": "f02badc6-3e8a-4800-8433-982e3fb2330a",
          "origin": 1680687749447560200,
          "deviceName": "Random-Integer-Device",
          "resourceName": "Int16",
          "profileName": "Random-Integer-Device",
          "valueType": "Int16",
          "value": "13716"
        }
      ]
    }
  ]
}
```

:::note info
デフォルトでは、返される Event の最大数は`20`となっています。取得する Event 数を変更するには、`limit`パラメータを指定します。
```bash
$　curl http://localhost:59880/api/v2/event/device/name/Random-Integer-Device?limit=50
```
:::

### 7.2.2. デバイスの制御
デバイスからのデータの読み取りは、EdgeX の機能の一部に過ぎません。他には、デバイスの制御にも使用することができます。
デバイスが EdgeX サービスに登録されると、そのデバイスから利用可能なデータの読み取りと、それを制御するコマンドの両方を記述するDevice プロファイルが提供されます。

仮想デバイスサービスがデバイスを登録した際に Random-Integer-Device およびプロファイルを使用して、ランダムな整数を生成するのではなく、設定した値を常に返すようにサービスに指示できるようにするコマンドを定義していきます。

デバイスでコマンドを直接呼び出すのではなく、Command サービスを使用して呼び出すようにします。
まずはじめに、デバイスについて Command サービスに問い合わせを行い、呼び出し可能なコマンドを確認します。

```bash
$　curl http://localhost:59882/api/v2/device/name/Random-Integer-Device | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "deviceCoreCommand": {
    "deviceName": "Random-Integer-Device",
    "profileName": "Random-Integer-Device",
    "coreCommands": [
      {
        "name": "Int64Array",
        "get": true,
        "set": true,
        "path": "/api/v2/device/name/Random-Integer-Device/Int64Array",
        "url": "http://edgex-core-command:59882",
        "parameters": [
          {
            "resourceName": "Int64Array",
            "valueType": "Int64Array"
          }
        ]
      },
      ### 省略
      {
        "name": "Int64",
        "get": true,
        "set": true,
        "path": "/api/v2/device/name/Random-Integer-Device/Int64",
        "url": "http://edgex-core-command:59882",
        "parameters": [
          {
            "resourceName": "Int64",
            "valueType": "Int64"
          }
        ]
      }
    ]
  }
}
```

今回は `Int` (現在の整数16値を取得するコマンド) と `WriteInt Value` (ランダムな整数16値の生成を無効にし、返す整数値を指定するコマンド) を使用します。

```bash
{
  "apiVersion": "v2",
  "statusCode": 200,
  "deviceCoreCommand": {
    "deviceName": "Random-Integer-Device",
    "profileName": "Random-Integer-Device",
    "coreCommands": [
      ### 省略
      {
        "name": "WriteInt16Value",
        "set": true,
        "path": "/api/v2/device/name/Random-Integer-Device/WriteInt16Value",
        "url": "http://edgex-core-command:59882",
        "parameters": [
          {
            "resourceName": "Int16",
            "valueType": "Int16"
          },
          {
            "resourceName": "EnableRandomization_Int16",
            "valueType": "Bool"
          }
        ]
      },
      ### 省略
      {
        "name": "Int16",
        "get": true,
        "set": true,
        "path": "/api/v2/device/name/Random-Integer-Device/Int16",
        "url": "http://edgex-core-command:59882",
        "parameters": [
          {
            "resourceName": "Int16",
            "valueType": "Int16"
          }
        ]
      },
      ### 省略
    ]
  }
}
```

これらのコマンドには、`get` または `set` (または両方) のオプションがあることに気付かと思います。
`get` 呼び出しは乱数 (整数 16) を返し、EdgeX の他の機能 (具体的には Core Data) にデータを送信するために自動的に呼び出されます。URL を使用して手動で `get` を呼び出すこともできます。

```bash
$　curl http://localhost:59882/api/v2/device/name/Random-Integer-Device/Int16 | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "event": {
    "apiVersion": "v2",
    "id": "e793f65b-69d1-473d-bdd6-169b83138f52",
    "deviceName": "Random-Integer-Device",
    "profileName": "Random-Integer-Device",
    "sourceName": "Int16",
    "origin": 1680695925781846000,
    "readings": [
      {
        "id": "d9f6825e-a276-4560-90ce-2088b1422b08",
        "origin": 1680695925781846000,
        "deviceName": "Random-Integer-Device",
        "resourceName": "Int16",
        "profileName": "Random-Integer-Device",
        "valueType": "Int16",
        "value": "25401"
      }
    ]
  }
}
```

コマンド サービスを介して `Int16` デバイスの Random-Integer-Device 操作の `GET` を呼び出すと、デバイスによって生成される次のランダム値が JSON 形式で表示されます。

この読み取り値のデフォルトの範囲は -32,768 ～ 32,767 です。 上記の例では、読み取り値として -8146 が返されました。 サービスがランダムに値を返すように設定されている場合、返される値は `Int16` コマンドが送信されるたびに異なります。 

ただし、`WriteInt16Value` コマンドを使用してランダムな値が返されないようにし、代わりに返す値を指定することができます。 
以下の curl コマンドを使用して set コマンドを呼び出し、ランダムな値を無効にして毎回値 42 を返すようにします。

```bash
$　curl -X PUT -d '{"Int16":"42", "EnableRandomization_Int16":"false"}' http://localhost:59882/api/v2/device/name/Random-Integer-Device/WriteInt16Value
{
  "apiVersion": "v2",
  "statusCode": 200
}
```

成功した場合、`statusCode` で 200 が返されます。

これで、何度 `Int16` コマンドで `get` を呼び出しても、返される値は毎回 42 になります。

```bash
$ curl http://localhost:59882/api/v2/device/name/Random-Integer-Device/Int16 | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "event": {
    "apiVersion": "v2",
    "id": "aaf5c719-cc5d-40f3-a845-d05906bc64e9",
    "deviceName": "Random-Integer-Device",
    "profileName": "Random-Integer-Device",
    "sourceName": "Int16",
    "origin": 1680696674524262100,
    "readings": [
      {
        "id": "f51854a8-67dc-40b7-a08f-abf7d4fa0a36",
        "origin": 1680696674524262100,
        "deviceName": "Random-Integer-Device",
        "resourceName": "Int16",
        "profileName": "Random-Integer-Device",
        "valueType": "Int16",
        "value": "42"
      }
    ]
  }
}
```

### 7.2.3. データのエクスポート
EdgeX は、さまざまなクラウド サービスとアプリケーションのエクスポーター (アプリケーションサービスと呼ばれる) を提供します。

これをわかりやすくするために、コミュニティが提供する「アプリケーションサービス」を使用して、EdgeX データを HiveMQ が所有するパブリック MQTT ブローカーに送信した後、HiveMQ が提供する MQTT ブラウザー・クライアントを介して EdgeX の Event データを監視します。

まずはじめに、アプリケーションサービスを `docker-compose.yml` ファイルの `app-service-rules` サービスの直後に追加します。

```yaml
  app-service-mqtt:
    container_name: edgex-app-mqtt
    depends_on:
    - consul
    - data
    environment:
      CLIENTS_CORE_COMMAND_HOST: edgex-core-command
      CLIENTS_CORE_DATA_HOST: edgex-core-data
      CLIENTS_CORE_METADATA_HOST: edgex-core-metadata
      CLIENTS_SUPPORT_NOTIFICATIONS_HOST: edgex-support-notifications
      CLIENTS_SUPPORT_SCHEDULER_HOST: edgex-support-scheduler
      DATABASES_PRIMARY_HOST: edgex-redis
      EDGEX_PROFILE: mqtt-export
      EDGEX_SECURITY_SECRET_STORE: "false"
      MESSAGEQUEUE_HOST: edgex-redis
      REGISTRY_HOST: edgex-core-consul
      SERVICE_HOST: edgex-app-mqtt
      TRIGGER_EDGEXMESSAGEBUS_PUBLISHHOST_HOST: edgex-redis
      TRIGGER_EDGEXMESSAGEBUS_SUBSCRIBEHOST_HOST: edgex-redis
      WRITABLE_PIPELINE_FUNCTIONS_MQTTEXPORT_PARAMETERS_BROKERADDRESS: tcp://broker.mqttdashboard.com:1883
      WRITABLE_PIPELINE_FUNCTIONS_MQTTEXPORT_PARAMETERS_TOPIC: EdgeXEvents
    hostname: edgex-app-mqtt
    image: edgexfoundry/app-service-configurable:2.0.0
    networks:
      edgex-network: {}
    ports:
    - 127.0.0.1:59702:59702/tcp
    read_only: true
    security_opt:
    - no-new-privileges:true
    user: 2002:2001
```

Compose ファイルを保存してから、`compose up` コマンドを実行して、Docker Compose を使って、アプリケーションサービスを pull して開始します。

```bash
$　docker-compose up -d
Pulling app-service-mqtt (edgexfoundry/app-service-configurable:2.0.0)...
2.0.0: Pulling from edgexfoundry/app-service-configurable
339de151aab4: Pull complete
e7b6b57a2b6a: Pull complete
6229971c1f98: Pull complete
616af87017ee: Pull complete
91248f4e9565: Pull complete
8bbed88a8e5a: Pull complete
Digest: sha256:42a63a8dd43fc0eb776bb5d40a813cbba6b56db0d32b0535ac6c36bb0e1c34f7
Status: Downloaded newer image for edgexfoundry/app-service-configurable:2.0.0
edgex-core-consul is up-to-date
edgex-redis is up-to-date
edgex-ui-go is up-to-date
edgex-kuiper is up-to-date
edgex-support-scheduler is up-to-date
edgex-support-notifications is up-to-date
edgex-core-metadata is up-to-date
edgex-core-command is up-to-date
edgex-core-data is up-to-date
edgex-app-sample is up-to-date
edgex-app-rules-engine is up-to-date
edgex-sys-mgmt-agent is up-to-date
edgex-device-virtual is up-to-date
edgex-device-rest is up-to-date
Creating edgex-app-mqtt ... done
```

任意の MQTT クライアントでこのブローカーに接続して、送信されたデータを監視できます。

HiveMQ は、[Web ベースのクライアント](http://www.hivemq.com/demos/websocket-client/)を提供しています。
Web ベースのクライアントページに移動し、接続情報を入力後、[接続] ボタンを押下して、HiveMQ パブリック・ブローカーに接続します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/5d4429ef-4357-668f-5be7-25f440b78f5e.png" width="800px"/>

HiveMQ のクライアントツールを使用して、アプリケーションサービスが EdgeX データを送信しているのと同じパブリックの HiveMQ ブローカーに接続します。

次に、サブスクリプション領域を使用して、`EdgeXEvents` トピックをサブスクライブします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/ca01c3a1-984c-a0ec-a98b-f84520819d5a.png" width="800px"/>

サブスクライブすると、ブラウザ画面のメッセージ領域に EdgeX の Event データが表示され始めます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/f726a8e2-f830-f34c-545a-f24bccf45efe.png" width="800px"/>

# 第8章: チュートリアル
## 8.1. EdgeX デモ API
EdgeX のマイクロサービスの機能/動作や運用方法、各マイクロサービスが提供する主な API 呼び出しについて、より理解するために、ここでは、デモ API について説明していきます。

デバイスサービスとデバイスが EdgeX で確立される方法や、データがさまざまなサービスを介して送信される方法、データが EdgeX からクラウドまたは商用システムに送信される方法について示します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/8408f87a-96cb-9a9f-d061-51a4b64d05c9.png" width="600px"/>

このデモでは、EdgeX システムの動作を模倣する方法で手動で REST 呼び出しを行うことにより、さまざまな EdgeX マイクロサービスの役割を実行します。このデモを検討し、うまくいけば自分で API を実行すると、EdgeX の仕組みをよりよく理解できるようになるはずです。

明確に言っておきますが、このチュートリアルは、すべてのデバイスサービスやデバイスなどをセットアップする方法ではありません。
このチュートリアルでは、EdgeX API を手動で呼び出して、デバイスサービスが新しいデバイス・セットアップを取得し、EdgeX に (または EdgeX 経由で) データを送信するために行う作業を実行します。
つまり、EdgeX API を手動で実行することによって、デバイスサービスが自動的に行う作業をシミュレートしています。また、API を使用して、デバイスサービスとすべての EdgeX によって行われた処理の結果を確認します。

## 8.2. ユースケースの例
EdgeX と各サービス、および API を調査し、一般的にどのように機能するかを理解するには、実際のユースケースを使って確認するのが効果的です。

EdgeX がどのように機能するかを示すために架空の状況で API を実行しますが、この使用例では、EdgeXを使用してデバイスからデータを収集し、監視する環境の制御を作動させます。

### 8.2.1. 物体カウントカメラ
EdgeX に接続するデバイスがあり、そのデバイスはカメラで写真を撮り、その写真を解析して、人間や犬の数を報告するチップを搭載します。

カメラが撮影し、その結果を報告する頻度は、設定することができます。
実際には、カメラデバイスで以下の2つの処理コマンドを送信することができます。
- スナップショット間隔: 撮影間隔を秒単位で設定し、その結果得られた画像に写っている犬や人間の数を計算させる
- スキャン深度: カメラのスキャン深度（距離）を設定する。つまり、カメラがどれだけ遠くまで見るかを設定する。
  これは、遠くを見れば見るほど、人間や犬の数の精度が落ちるため、ユースケースのニーズに応じてユーザーに設定させることを目的としている。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/0aca2ae0-80d0-5be6-0198-74cf3cf745df.png" width="750px" />

### 8.2.2. EdgeX デバイスの表現
EdgeX ではカメラはデバイスとして認識され、デバイスはデバイスサービスによって管理されます。デバイスサービスは、そのデバイスに対応したプロトコルで、基盤となるハードウェア（今回の場合はカメラ）と通信します。デバイスサービスは、管理するデバイスからデータを収集し、そのデータを EdgeX の他のサービスに引き渡します。

今回の場合、デバイスサービスは、カメラが捉えた人間や犬の数を収集することになります。また、デバイスサービスは、EdgeX やその他のシステムからの処理要求を、物理デバイスが理解できるプロトコル要求に変換する役割も果たします。
この例では、デバイスサービスは、スナップショット間隔を設定したり、スキャン深度を設定したりする要求を受け取り、それらの要求をカメラが理解できるプロトコルコマンドに変換することになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/6f9e2c58-353d-c395-e8cd-54cca0b6e860.png" width="350px"/>

## 8.3. デバイス定義
Device プロファイルは、テンプレート、またはデバイスのタイプや分類として捉えることができます。
通常、デバイスサービスは、管理するデバイスのタイプの Device プロファイルを提供します。

### 8.3.1. Device プロファイル
この架空のデバイスサービスは、人間や犬を数えるカメラだけを管理するため、監視カメラの Device プロファイルを作成するには、一度 `POST` リクエストを行うだけで済みます。
YAML 形式の「監視カメラの Device プロファイル用のファイル」を使って `POST` リクエストを行い、カメラモニターのプロファイルを作成します。

<details>
<summary>監視カメラの Device プロファイル用のファイル (EdgeX_CameraMonitorProfile.yml)</summary>

```yaml
name: "camera-monitor-profile"
manufacturer: "IOTech"
model: "Cam12345"
labels: 
- "camera"
description: "Human and canine camera monitor profile"

deviceResources:
-
  name: "HumanCount"
  isHidden: false  #is hidden is false by default so this is just making it explicit for purpose of the walkthrough demonstration
  description: "Number of people on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "CanineCount"
  isHidden: false
  description: "Number of dogs on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "ScanDepth"
  isHidden: false
  description: "Get/set the scan depth"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"

-
  name: "SnapshotDuration"
  isHidden: false
  description: "Get the snaphot duration"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"

deviceCommands:
-
  name: "Counts"
  readWrite: "R"
  isHidden: false
  resourceOperations:
  - { deviceResource: "HumanCount" }
  - { deviceResource: "CanineCount" }
```

</details>

今回の Device プロファイルを確認すると、冒頭には一般的な情報が記載されていることが見て取れます。
```yaml
name: "camera-monitor-profile"
manufacturer: "IOTech"
model: "Cam12345"
labels: 
- "camera"
description: "Human and canine camera monitor profile"
```

各 Device プロファイルには、固有名や説明、製造者、モデル、クエリを補助するためのラベルが記載されます。

#### 8.3.1.1. リソースとコマンド
Device プロファイルには、そのプロファイルを遵守するデバイスとの通信方法を定義します。
主には、デバイスに（デバイスサービスを介して）リクエストを送信するために使用される `deviceResources` と `deviceCommands` を定義します。

##### デバイスリソースについて
Device プロファイルは、デバイスやセンサーから取得できるデータの要素や、デバイスやセンサーの設定を変更する方法を記述しています。取得できるデータや変更できる設定はリソース （厳密にはデバイスリソース） と呼ばれます。

今回のチュートリアルでは、カメラから取得するデータは犬の数と人間の数の2つとなります。したがって、この2つを Device プロファイルのデバイスリソースとして表現します。
さらに、カメラの設定として、スキャン深度とスナップショット間隔の2つを設定できるようにしたいと思います。これらも Device プロファイルのデバイスリソースとして表現されます。

```yaml
deviceResources:
-
  name: "HumanCount"
  isHidden: false  #is hidden is false by default so this is just making it explicit for purpose of the walkthrough demonstration
  description: "Number of people on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "CanineCount"
  isHidden: false
  description: "Number of dogs on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "ScanDepth"
  isHidden: false
  description: "Get/set the scan depth"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"

-
  name: "SnapshotDuration"
  isHidden: false
  description: "Get the snaphot duration"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"
```

##### デバイスコマンドについて
コマンド （厳密にはデバイスコマンド） は、同時に複数のデバイスリソースに対する読み取りと書き込みのアクセスを指定します。つまり、デバイスコマンドを使えば、センサーから一度に複数のデータを要求する （もしくは一度に複数の設定を行う） ことができます。
今回の例では、人間と犬の両方のカウントを要求することを指定する Device コマンドを設定することで、1回の要求で両方のカウントを要求することができます。

```yaml
deviceCommands:
-
  name: "Counts"
  readWrite: "R"
  isHidden: false
  resourceOperations:
  - { deviceResource: "HumanCount" }
  - { deviceResource: "CanineCount" }
```

#### 8.3.1.2. チュートリアル: Device プロファイル
Device プロファイルをアップロードする手順について説明します。

##### Device プロファイルのダウンロード
以下コピーして、Device プロファイル （`EdgeX_CameraMonitorProfile.yml`） を作成し、ローカルに保存してください。

<details>
<summary>監視カメラの Device プロファイル用のファイル (EdgeX_CameraMonitorProfile.yml)</summary>

```yaml
name: "camera-monitor-profile"
manufacturer: "IOTech"
model: "Cam12345"
labels: 
- "camera"
description: "Human and canine camera monitor profile"

deviceResources:
-
  name: "HumanCount"
  isHidden: false  #is hidden is false by default so this is just making it explicit for purpose of the walkthrough demonstration
  description: "Number of people on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "CanineCount"
  isHidden: false
  description: "Number of dogs on camera"
  properties:
    valueType:  "Int16"
    readWrite: "R"  #designates that this property can only be read and not set
    defaultValue: "0"
-
  name: "ScanDepth"
  isHidden: false
  description: "Get/set the scan depth"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"

-
  name: "SnapshotDuration"
  isHidden: false
  description: "Get the snaphot duration"
  properties:
    valueType:  "Int16"
    readWrite: "RW"  #designates that this property can be read or set
    defaultValue: "0"

deviceCommands:
-
  name: "Counts"
  readWrite: "R"
  isHidden: false
  resourceOperations:
  - { deviceResource: "HumanCount" }
  - { deviceResource: "CanineCount" }
```

</details>

##### EdgeX への Device プロファイルのアップロード
curl POST リクエストを作成します。

```bash
$　$ curl -X POST -F 'file=@./EdgeX_CameraMonitorProfile.yml' http://localhost:59881/api/v2/deviceprofile/uploadfile
```

API コールが成功すると、新たに生成された `DeviceProfile` の ID が表示されます。
```bash
{"apiVersion":"v2","statusCode":201,"id":"35577dad-fe6e-4b63-a520-4de06259d849"}
```

#### 8.3.1.3. GET API のテスト
curl などを使って `http://localhost:59881/api/v2/deviceprofile/all` を `GET` すると、EdgeX のインスタンスで現在定義されているすべての Device プロファイル （および関連するすべての `deviceResource` と `deviceCommand`） のリストを JSON 形式で取得することができます。

```bash
$ curl http://localhost:59881/api/v2/deviceprofile/all | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "totalCount": 9,
  "profiles": [
    ### 省略
    {
      "created": 1680738883230,
      "modified": 1680738883230,
      "id": "35577dad-fe6e-4b63-a520-4de06259d849",
      "name": "camera-monitor-profile",
      "manufacturer": "IOTech",
      "description": "Human and canine camera monitor profile",
      "model": "Cam12345",
      "labels": [
        "camera"
      ],
      "deviceResources": [
        {
          "description": "Number of people on camera",
          "name": "HumanCount",
          "isHidden": false,
          "tag": "",
          "properties": {
            "valueType": "Int16",
            "readWrite": "R",
            "units": "",
            "minimum": "",
            "maximum": "",
            "defaultValue": "0",
            "mask": "",
            "shift": "",
            "scale": "",
            "offset": "",
            "base": "",
            "assertion": "",
            "mediaType": ""
          },
          "attributes": null
        },
        {
          "description": "Number of dogs on camera",
          "name": "CanineCount",
          "isHidden": false,
          "tag": "",
          "properties": {
            "valueType": "Int16",
            "readWrite": "R",
            "units": "",
            "minimum": "",
            "maximum": "",
            "defaultValue": "0",
            "mask": "",
            "shift": "",
            "scale": "",
            "offset": "",
            "base": "",
            "assertion": "",
            "mediaType": ""
          },
          "attributes": null
        },
        {
          "description": "Get/set the scan depth",
          "name": "ScanDepth",
          "isHidden": false,
          "tag": "",
          "properties": {
            "valueType": "Int16",
            "readWrite": "RW",
            "units": "",
            "minimum": "",
            "maximum": "",
            "defaultValue": "0",
            "mask": "",
            "shift": "",
            "scale": "",
            "offset": "",
            "base": "",
            "assertion": "",
            "mediaType": ""
          },
          "attributes": null
        },
        {
          "description": "Get the snaphot duration",
          "name": "SnapshotDuration",
          "isHidden": false,
          "tag": "",
          "properties": {
            "valueType": "Int16",
            "readWrite": "RW",
            "units": "",
            "minimum": "",
            "maximum": "",
            "defaultValue": "0",
            "mask": "",
            "shift": "",
            "scale": "",
            "offset": "",
            "base": "",
            "assertion": "",
            "mediaType": ""
          },
          "attributes": null
        }
      ],
      "deviceCommands": [
        {
          "name": "Counts",
          "isHidden": false,
          "readWrite": "R",
          "resourceOperations": [
            {
              "deviceResource": "HumanCount",
              "defaultValue": "",
              "mappings": null
            },
            {
              "deviceResource": "CanineCount",
              "defaultValue": "",
              "mappings": null
            }
          ]
        }
      ]
    },
    ### 省略
  ]
}
```

## 8.4. デバイスサービスの登録
次に、デバイスサービスを EdgeX に登録または定義します。つまり、「デバイスが接続され、機能している」ことを EdgeX に対して宣言します。

### 8.4.1. コアサービスの Configuration/Registration への登録
デバイスサービスや EdgeX のマイクロサービスの登録処理には、コアサービスの Configuration および Registration への登録が含まれます。

### 8.4.2. デバイスサービス
この時点で、デバイスサービスはコアサービスの Metadata に自身の代表インスタンスを作成する必要があります。この登録で、デバイスサービスには、`core` コマンドや任意の EdgeX サービスが通信できるようにするためのアドレスが与えられます。

デバイスサービスの名前は、EdgeX 全体で一意である必要があります。
デバイスサービスを登録する際、初期の管理状態を指定することができます。管理状態は、人または他のシステムによるデバイスサービスの制御を行うもので、`LOCKED` または `UNLOCKED` を設定することができます。`LOCKED` に設定されている場合、コマンド要求に応答したり、デバイスからデータを送信したりすることはできません。

#### 8.4.2.1. チュートリアル: デバイスサービス
`DeviceService` を作成するには、curl で `POST` リクエストを行います。

```bash
$　curl -X 'POST' 'http://localhost:59881/api/v2/deviceservice' -d '[{"apiVersion": "v2","service": {"name": "camera-control-device-service","description": "Manage human and dog counting cameras", "adminState": "UNLOCKED", "labels": ["camera","counter"], "baseAddress": "camera-device-service:59990"}}]'
```

API コールが成功すると、新たに生成された `DeviceService` の ID が表示されます。

```
[{"apiVersion":"v2","statusCode":201,"id":"8d1aa144-f4cf-4f0f-b185-43135a80e1c2"}]
```

##### GET API のテスト
curl などを使って `http://localhost:59881/api/v2/deviceservice/all` を `GET` すると、EdgeX のインスタンスで現在定義されているすべてのデバイスサービスのリストが JSON 形式で表示されます。

```bash
$ curl http://localhost:59881/api/v2/deviceservice/all | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "totalCount": 3,
  "services": [
    {
      "created": 1680739786819,
      "modified": 1680739786819,
      "id": "8d1aa144-f4cf-4f0f-b185-43135a80e1c2",
      "name": "camera-control-device-service",
      "description": "Manage human and dog counting cameras",
      "labels": [
        "camera",
        "counter"
      ],
      "baseAddress": "camera-device-service:59990",
      "adminState": "UNLOCKED"
    },
    ### 省略
  ]
}
```

## 8.5. デバイスのプロビジョニング
セットアップの最終段階で、デバイスサービスは、"多くの場合"、EdgeX の側で管理しようとするデバイスを （静的または動的に） 検出し、プロビジョニングします。
これはあくまでも "多くの場合" であり、すべてのデバイスサービスが新しいデバイスを検出したり、すぐにプロビジョニングしたりするわけではないことに留意してください。デバイスの種類やデバイスの通信方法に応じて、デバイスをいつ/どのようにプロビジョニングするかは、デバイスサービス次第となります。場合によっては、すべてが整い、デバイスと物理的に接続するためにデバイスサービスが必要とする情報を人間が提供できるようになった時点で、人が要求することによって、デバイスサービスに対するプロビジョニングが発動されるケースもあります。

### 8.5.1. デバイス
今回のデモでは、コアサービスの Metadata を呼び出すと、デバイスサービスが人間／犬のカウントモニタカメラを検出し、開始処理の一部としてデバイスをプロビジョニングするようになります。
デバイスを作成するには、`DeviceProfile`、`DeviceService` に関連付け、デバイスと通信する方法と1つ以上のプロトコル （アドレスを提供する場合もある） を含む必要があります。

デバイスを作成する際には、管理状態と動作状態の両方を指定します。
動作状態は、EdgeX 側でデバイスの内部動作状態を示すものです。動作状態は、外部 （他のシステムや人） から設定されるのではなく、EdgeX 内部 （場合によってはデバイスサービス自体） からデバイスの状態について発信される信号です。デバイスの動作状態としては、`UP` や `DOWN`、`UNKNOWN` があります。デバイスの動作状態が `DOWN` となるケースとしては、デバイスは何らかの問題が発生しているか、通常通り機能できない処理 （アップグレードなど） を行なっている状態となります。

#### 8.5.1.1. チュートリアル: デバイス
デバイスを作成するには、curl で `POST` リクエストを送信します。

```bash
$　curl -X 'POST' 'http://localhost:59881/api/v2/device' -d '[{"apiVersion": "v2", "device": {"name": "countcamera1","description": "human and dog counting camera #1","adminState": "UNLOCKED","operatingState": "UP","labels": ["camera","counter"],"location": "{lat:45.45,long:47.80}","serviceName": "camera-control-device-service","profileName": "camera-monitor-profile","protocols": {"camera-protocol": {"camera-address": "localhost","port": "1234","unitID": "1"}},"notify": false}}]'
```

API コールが成功すると、新しく生成された `Device` の ID が表示されます。

```bash
[{"apiVersion":"v2","statusCode":201,"id":"93d205aa-40d7-4b3d-853b-2810c2a29a85"}]
```

##### GET API のテスト
コアサービスの Metadata に登録されているデバイスの中に、監視カメラが含まれていることを確認します。curl などを使って `http://localhost:59881/api/v2/device/all` を GET すると、EdgeX のインスタンスで現在定義されているすべてのデバイスのリストが JSON 形式で表示されます。

```bash
$ curl http://localhost:59881/api/v2/device/all | jq .
{
  "apiVersion": "v2",
  "statusCode": 200,
  "totalCount": 9,
  "devices": [
    ### 省略
    {
      "created": 1680741539284,
      "modified": 1680741539284,
      "id": "93d205aa-40d7-4b3d-853b-2810c2a29a85",
      "name": "countcamera1",
      "description": "human and dog counting camera #1",
      "adminState": "UNLOCKED",
      "operatingState": "UP",
      "labels": [
        "camera",
        "counter"
      ],
      "location": "{lat:45.45,long:47.80}",
      "serviceName": "camera-control-device-service",
      "profileName": "camera-monitor-profile",
      "protocols": {
        "camera-protocol": {
          "camera-address": "localhost",
          "port": "1234",
          "unitID": "1"
        }
      }
    },
    ### 省略
  ]
}
```

コアサービスの Metadata には、`DeviceProfile`、`Device`、`DeviceService` などを取得するための追加 API が多数用意されています。例えば、`DeviceProfile` を名前から検索する場合の API は以下のようになります。

```bash
$　curl -X GET http://localhost:59881/api/v2/device/profile/name/camera-monitor-profile | json_pp
{
   "apiVersion" : "v2",
   "devices" : [
      {
         "adminState" : "UNLOCKED",
         "created" : 1680741539284,
         "description" : "human and dog counting camera #1",
         "id" : "93d205aa-40d7-4b3d-853b-2810c2a29a85",
         "labels" : [
            "camera",
            "counter"
         ],
         "location" : "{lat:45.45,long:47.80}",
         "modified" : 1680741539284,
         "name" : "countcamera1",
         "operatingState" : "UP",
         "profileName" : "camera-monitor-profile",
         "protocols" : {
            "camera-protocol" : {
               "camera-address" : "localhost",
               "port" : "1234",
               "unitID" : "1"
            }
         },
         "serviceName" : "camera-control-device-service"
      }
   ],
   "statusCode" : 200,
   "totalCount" : 1
}
```

## 8.6. コマンドの呼び出し
デバイスプロファイル （今回のチュートリアルでは `camera-monitor-profile`） には、そのタイプのデバイスから情報を取得/設定するためのコマンドが多数含まれていました。また、デバイス （今回のチュートリアルでは `countcamera1`） は、デバイスがプロビジョニングされた際に Device プロファイル （`camera-monitor-profile`） に関連付けられました。

セットアップが完了したら、デバイス （`countcamera1`） に関連付けられたコマンドのリストを、コアサービスの Command サービスに問い合わせることができます。Command サービスは、共通の正規化された方法でコマンドを公開するため、次のようなデバイスとの通信を簡素化することができます。

- EdgeX 内の他のマイクロサービス（例えば、エッジ分析や Rules Engine サービスなど）
- EdgeX と同じホスト上に存在する可能性のある他のアプリケーション（例えば、センサーを停止させる管理エージェント）。
- デバイスを操作する外部システム（例えば、デバイスの設定変更を行うクラウドベースのアプリケーションなど）。

### 8.6.1. チュートリアル: コマンド
コマンドのリストを取得する手順について説明します。

以下に示すように、curl GET リクエストを作成します。

```bash
$　curl -X GET localhost:59882/api/v2/device/name/countcamera1 | json_pp
{
   "apiVersion" : "v2",
   "deviceCoreCommand" : {
      "coreCommands" : [
         {
            "get" : true,
            "name" : "Counts",
            "parameters" : [
               {
                  "resourceName" : "HumanCount",
                  "valueType" : "Int16"
               },
               {
                  "resourceName" : "CanineCount",
                  "valueType" : "Int16"
               }
            ],
            "path" : "/api/v2/device/name/countcamera1/Counts",
            "url" : "http://edgex-core-command:59882"
         },
         ### 省略
      ],
      "deviceName" : "countcamera1",
      "profileName" : "camera-monitor-profile"
   },
   "statusCode" : 200
}
```

レスポンスとして表示されている URL は、デバイス上のさまざまな取得/設定を行うことができるものです。
（今回は、Dockerを使用しているため、実際にリクエストを投げる際は URL の `edgex-core-command` を `localhost` に置き換える必要があります。）

- http://edgex-core-command:59882/api/v2/device/name/countcamera1/SnapshotDuration
- http://edgex-core-command:59882/api/v2/device/name/countcamera1/Counts
- http://edgex-core-command:59882/api/v2/device/name/countcamera1/HumanCount
- http://edgex-core-command:59882/api/v2/device/name/countcamera1/CanineCount
- http://edgex-core-command:59882/api/v2/device/name/countcamera1/ScanDepth

### 8.6.2. Event の確認
カメラデバイスのセンサーデータが、Core Data に収集されていないことを確認します。今回のデモでは、デバイスサービスとデバイスはすべて手動で動作させているため、センサーデータはまだ収集されていないかと思います。これは、Core Data の Event 数を求めることにより確認することができます。

#### 8.6.2.1. チュートリアル: Event
Event　のリストを表示するには、curl で `GET` リクエストを送信します。

```bash
$　curl -X GET localhost:59880/api/v2/event/count/device/name/countcamera1
```

Core Data にカメラの Event がないことを確認することができるかと思います。

```bash
{"apiVersion":"v2","statusCode":200,"Count":0}
```

### 8.6.3. コマンドの実行
今回のチュートリアルでは、実際のデバイスやデバイスサービスは存在していないのですが、EdgeX はそのことを認識していません。そのため、これまで行ってきた設定やセットアップで、EdgeX にスキャン深度の設定やカメラへのスナップショット間隔の設定を依頼すると、EdgeX は忠実にそのタスクを実行しようとします。しかし、デバイスサービスやデバイスが存在していないので、EdgeX としては、想定通りエラーを返します。しかし、ログファイルを見ると、マイクロサービスのコアコマンドで作られたコマンドが、架空のカメラを管理する架空のデバイスサービスの適切なコマンドを呼び出そうとしていることを確認できます。

例えば、`countcamera1` （EdgeX に登録されている1台の物体 （人間/犬） のカウントカメラのデバイス名） のスキャン深度を設定するコマンドを起動してみます。
まずはじめに、前の手順で実行した `curl -X GET localhost:59882/api/v2/device/name/countcamera1 | json_pp` の結果より、デバイスに新しいスキャン深度を設定するコマンドの URL を確認します。

```bash
         {
            "get" : true,
            "name" : "ScanDepth",
            "parameters" : [
               {
                  "resourceName" : "ScanDepth",
                  "valueType" : "Int16"
               }
            ],
            "path" : "/api/v2/device/name/countcamera1/ScanDepth",
            "set" : true,
            "url" : "http://edgex-core-command:59882"
         },
```

#### 8.6.3.1. チュートリアル: 作動コマンド
デバイスを動作させるには、curl で `PUT` リクエストを送信します。

```bash
$　curl -X PUT -d '{"depth":"9"}' localhost:59882/api/v2/device/name/countcamera1/ScanDepth
{"apiVersion":"v2","message":"failed to send a http request","statusCode":503}
```

##### Command サービス ログの確認
実際にはデバイスサービス（またはデバイス）が存在しないため、コアサービスの Command は `Failed to send a http request` エラーを出力します。しかし、以下の Docker コマンドでログ出力を確認すると、コアサービスの Command がリクエストを受信し、存在しないデバイスサービスを呼び出し、動作コマンドを発行しようとしたことを見て取れます。

```bash
$　docker logs edgex-core-command
```

ログエントリーの最後の行に表示されているメッセージでは、存在しないデバイスにアクセスしようとしたことを表しています。

```bash
level=ERROR ts=2023-04-06T01:35:48.1613299Z app=core-command source=http.go:47 X-Correlation-ID=bd02e4aa-507e-4549-810d-f1961726b367 msg="failed to send a http request -> Put \"camera-device-service:59990\": unsupported protocol scheme \"camera-device-service\""
```

## 8.7. Event の送信とデータの読み取り
実際には、物体 (人間/犬) カウントカメラが写真を撮影した後、カウントし、そのデータを EdgeX に送信することになります。
このセクションでは、この動作をシミュレートするために、カメラのデバイスとデバイスサービスになったつもりで、Core Data の API コールを行います。つまり、人間や犬の数を event/reading オブジェクトの形で Core Data に送信します。

### 8.7.1. Event/Reading の送信
データは、Event オブジェクトとして Core Data に送信されます。Event は、特定の時点におけるデバイスからのセンサー読み取り値のコレクションです。Event オブジェクトの Reading オブジェクトは、デバイスによって感知された特定の値で、Reading にコンテキストを提供するために Device Resource に関連付けられます。

つまり、物体 (人間／犬) カウントカメラは、監視している空間に5人の人間と3匹の犬がいると判断するかもしれません。EdgeX では、カメラデバイスからこれらの感知された値を受け取ったデバイスサービスは、2つの Reading を持つ Event を作成します。

デバイスサービスは、Event と関連する Reading オブジェクトを作成すると、REST コールを介して Core Data にこの情報を送信します。

#### 8.7.1.1. チュートリアル: Event の送信
`Reading` を含む `Event` を Core Data に送信する手順について説明します。

以下に示すように、curl POST リクエストを作成します。

```bash
$　curl -X POST -d '{"apiVersion": "v2","event": {"apiVersion": "v2","deviceName": "countcamera1","profileName": "camera-monitor-profile","sourceName": "HumanCount","id":"d5471d59-2810-419a-8744-18eb8fa03464","origin": 1602168089665565200,"readings": [{"id": "7003cacc-0e00-4676-977c-4e58b9612abc","origin": 1602168089665565200,"deviceName": "countcamera1","resourceName": "HumanCount","profileName": "camera-monitor-profile","valueType": "Int16","value": "5"},{"id": "7003cacc-0e00-4676-977c-4e58b9612abf","origin":1602168089665565200,"deviceName": "countcamera1","resourceName": "CanineCount","profileName": "camera-monitor-profile","valueType": "Int16","value": "3"}]}}' localhost:59880/api/v2/event/camera-monitor-profile/countcamera1/HumanCount
```

##### 元のタイムスタンプ
デバイスサービスは、データが感知/収集された時間 （Epoch timestamp/nanoseconds 形式） を示すために、`Event and Reading` オブジェクトの `origin` プロパティを提供します。

EdgeX がナノ秒を使用するのは、一部のデバイスやユースケースで、その程度の精度が必要とされる場合があるからです。ナノ秒の精度であれば、衝突が発生することはまずありません。

元の Event は常にデバイスサービス SDK によって設定され、そのデバイスサービスインスタンスで一意であることが意図されています。元の Reading はデバイスサービスの `ProtocolDriver` 実装によって設定されるべきで、SDK はそれが設定されていない場合、それに元の Event をコピーします。

### 8.7.2. Event/Reading の探索
Event と、関連する Reading が Core Data に送信されたので、Core Data API を使用して、データベースに保存されているデータを調べることができます。

以前の手順で　Core Data にデータがまだ保存されていないことを確認しました。今回も同様の操作で、今度は、Event レコードがコアデータに送信されたことを確認します。

```bash
$　curl -X GET localhost:59880/api/v2/event/count/device/name/countcamera1
{"apiVersion":"v2","statusCode":200,"Count":1}
```

#### 8.7.2.1. チュートリアル: Event/Reading のクエリ
Event のリストを取得する手順について説明します。

`countcamera1` デバイスに関連する Event、および Reading を取得するために、curl で `GET` リクエストを行います。

```bash
$　curl -X GET localhost:59880/api/v2/event/device/name/countcamera1 | json_pp
{
   "apiVersion" : "v2",
   "events" : [
      {
         "apiVersion" : "v2",
         "deviceName" : "countcamera1",
         "id" : "d5471d59-2810-419a-8744-18eb8fa03464",
         "origin" : 1602168089665565200,
         "profileName" : "camera-monitor-profile",
         "readings" : [
            {
               "deviceName" : "countcamera1",
               "id" : "7003cacc-0e00-4676-977c-4e58b9612abc",
               "origin" : 1602168089665565200,
               "profileName" : "camera-monitor-profile",
               "resourceName" : "HumanCount",
               "value" : "5",
               "valueType" : "Int16"
            },
            {
               "deviceName" : "countcamera1",
               "id" : "7003cacc-0e00-4676-977c-4e58b9612abf",
               "origin" : 1602168089665565200,
               "profileName" : "camera-monitor-profile",
               "resourceName" : "CanineCount",
               "value" : "3",
               "valueType" : "Int16"
            }
         ],
         "sourceName" : "HumanCount"
      }
   ],
   "statusCode" : 200,
   "totalCount" : 1
}

```bash
$　curl -X GET localhost:59880/api/v2/reading/device/name/countcamera1 | json_pp
{
   "apiVersion" : "v2",
   "readings" : [
      {
         "deviceName" : "countcamera1",
         "id" : "7003cacc-0e00-4676-977c-4e58b9612abf",
         "origin" : 1602168089665565200,
         "profileName" : "camera-monitor-profile",
         "resourceName" : "CanineCount",
         "value" : "3",
         "valueType" : "Int16"
      },
      {
         "deviceName" : "countcamera1",
         "id" : "7003cacc-0e00-4676-977c-4e58b9612abc",
         "origin" : 1602168089665565200,
         "profileName" : "camera-monitor-profile",
         "resourceName" : "HumanCount",
         "value" : "5",
         "valueType" : "Int16"
      }
   ],
   "statusCode" : 200,
   "totalCount" : 2
}
```

Core Data には、Event や Reading のデータを取得するための API が多数用意されています。
例として、開始と終了の時間帯のすべての Event を検索する API は以下のようになります。

```bash
$　curl -X GET localhost:59880/api/v2/event/start/1602168089665560000/end/1602168089665570000 | json_pp
```

## 8.8. Device データのエクスポート
これで、カメラデバイスから送信されたデータが Core Data に到達します。
そのデータを企業システムやクラウドに送ったり、エッジ分析システム （Rules Engine など） に渡してデバイスを操作する方法について、これから説明していきます。

### 8.8.1. Rules Engine へのデータ送信
デフォルトでは、すでに Core Data サービスからアプリケーションサービスへ、Redis Pub/Sub メッセージングを介してデータが渡されています。他の方法として、MQTT 経由で両者の間にデータを供給することもできます。
EdgeX のデフォルトの Docker Compose ファイルには、このデータを取得して eKuiper (ルールエンジンにルーティングする、事前設定済みのアプリケーションサービス) が提供されています。このアプリケーションサービスは、`app-service-rules` (具体的には、アプリケーションサービスの Configurable) と呼ばれています。

```yaml
  app-service-rules:
    container_name: edgex-app-rules-engine
    depends_on:
    - consul
    - data
    environment:
      CLIENTS_CORE_COMMAND_HOST: edgex-core-command
      CLIENTS_CORE_DATA_HOST: edgex-core-data
      CLIENTS_CORE_METADATA_HOST: edgex-core-metadata
      CLIENTS_SUPPORT_NOTIFICATIONS_HOST: edgex-support-notifications
      CLIENTS_SUPPORT_SCHEDULER_HOST: edgex-support-scheduler
      DATABASES_PRIMARY_HOST: edgex-redis
      EDGEX_PROFILE: rules-engine
      EDGEX_SECURITY_SECRET_STORE: "false"
      MESSAGEQUEUE_HOST: edgex-redis
      REGISTRY_HOST: edgex-core-consul
      SERVICE_HOST: edgex-app-rules-engine
      TRIGGER_EDGEXMESSAGEBUS_PUBLISHHOST_HOST: edgex-redis
      TRIGGER_EDGEXMESSAGEBUS_SUBSCRIBEHOST_HOST: edgex-redis
    hostname: edgex-app-rules-engine
    image: edgexfoundry/app-service-configurable:2.0.1
    networks:
      edgex-network: {}
    ports:
    - 127.0.0.1:59701:59701/tcp
    read_only: true
    security_opt:
    - no-new-privileges:true
    user: 2002:2001
```

#### 8.8.1.1. データのエクスポートの確認
EdgeX マイクロサービスのログレベルは、デフォルトで `INFO` に設定されています。`app-service-rules` マイクロサービスのログレベルを `DEBUG` に設定すると、Rules Engine に到達するまでの間にアプリサービスを経由する Event を確認できます。

##### ログレベルの設定
任意のサービスのログレベルを設定するには、ブラウザで `http://localhost:8500` にアクセスし、Consul UI を開きます。Consul UI が開いたら、画面左部の「Key/Value」タブを押下します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/6bdd5f3e-a55c-5c87-10c2-c60e6dcae2a0.png" width="500px"/>

`Key/Value` 表示ページで、`edgex > appservices > 2.0 > app-rules-engine > Writable > LogLevel` を順に押下します。表示された値の入力フィールドで、`INFO` を `DEBUG` に置き換えて、「Save」ボタンを押下します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659159/bc82fd4e-a9da-72fd-964b-7e6786775813.png" width="700px"/>

##### サービスログの表示
ログレベルの変更は、アプリケーションサービスによって取得されます。ターミナルウィンドウで、以下の Docker コマンドを実行し、サービスログを表示します。

```bash
$　docker logs -f edgex-app-rules-engine
```

次に、先ほどと同じように、別の Event/Reading を Core Data に push します。
```bash
$ curl -X POST -d '{"apiVersion": "v2","event": {"apiVersion": "v2","deviceName": "countcamera1","profileName": "camera-monitor-profile","sourceName": "HumanCount","id":"adf0f76d-75b3-4f4f-9536-1c43994f1407","origin": 1602168089665565200,"readings": [{"id": "dd29d8c4-1376-49bd-bf6e-32b1d34e1e8e","origin": 1602168089665565200,"deviceName": "countcamera1","resourceName": "HumanCount","profileName": "camera-monitor-profile","valueType": "Int16","value": "5"},{"id": "c5c4cfda-290e-48d7-84b2-28af66473ffd", "origin":1602168089665565200,"deviceName": "countcamera1","resourceName": "CanineCount","profileName": "camera-monitor-profile","valueType": "Int16","value": "3"}]}}' localhost:59880/api/v2/event/camera-monitor-profile/countcamera1/HumanCount
{"apiVersion":"v2","statusCode":201,"id":"adf0f76d-75b3-4f4f-9536-1c43994f1407"}
```

新しい Event/Reading が作成されるたびに、アプリケーション・サービスによって認識されるのがわかるはずです。
```bash
$　docker logs -f edgex-app-rules-engine
### 省略
level=DEBUG ts=2023-04-06T02:21:55.4025485Z app=app-rules-engine source=triggermessageprocessor.go:196 msg="trigger successfully processed message 'default-pipeline' in pipeline 240fad57-667b-4452-918b-747999fe5602"
level=DEBUG ts=2023-04-06T02:21:55.4025333Z app=app-rules-engine source=responsedata.go:40 msg="Setting response data in pipeline 'default-pipeline'"
level=DEBUG ts=2023-04-06T02:21:55.4040636Z app=app-rules-engine source=messaging.go:246 msg="MessageBus Trigger: Published response message for pipeline 'default-pipeline' on topic 'rules-events' with 415 bytes"
### 省略
```

アプリケーションサービスおよび Rules Engine の設定が適切にされていれば、Event/Reading データは Rules Engine トピックに公開され後、Rules Engine サービスによってピックアップされ、コマンド実行する際に使用することができます。

# Appendix
## Appendix A: 参考文献q
- Webページ: " EdgeX Foudry", https://www.edgex.org/
- Webページ: "Getting Started With EdgeX Foundry", Jim White, https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Workshop-Getting-Started-with-EdgeX-Foundry-Jim-White-Dell-Technologies.pdf

## Appendix B: EdgeX 用語集
| 用語 | 説明 |
|:--|:--|
| エッジコンピューティング | IoT デバイスなどのエッジ端末で行われるコンピューティングのこと。エッジ端末自体でデータ処理を行うことで、クラウドへのデータ転送や処理負荷を減らすことができる。|
| EdgeX | エッジコンピューティングにおけるフレームワークで、オープンソースで提供される。様々なデバイスとシステムを接続し、データ処理や分析、アプリケーションの実行などを行う。|
| マイクロサービス | 小さな機能を持つサービスのこと。EdgeX では、マイクロサービスを組み合わせることで、システムをより柔軟かつ拡張可能にすることができる。|
| Docker | アプリケーションをコンテナ化するためのオープンソースのプラットフォーム。EdgeX では、Docker コンテナを利用してアプリケーションをデプロイすることができる。|
| Kubernetes | コンテナオーケストレーションシステムの一つで、複数の Docker コンテナを自動的に管理することができる。EdgeX では、Kubernetesを利用してアプリケーションをデプロイすることができる。|
| MQTT | IoT デバイスとの通信に用いられるプロトコルの一つ。EdgeX では、MQTTを利用してデバイスとの通信を行うことができる。|
| InfluxDB | オープンソースの時系列データベースで、EdgeX では、InfluxDB を利用してデータの保存やクエリ処理を行うことができる。|
| Grafana | オープンソースのデータ可視化ツールで、EdgeX では、Grafana を利用してデータの可視化やダッシュボードの作成を行うことができる。|
| Consul | オープンソースのサービスメッシュツールで、EdgeX では、Consul を利用してマイクロサービスのディスカバリーやロードバランシングを行うことができる。|
| Envoy | オープンソースのサイドカーコンテナで、EdgeX では、Envoy を利用してマイクロサービス間の通信をプロキシすることができる。|
| Fluentd | データコレクションのためのオープンソースのデータ収集エージェントであり、ログや Event などのデータを収集して様々なデータストアやツールに転送することができます。EdgeX においても、Fluentd はデータの収集・転送に用いられることがあります。|
| MQTT | 「Message Queuing Telemetry Transport」の略で、軽量なメッセージングプロトコルの一つ。特に IoT 分野で使用され、小規模デバイスからのメッセージ通信に適している。|
| OpenAPI | API の設計とドキュメント作成を支援する仕様書の一つ。API のエンドポイント、HTTP メソッド、パラメーター、レスポンスなどを定義する。|
| RESTful API | REST と呼ばれるアーキテクチャスタイルに基づくAPIのこと。HTTP プロトコルを使用し、リソースを表現し、URI を通じてアクセスすることでデータのやり取りを行う。|
| TLS | 「Transport Layer Security」の略で、セキュアな通信を行うためのプロトコルの一つ。主に HTTPS 通信などで使用され、通信内容の暗号化や認証を行う。|
| YAML | シンプルなマークアップ言語の一つで、データの表現や構成を記述するために使用される。EdgeX の設定ファイルやマニフェストファイルなどで使用される。

## Appendix C: デフォルトのサービスポート

**コアサービス**

| サービス名 | ポート番号 |
|:--|:--|
| core-data | 59880<br/>5563 (deprecated ZMQ port) |
| core-metadata | 59881 |
| core-command | 59882 |
| redis | 6379 |
| consul | 8500 |

**サポートサービス**

| サービス名 | ポート番号 |
|:--|:--|
| support-notifications | 59860 |
| support-scheduler | 59861 |
| rules engine / eKuiper | 59720 |
| system management agent (deprecated) | 58890 |

**アプリケーションサービス**

| サービス名 | ポート番号 |
|:--|:--|
| app-sample | 59700 |
| app-service-rules | 59701 |
| app-push-to-core | 59702 |
| app-mqtt-export | 59703 |
| app-http-export | 59704 |
| app-functional-tests | 59705 |
| app-external-mqtt-trigger | 59706 |
| app-metrics-influxdb | 59707 |
| app-rfid-llrp-inventory | 59711 |

**デバイスサービス**

| サービス名 | ポート番号 |
|:--|:--|
| device-virtual | 59900 |
| device-modbus | 59901 |
| device-bacnet | 59980 |
| device-mqtt | 59982 |
| device-usb-camera | 59983 |
| device-onvif-camera | 59984 |
| device-camera | 59985 |
| device-rest | 59986 |
| device-coap | 59988 |
| device-rfid-llrp | 59989 |
| device-grove | 59992 |
| device-snmp | 59993 |
| device-gpio | 59994 |

**セキュリティサービス**

| サービス名 | ポート番号 |
|:--|:--|
| kong-db | 5432 |
| vault | 8200 |
| kong | 8000, 8100, 8443 |
| security-spire-server | 59840 |
| security-spiffe-token-provider | 59841 |

**その他**

| サービス名 | ポート番号 |
|:--|:--|
| ui | 4000 |
| Modbus simulator | 1502 |
| MQTT broker | 1883 |