TwinMakerからローカルPC上のデータソースに直結する方法（例：Echonet Liteを直接操作する）

# この記事について

クラウドを経由せず、TwinMakerからローカルPCのデータを直接参照する方法を紹介します

## どうやって？

- TwinMakerの接続先をローカルに切り替えるオプションがあります
- FastAPIを使って、ローカルPC上にTwinMaker互換のWebサーバを立てます

## 何が嬉しいの？

- どれだけヘビーに使っても、TwinMakerの利用料金が0になります
- ブラウザから端末方向への取得リクエストができるようになります
- セキュリティの都合でインターネットに送れないデータも、デジタルツインで可視化できます

## 動作しているところ（GIF画像）

気温センサーが38度を超えると、タグの色が赤くなって、MMDのキャラクターが手を振るように設定しています。Echonet Liteのシミュレータ（萌花電ルーム）の気温を変更すると、ほぼリアルタイムに連動してTwinMakerに反映されます。

![echonet-direct.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/8ca8a1e5-8b3b-55d0-597b-4bcb7a3e781b.gif)

## ソースコード

プロジェクトのソースコードはこちらに置いています
https://github.com/ShotaOki/twinmaker-echonet-direct

----

# 構成図

### 本来のTwinMakerのデータの流れ

本来のTwinMakerは、クラウド（主にSiteWise）にデータを保管します。
TwinMakerは保管したデータをリアルタイムに表示しています。

![common-structure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/956363bd-fd26-fbfa-20d1-0294f1e1e46b.png)

### ローカルで動かす

この記事では、下のようにフローを切り替える方法を紹介します。

![common-structure copy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/7750fdf6-1e26-1bc2-1400-3b9bbd4e8321.png)

# 前提条件

- AWSのiot-app-kitを利用します

https://github.com/awslabs/iot-app-kit

# 実装方法（React側）

iot-app-kitでTwinMakerに接続するときに、認証情報とリージョンを渡しています。

```typescript: App.tsx
  // TwinMakerのシーンを読み込む
  const twinmaker = initialize(process.env.REACT_APP_AWS_WORKSPACE_NAME ?? "", {
    awsCredentials: {
      accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID ?? "",
      secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY ?? "",
    },
    awsRegion: process.env.REACT_APP_AWS_REGION ?? "",
  });
```

これを、以下のように書き変えます。
IoTTwinMakerClientを作成して、initialize関数の引数に渡します。

```typescript: App.tsx
  // ローカル環境にエンドポイントが向いたTwinMakerClientを作成する
  const twinMakerClient = new IoTTwinMakerClient({
    endpoint: "http://localhost:8000", // localhostを接続先にする
    region: process.env.REACT_APP_AWS_REGION ?? "",
    credentials: {
      accessKeyId: "xxxxxxx", // ダミー文字列（未指定だとエラーになるので適当な文字を入れる）
      secretAccessKey: "xxxxxx", // ダミー文字列（未指定だとエラーになるので適当な文字を入れる）
    },
  });

  // TwinMakerのシーンを読み込む
  const twinmaker = initialize(process.env.REACT_APP_AWS_WORKSPACE_NAME ?? "", {
    iotTwinMakerClient: twinMakerClient, // これを追加:: TwinMakerの接続先を変更する
    awsCredentials: {
      accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID ?? "",
      secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY ?? "",
    },
    awsRegion: process.env.REACT_APP_AWS_REGION ?? "",
  });
```

これでlocalhostからデータを取るようになります。

# 実装方法（サーバ側）

取りに行った先で正しいデータを返す必要があります。
FastAPIを使って、ローカル環境（localhost:8000）にサーバを作ります。

必要な依存ライブラリは以下の通りです。

```text:requirements.txt
fastapi
hypercorn
requests_aws4auth
```

```bash
python -m pip install -r requirements.txt
```

リクエストへの返答を全て実装してもよいのですが、時系列データの取得と基本設定の取得が飛んでくるため、全部作るのは手間がかかります。今回は時系列データの取得リクエストだけをローカルで処理して、それ以外のリクエストはAWSにスルーパスします。

![datasource.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/9bc07245-8b12-bc2c-bdd7-49e663338e83.png)

ソースコードは以下のようになります。

