広告 デバイス

【解説】Node-Redによるアプリケーションサーバ構築

※本ページには、プロモーション(広告)が含まれています。

悩んでいる人
悩んでいる人

音声認識やWebサーバからIoT機器を制御したい。

ただ、それぞれ個別に設定すると機器が増える度に実装が必要になってくるため、効率的な方法を教えて欲しい。

こんなお悩みを解決します。

以前、赤外線機器を制御するために、http serverを立ち上げました。

あわせて読みたい
【解説】Raspberry Pi+Dockerでサーバ構築

続きを見る

このサーバへのリクエスト方法ですが、後日構築するWebサーバのほか、Amazon Alexa、Google Homeなどを対象とする予定です。

ただ、リクエスト時に用いる機器を追加する度に設定をするのは非効率です。

ここでは、リクエスト元のアクセス方法の違いを吸収する薄いWrapperを用意し、赤外線機器制御用http serverは極力変更を加えない方針とします。

こうすることで、アクセス方法の違いの管理を一か所に集約でき、影響を局所化できると考えました。

また、ほかの機能(例えばルンバの制御)が増えた場合もHTTPエンドポイントを用意することで、同一の方法で管理できます。

上記を実現するにあたり、コードベースでは見通しが悪くなると考え、ここでは、GUIベースで処理を記述できるNode-Redを用いることにしました。

実現しようとしている構成の全体像を以下に示します。

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での開発

ここから、Node-Redで開発していきます。

まずは、完成形をイメージしてもらうため、私の環境での構築結果を共有します。

完成形イメージ

上記には、Amazon Alexaによる制御も含まれていますが、今回は、照明の制御を対象に処理を追加していきます。

同じように設定をすれば、扇風機やエアコンの制御もできます。

事前準備

大まかな流れは以下のようになります。

  1. リクエストを受けるため、HTTPエンドポイントを定義する。
  2. データを共通フォーマットに書き換える。
  3. 共通フォーマットをもとにリクエストを赤外線制御サーバが解釈できるのデータ形式に変換する。
  4. 赤外線制御サーバにリクエストを出す。
  5. 赤外線制御サーバからのレスポンスを処理し、Node-Redとしてのレスポンスを返す。

今回、HTTPエンドポイント、共通フォーマットの仕様は、以下のように定義します。

HTTPエンドポイント

対象説明フォーマット
HTTP Methodリクエストを許可するHTTP Method名POST (固定)POST
URL制御対象のリンク/machine_name/light
bodyリクエスト時に送信されるデータ{"payload": "controller_name"}{"payload": "on"}
Node-RedのHTTPエンドポイント

machine_nameは、共通フォーマットを定義する際に指定するため何でも良いですが、私は基本的に赤外線制御サーバ側の名称に揃えています。例外は、エアコンの操作です。

エアコンは、冷房、暖房、除湿があるため、制御対象は順にcoolerheaterdryという名称で分けています。

ただ、赤外線制御サーバ側の制御対象はair_conditionerとしているため、ここにズレが生じます。

このズレは、共通フォーマットのcontrollerで吸収しています。

共通フォーマット

項目説明
machine制御対象機器の名称light
pattern制御対象の種類light
controller制御対象のコマンド名on
共通フォーマット

machineは、赤外線制御サーバのconfig.jsonの内容を参照する構成とするため、config.jsonの内容と揃えるようにしてください。

patterncontrollerは、変換処理部分でマッピングさせるため、区別できる名称であればよいです。

フローの名称定義

後で確認したい際に何をするフローであるかが分かるようにフローの名称を定義します。

「フロー1」という部分をダブルクリックし、「名前」の部分を「家電操作」に変更し、「完了」を押下します。

フローの名称の変更(選択処理)
フローの名称の変更(名前変更)

HTTPエンドポイントの定義

以下の手順でHTTPエンドポイントを定義します。

  1. 「http in」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 配置したノードをダブルクリック
  3. ノードの内容を以下のように編集
    • メソッド[固定値]:POST
    • URL[任意の値]:/light
    • 名前[任意の値]:照明
  4. 完了を押下
