音声認識やWebサーバからIoT機器を制御したい。
ただ、それぞれ個別に設定すると機器が増える度に実装が必要になってくるため、効率的な方法を教えて欲しい。
こんなお悩みを解決します。
以前、赤外線機器を制御するために、http server
を立ち上げました。
【解説】Raspberry Pi+Dockerでサーバ構築
続きを見る
このサーバへのリクエスト方法ですが、後日構築するWebサーバのほか、Amazon Alexa、Google Homeなどを対象とする予定です。
ただ、リクエスト時に用いる機器を追加する度に設定をするのは非効率です。
ここでは、リクエスト元のアクセス方法の違いを吸収する薄いWrapperを用意し、赤外線機器制御用http server
は極力変更を加えない方針とします。
こうすることで、アクセス方法の違いの管理を一か所に集約でき、影響を局所化できると考えました。
また、ほかの機能(例えばルンバの制御)が増えた場合もHTTPエンドポイントを用意することで、同一の方法で管理できます。
上記を実現するにあたり、コードベースでは見通しが悪くなると考え、ここでは、GUIベースで処理を記述できるNode-Red
を用いることにしました。
実現しようとしている構成の全体像を以下に示します。
Docker環境構築
DockerでNode-Redを構築します。環境構築した部分までをGitHubに反映しました。
https://github.com/yuruto-free/home-server/tree/v0.3.0_nodered
Node-Redへのアクセス
以下のコマンドを実行し、Node-Redを起動します。
docker-compose up -d
しばらくして、Node-Red起動後にWebブラウザからhttp://raspberrypi.local:1880
にアクセスします。
以下の画面が表示されれば成功です。
表示されない場合は、内部処理が完了していない可能性があるため、しばらく経ってからアクセスしてみてください。
Node-Redでの開発
ここから、Node-Redで開発していきます。
まずは、完成形をイメージしてもらうため、私の環境での構築結果を共有します。
上記には、Amazon Alexaによる制御も含まれていますが、今回は、照明の制御を対象に処理を追加していきます。
同じように設定をすれば、扇風機やエアコンの制御もできます。
事前準備
大まかな流れは以下のようになります。
- リクエストを受けるため、HTTPエンドポイントを定義する。
- データを共通フォーマットに書き換える。
- 共通フォーマットをもとにリクエストを赤外線制御サーバが解釈できるのデータ形式に変換する。
- 赤外線制御サーバにリクエストを出す。
- 赤外線制御サーバからのレスポンスを処理し、Node-Redとしてのレスポンスを返す。
今回、HTTPエンドポイント、共通フォーマットの仕様は、以下のように定義します。
HTTPエンドポイント
対象 | 説明 | フォーマット | 例 |
---|---|---|---|
HTTP Method | リクエストを許可するHTTP Method名 | POST (固定) | POST |
URL | 制御対象のリンク | /machine_name | /light |
body | リクエスト時に送信されるデータ | {"payload": "controller_name"} | {"payload": "on"} |
machine_nameは、共通フォーマットを定義する際に指定するため何でも良いですが、私は基本的に赤外線制御サーバ側の名称に揃えています。例外は、エアコンの操作です。
エアコンは、冷房、暖房、除湿があるため、制御対象は順にcooler
、heater
、dry
という名称で分けています。
ただ、赤外線制御サーバ側の制御対象はair_conditioner
としているため、ここにズレが生じます。
このズレは、共通フォーマットのcontroller
で吸収しています。
共通フォーマット
項目 | 説明 | 例 |
---|---|---|
machine | 制御対象機器の名称 | light |
pattern | 制御対象の種類 | light |
controller | 制御対象のコマンド名 | on |
machine
は、赤外線制御サーバのconfig.json
の内容を参照する構成とするため、config.json
の内容と揃えるようにしてください。
pattern
とcontroller
は、変換処理部分でマッピングさせるため、区別できる名称であればよいです。
フローの名称定義
後で確認したい際に何をするフローであるかが分かるようにフローの名称を定義します。
「フロー1」という部分をダブルクリックし、「名前」の部分を「家電操作」に変更し、「完了」を押下します。
HTTPエンドポイントの定義
以下の手順でHTTPエンドポイントを定義します。
- 「http in」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- メソッド[固定値]:POST
- URL[任意の値]:/light
- 名前[任意の値]:照明
- 完了を押下
共通フォーマットへの書き換え
まず、以下の手順でリクエストされてきたbodyの値をpayloadに設定します。
- 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「照明」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:キー取得(照明)
- 値の代入[固定値]:
msg.payload.key
対象の値[固定値]:msg.req.body.payload
- 完了を押下
次に、以下の手順で共通フォーマットを設定します。
- 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「キーの取得(照明)」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:制御対象の指定(照明)
- ルール[固定値]:
- 値の代入:
msg.payload.machine
対象の値:light
- 値の代入:
msg.payload.pattern
対象の値:light
- 値の代入:
msg.payload.controller
対象の値:msg.payload.key
- 値の削除:
msg.payload.key
- 値の代入:
- 完了を押下
データ形式の変換
以下の手順で赤外線制御サーバが解釈できるデータ形式に変換します。
- 「function」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「制御対象の指定(照明)」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を後述のように編集
- 完了を押下
ノードの内容
// define table
/*
[注意]
on, off, nightは、リクエスト時にpayloadとして送信する値(msg.req.body.payload)と一致するように設定してください。
*/
const replace_table = {
'light': {
'on': {
'command': 'on',
'message': '照明ON',
},
'off': {
'command': 'off',
'message': '照明OFF',
},
'night': {
'command': 'night_light',
'message': 'こだまにする',
},
},
};
const pattern_table = {
'light': '照明',
};
// get request data
const controller = msg.payload.controller;
const pattern = msg.payload.pattern;
const machine = msg.payload.machine;
const request = replace_table[pattern];
// set payload data
if (controller in request) {
msg.payload = request[controller];
msg.statusCode = 200;
}
else {
const target = pattern_table[pattern];
msg.payload = {
'message': `[${target}]Invalid request`,
};
msg.statusCode = 400;
}
msg.payload['machine'] = machine;
return msg;
上記の設定により、赤外線制御サーバが解釈できるデータを構築できます。
例えば、リクエスト時にon
を指定した場合、以下に示すデータがmsg.payload
に設定されます。
{
"machine": "light",
"command": "on",
"message": "照明ON"
}
msg.payloadの設定値は、irc.py
のexecute
メソッド111行目~114行目で参照されます。
https://github.com/yuruto-free/home-server/blob/v0.3.0_nodered/infrared_controller/src/irc.py#L111
赤外線制御サーバにリクエスト
赤外線制御サーバにリクエストを出すための準備をします。
まず、以下の手順でdictionaly型のオブジェクトをJSON文字列に変換します。
- 「json」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「変換」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 動作[固定値]:JSON文字列とオブジェクト間の相互変換
- プロパティ[固定値]:
msg.payload
- 完了を押下
次に、HTTP headerを設定します。今回、データはJSON形式で送信するため、Content-Type
を明示的に指定します。
- 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「json」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:ヘッダの設定
- 値の代入[固定値]:
msg.headers.content-type
対象の値[固定値]:application/json; charset=utf-8
- 完了を押下
最後に、赤外線制御サーバにリクエストを出します。
- 「http request」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「ヘッダの設定」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- メソッド[固定値]:POST
- URL[固定値]:
http://infrared_controller:8180
- 出力形式[固定値]:JSONオブジェクト
- 名前[任意の値]:赤外線制御サーバ
- 完了を押下
ここで、URLは、docker-compose.ymlで指定したサービス名(ここでは、infrared_controller
)とポート番号を指定しています。
dockerには名前解決機能も備わっているため、このような方法で通信が可能となります。
赤外線制御サーバからのレスポンスを処理・Node-Redのレスポンス作成
制御実行後に得られたレスポンスをもとに、Node-Redとしてのレスポンスを返します。
まず、赤外線制御サーバからのレスポンスを処理します。
- 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「赤外線制御サーバ」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:データの集約
- 値の代入[固定値]:
msg.result
対象の値[固定値]:msg.payload
- 完了を押下
次に、レスポンスデータを作成します。
- 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「赤外線制御サーバ」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:responseデータ作成
- ルール[固定値]:
- 値の代入[固定値]:
msg.statusCode
対象の値[固定値]:msg.result.status_code
- 値の代入[固定値]:
msg.payload
対象の値[固定値]:msg.result.message
- 値の削除[固定値]:
msg.result
- 値の代入[固定値]:
- 完了を押下
最後に、レスポンス処理を行います。
- 「http response」ノードをドラッグ&ドロップし、フローの任意の場所に配置
- 「responseデータ作成」ノードと配置したノードを接続
- 配置したノードをダブルクリック
- ノードの内容を以下のように編集
- 名前[任意の値]:response
- 完了を押下
一通り作業が完了したら、「デプロイ」を押下します。
デプロイと同時にフローの内容が保存されます。
動作確認
今回もcurl
コマンドで動作確認を行います。
docker-composeコマンドで赤外線制御サーバとNode-Redを起動した状態で、以下のコマンドを実行してください。
curl -X POST -H "Content-Type: application/json" -d '{"payload": "on"}' http://raspberrypi.local:1880/light -w "\nstatus code:%{http_code}\n"
status code:200
コマンドを実行後、照明がつくことを確認します。また、以下のコマンドを実行し、ログが表示されていればOKです。
docker-compose logs infrared_controller
infrared_controller | [2022/05/01 16:56:17 INFO] irc Start initialization
infrared_controller | [2022/05/01 16:56:17 INFO] irc Complete initialization
infrared_controller | [2022/05/01 19:55:50 INFO] irc 照明ON
infrared_controller | 172.21.0.3 - - [01/May/2022 19:55:50] "POST / HTTP/1.1" 200 -
今回の実行結果は、以下に反映してあります。
https://github.com/yuruto-free/home-server/tree/v0.3.1_nodered
さいごに
今回は、Node-Redを用いてアプリケーションサーバを構築しました。照明のみを例に取り上げ、紹介しましたが、他の家電に対しても適用可能なため、自由にカスタマイズしていただいて構いません。
余談
扇風機やエアコンの利用例をサンプルとして追加しました。赤外線情報はダミーですので、ご自身の環境に合わせて更新してください。
https://github.com/yuruto-free/home-server/tree/v0.3.2_nodered