```python:main.py
from fastapi import FastAPI, Request
from starlette.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import requests_aws4auth
import requests
from datetime import datetime

class AwsAccessInfo(BaseModel):
    region: str
    access_key: str
    secret_access_key: str

class ApiProxy:
    _cache: dict
    def __init__(self) -> None:
        self._cache = {}
    
    def request_api(self, request: Request, aws_access_info: AwsAccessInfo):
        """
        TwinMakerの時系列データの取得以外の処理を実施する
        """
        twinmaker_domain = f"iottwinmaker.{aws_access_info.region}.amazonaws.com"
        original_url: str = request.url._url
        url = original_url.replace(f"localhost:{request.url.port}", twinmaker_domain).replace("http://", "https://")
        # AWSから取得したデータは変更されるものではないため、キャッシュする
        if url in self._cache:
            return self._cache[url]
        # リクエストのヘッダにV4署名をつける
        auth = requests_aws4auth.AWS4Auth(
            aws_access_info.access_key,
            aws_access_info.secret_access_key,
            aws_access_info.region,
            'iottwinmaker',
        )
        # リクエストを転送する
        # 基本情報の取得はGetリクエストなので、URLだけ参照すればよい
        response = requests.get(url, auth=auth)
        result = response.json()

        self._cache[url] = result
        return result


class HistoryInput(BaseModel):
    """
    リクエストとして受け取るPOST Bodyのデータ構造
    """
    componentName:str
    endTime: str
    entityId: str
    orderByTime: str
    selectedProperties: List[str]
    startTime: str

app = FastAPI()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"], 
    allow_headers=["*"]
)

api_proxy = ApiProxy()
@app.get("/{path:path}")
async def proxy(request: Request):
    """
    基本設定の取得リクエストを処理する
    """
    return api_proxy.request_api(request, AwsAccessInfo(
        access_key="xxxxxxxxxxxxxxxxxxxx",
        secret_access_key="xxxxxxxxxxxxxxxxxxxxxxxxx",
        region="us-east-1"
    ))

@app.post("/workspaces/{workspace_id}/entity-properties/history")
async def history(workspace_id: str, item: HistoryInput):
    """
    時系列データの取得リクエストを横取りする
    """
    value = 0.0 # TwinMakerに表示する値を取得して、valueに格納する
    return {
        'propertyValues': [
            {
                "entityPropertyReference": {
                    "componentName": item.componentName,
                    "entityId": item.entityId,
                    "propertyName": property
                },
                "values": [{
                    "time": datetime.now().isoformat(),
                    "value": {
                        "doubleValue": value
                    }
                }]
            }
            for property in item.selectedProperties
        ]
    }
```

上のソースコードを、ローカルPC上で動かせばOKです。実行コマンドは以下の通りです。

```bash
hypercorn main:app
```

----

# TwinMakerから直接Echonet Liteを実行してみる

上の手順を応用して、TwinMakerからEchonet Liteを直接実行してみます。

実機でEchonet Liteに対応した家電があればそれを使ってもよいのですが、今回はSonyのシミュレータ（萌花電ルーム）を使います。SonyCSLのgithubを開いて、Download executablesにある`Win64+Java Runtime`の実行ファイルをダウンロードします。

https://github.com/SonyCSL/MoekadenRoom/blob/master/README.jp.md

> ※萌花電はKadecotのプロジェクト（https://www.sonycsl.co.jp/tokyo/347/ ）の一部ですが、Kadecotは2018年に終了したため、後継プロジェクトのPicoGWに移行しました。ドメインが第三者に取られているので、githubとSonyCSLのページ以外を踏むときは気を付けてください。

Echonet Liteの通信をPythonでゼロから書くとなかなかの大仕事になるので、Node.jsのPicoGWを使って動かします。REST APIをEchonet Liteのソケット通信に変換してくれるサービスです。Windows上では動かないので、Macを用意して動かします。

https://github.com/KAIT-HEMS/node-picogw

https://github.com/KAIT-HEMS/node-picogw-plugin-echonet

**導入方法**

Mac上で以下のコマンドを実行すると、インストールされます。

```bash
npm install picogw -g
npm install picogw-plugin-echonet -g
```

実行は以下のコマンドで行います。

```bash
picogw
```

もし（setMulticastLoopback）でエラーが出るようなら、インストールされたライブラリ（/usr/local/lib/node_modules/picogw/node_modules/echonet-lite/index.js）を手で編集します。bindの完了前にコールバックが呼ばれているので、完了したことをeventsCountで判定してから後続処理を流すようにします。

```javascript: echonet-lite/index.js（184行目：修正前）
		EL.sock4.bind( {'address': '0.0.0.0', 'port': EL.EL_port}, function () {
			EL.sock4.setMulticastLoopback(true);
			EL.sock4.addMembership(EL.EL_Multi);
		});
```

```javascript: echonet-lite/index.js（184行目：修正後）
		EL.sock4.bind( {'address': '0.0.0.0', 'port': EL.EL_port}, function () {
            if (EL.sock4._eventsCount == 1) { // <- APPEND
    			EL.sock4.setMulticastLoopback(true);
    			EL.sock4.addMembership(EL.EL_Multi);
            }
		});
```

PicoGWが立ち上がったら、FastAPIのリクエスト部分に取得処理を書きます。
MoekadenRoomの温度センサーを取得しています。

```python
def read_history_data(parameter: DirectAccessParameter):
    # 気温センサーの接続情報を指定する
    device_name = "temperaturesensor_1"
    property_name = "temperaturemeasurementvalue"
    # PicoGWにリクエストを投げる
    response = requests.get(f"{picogw_info.picogw_domain}/v1/echonet/{device_name}/{property_name}")
    # 結果を取得する
    value = [v.get("value", 0.0) for v in response.json().values()][0]
    return [{
        "time": datetime.now().isoformat(),
        "value": {
            "doubleValue": value
        }
    }]
```

PicoGWを立ち上げて、実際にTwinMakerを動かしたGIFがこちらです。

![echonet-direct.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233180/8ca8a1e5-8b3b-55d0-597b-4bcb7a3e781b.gif)

データベースやSiteWiseを間に挟まず、Echonet Liteを直接実行して、その結果でタグの色が変わっています。

# まとめ

- iot-app-kitの実装をほとんど触らずに柔軟にデータソースを切り替えられること
- Echonet Liteの資産とTwinMakerがそのまま連携できること
- TwinMakerの料金がかかる部分も、構成によっては無料になること

が伝われば幸いです。