HTTPエンドポイントの定義

共通フォーマットへの書き換え

まず、以下の手順でリクエストされてきたbodyの値をpayloadに設定します。

  1. 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「照明」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:キー取得(照明)
    • 値の代入[固定値]:msg.payload.key
      対象の値[固定値]:msg.req.body.payload
  5. 完了を押下
bodyのデータをpayloadに設定

次に、以下の手順で共通フォーマットを設定します。

  1. 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「キーの取得(照明)」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:制御対象の指定(照明)
    • ルール[固定値]:
      1. 値の代入:msg.payload.machine
        対象の値:light
      2. 値の代入:msg.payload.pattern
        対象の値:light
      3. 値の代入:msg.payload.controller
        対象の値:msg.payload.key
      4. 値の削除:msg.payload.key
  5. 完了を押下
共通フォーマットの設定

データ形式の変換

以下の手順で赤外線制御サーバが解釈できるデータ形式に変換します。

  1. 「function」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「制御対象の指定(照明)」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を後述のように編集
  5. 完了を押下

ノードの内容

// 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.pyexecuteメソッド111行目~114行目で参照されます。

https://github.com/yuruto-free/home-server/blob/v0.3.0_nodered/infrared_controller/src/irc.py#L111

赤外線制御サーバにリクエスト

赤外線制御サーバにリクエストを出すための準備をします。

まず、以下の手順でdictionaly型のオブジェクトをJSON文字列に変換します。

  1. 「json」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「変換」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 動作[固定値]:JSON文字列とオブジェクト間の相互変換
    • プロパティ[固定値]:msg.payload
  5. 完了を押下
JSON形式の変換

次に、HTTP headerを設定します。今回、データはJSON形式で送信するため、Content-Typeを明示的に指定します。

  1. 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「json」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:ヘッダの設定
    • 値の代入[固定値]:msg.headers.content-type
      対象の値[固定値]:application/json; charset=utf-8
  5. 完了を押下
HTTP headerの設定

最後に、赤外線制御サーバにリクエストを出します。

  1. 「http request」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「ヘッダの設定」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • メソッド[固定値]:POST
    • URL[固定値]:http://infrared_controller:8180
    • 出力形式[固定値]:JSONオブジェクト
    • 名前[任意の値]:赤外線制御サーバ
  5. 完了を押下

ここで、URLは、docker-compose.ymlで指定したサービス名(ここでは、infrared_controller)とポート番号を指定しています。

dockerには名前解決機能も備わっているため、このような方法で通信が可能となります。

赤外線制御サーバへリクエストを送信

赤外線制御サーバからのレスポンスを処理・Node-Redのレスポンス作成

制御実行後に得られたレスポンスをもとに、Node-Redとしてのレスポンスを返します。

まず、赤外線制御サーバからのレスポンスを処理します。

  1. 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「赤外線制御サーバ」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:データの集約
    • 値の代入[固定値]:msg.result
      対象の値[固定値]:msg.payload
  5. 完了を押下
データの集約

次に、レスポンスデータを作成します。

  1. 「change」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「赤外線制御サーバ」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:responseデータ作成
    • ルール[固定値]:
      1. 値の代入[固定値]:msg.statusCode
        対象の値[固定値]:msg.result.status_code
      2. 値の代入[固定値]:msg.payload
        対象の値[固定値]:msg.result.message
      3. 値の削除[固定値]:msg.result
  5. 完了を押下
レスポンスデータの作成

最後に、レスポンス処理を行います。

  1. 「http response」ノードをドラッグ&ドロップし、フローの任意の場所に配置
  2. 「responseデータ作成」ノードと配置したノードを接続
  3. 配置したノードをダブルクリック
  4. ノードの内容を以下のように編集
    • 名前[任意の値]:response
  5. 完了を押下
http 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

スポンサードリンク



-デバイス
-,