数年越しのNode-RED入門 (その2)

ファームウェアエンジニアの井田です。前回 に引き続き、Node-RED でNature Cloud APIを触るお話です。

Node-REDのフローのデータ

本記事で使用しているNode-REDのフローのデータはこちらのリポジトリに置いてありますので、適宜参照してください。

github.com

Nature Cloud APIアクセスの簡略化

前回まで でhttp requestノードを使ってNature Cloud APIにアクセスできることを確認しました。この後、いくつかの処理を書いていきましたが、http requestノードの 認証情報をノードを作るたびに入力しないといけない ことに気づきました。複雑な処理を書き始めると破綻することは目に見えていたので、まずは認証情報の一元管理化とリクエスト送信の共通処理のくくりだしを行いました。

認証情報の一元管理

まずは認証情報の一元管理です。そもそもhttp requestノードの認証情報を含む外部に漏れては困るような情報は、ノードをコピーしたときにはコピーされない上に、フローを書き出したときも保存されないようになっています。書き出したときにも認証情報が保存されないので、フローを共有したときに誤って認証情報も共有してしまうという事故が起きなくなります。

代替手段として、changeノードを使って変数に設定すれば一元管理自体は実現できますが、この設定情報は書き出し時にも保存されてしまい危険です。

このような問題に対応するために、サードパーティ製のNode-REDノード拡張 node-red-contrib-credentials を使います。 1

node-red-contrib-credentialsには、 credentials ノードが含まれており、認証情報などの漏れては困る情報を保持するために使えます。

credentialsノードは、changeノードと同様に各種変数に値を設定することができますが、設定値はノードのコピーや書き出し時に出力されなくなります。 今回は、credentialsノードにNature Cloud APIのアクセストークンを設定して使います。

まずは node-red-contrib-credentials を追加します。 パレットの管理 メニューからパレットの設定画面を開いて、 ノードを追加 タブを開きます。 検索入力に node-red-contrib-credentials と入力して、リストに出てきた node-red-contrib-credentials を選んで ノードを追加 ボタンを押します。

しばらくするとダウンロードが完了して、 現在のノード タブに node-red-contrib-credentials が現れます。

node-red-contrib-credentialsを追加

次に、ストレージグループに追加された credentials ノードを追加し、編集画面を開きます。 Values の下の 追加 ボタンをおして値を追加し、 private の横のドロップダウンリストから *** を選択し、Nature Cloud APIのアクセストークンを入力します。 また、 to の横のドロップダウンリストから msg を選択し、accessToken を入力します。

credentialsノードの追加

これで、credentialsノードが実行されたときに msg.accessToken にアクセストークンの文字列が設定されるようになります。あとは msg.accessToken に含まれるアクセストークンを http requestノードに渡すだけです。

但し、その前にhttp requestノードで扱えるように、アクセストークンの前に Bearer (末尾のスペース含む) を付加する必要があります。このために新たにchangeノードを以下の通り追加します。

Bearer を追加するchangeノードを追加

changeノードでは値の代入のほかに追加・削除といった操作ができますが、今回はデフォルトの値の代入操作を使って、 msg.accessToken の内容に Bearer を付加して再代入します。 内容の変換にはJSONata式を使うので、対象の値の形式から JSONata形式 を選択します。 "Bearer " & accessToken というJSONata式は、単に文字列 "Bearer "msg.accessToken を連結するという意味です。

これで、msg.accessToken の内容は Bearer (アクセストークン) になりましたので、最後にhttp requestノードで msg.accessToken を使うように変更します。

http requestノードをaccessToken対応に変更

前回有効にした 認証を使用チェックボックスからチェックを外します。代わりに ヘッダ リストにヘッダを追加して Authorization に対して msg.accessToken の内容を入れるように設定します。これで、HTTPリクエスト送信時に、 Authorization: Bearer (アクセストークン) の形式でヘッダが追加され、Nature Cloud APIサーバーの認証を行うことができます。

デプロイして試しに実行すると、アプライアンス一覧が取得できるのを確認できます。

ここまででhttp requestの外のノードから認証情報を設定できることを確認したので、最後に認証情報を現在のフロー全体に適用するようにします。

まず、credentialsノードとBearerを付加するchangeノードをhttp requestのグラフから分離し、新たに別系統のグラフを作成します。

グラフを2系統に分離

新たに追加したinjectノードの名前を "load Access Token" に変更し、 Node-RED起動の 0.1秒後、以下を行う にチェックを入れます。

injectノードの自動起動設定

次に、Bearerを付加するchangeノードを編集し、設定先を msg.accessToken から flow.accessToken に変更します。これでアクセストークンの設定値が現在のフロー全体の設定値となります。

Bearer付加後の設定先をflow.accessTokenに変更

最後に、http requestを行う側のグラフに新たにchangeノードを追加し、 flow.accessToken の内容を msg.accessToken にコピーするようにします。これはhttp requestノードがヘッダの値の設定元として msg しか取れない対策として必要です。

flow.accessTokenの値をmsg.accessTokenに代入

これで再度デプロイしてみると、先ほどと同様にアプライアンス一覧が取得できるのを確認できるはずです。 出来ることは変わっていませんが、他のAPIにアクセスするときも inject accessToken のchangeノードとhttp requestを一緒にコピーすれば、認証情報の取得処理を使いまわすことができます。

Nature Remo APIのリクエスト処理をまとめる

ここまでで認証情報の一元管理ができるようになったので、Cloud APIのアクセス処理のノード2つをまとめて サブフロー にして、ついでにいくつか便利機能をつけておきます。

まず、inject accessToken の名前のchangeノードと、後続の http request ノードの2つを選択し、メニューから 選択部分をサブフロー化 を選びます。

2つのノードをサブフローに変換

選択していた2つのノードが Subflow1 という名前のサブフローに置き換わるので、ダブルクリックして編集画面を出して、 サブフローのテンプレートの編集 を押します。

テンプレートを編集する

サブフローのタブに移動するので、タブの左上の プロパティを編集 を押して、 サブフローのテンプレートを編集 画面を表示し、テンプレートの名前を Remo API Request に変更しておきます。

サブフローの名前をRemo API Requestに変更

これでひと段落…とおもいきや、このままではうまく動きません。実はサブフローからは親のフローの flow.xxx の変数にそのままアクセスできません。 親のフローの変数にアクセスするには、 flow.$parent.xxx と書く必要があるので、 inject accessToken ノードを編集して、 flow.$parent.accessToken の値を引っ張ってくるようにします。

inject accessTokenの対象の値を $parent.accesToken に変更

ここまででデプロイして実行してみます。アプライアンス一覧は変わらず取得できているはずです。

現在のところアクセス先のNature Cloud APIのエンドポイントは https://api.nature.global/1/appliances 固定ですが、他のAPIにアクセスするときも使いたいので、外部から与えられるようにします。 http requestノードはURL欄を空欄にしておくことにより、 msg.url からアクセス先のエンドポイントのURLを取得するようになります。またHTTPメソッド (GETやPOST) も設定を変更して msg.method から取得するようにできます。

まずはサブフロー内のhttp requestを編集して、メソッドを msg.methodに定義 に変更、URLを空欄に変更します。

サブフローのhttp requestノードのURLとmethodを編集

元のフローにもどり、injectノードのプロパティに msg.method = GETmsg.url = https://api.nature.global/1/appliances を追加します。

injectノードにmsg.methodとmsg.urlを設定する

ここまでで一旦デプロイして、アプライアンスの取得ができることを確認します。

目的のアプライアンスの抽出

ここまででNature Remo APIへのアクセスは比較的簡単にできるようになってので、制御したい家電の情報を抽出してみます。

appliances に家電に対応する情報が含まれているので、これを使えばいいと思われますが、複数ホームに所属している場合に問題が起きるので、もう少し真面目に対応してみます。

Nature Remoに登録されている赤外線制御可能な家電は、それぞれ1つのNature Remoデバイスに結び付いています。

/1/appliances 等で取得できる各家電に対応するアプライアンス情報には、ユーザーが登録時につけた名前が nickname として含まれていますが、この nickname はホームの中では重複しないようになっていますが、異なるホームには同じ nickname の家電がある可能性があります。 例えば、「照明」という nickname の家電を検索すると、筆者の環境だと自宅の「照明」だけでなく、実家の「照明」もひっかかってしまいます。2

そこで、まずは対象のNature Remoデバイスを探し出してみます。

まずは /1/devices にアクセスしてデバイス一覧を取得します。 appliancesにアクセスするノード3つをコピーして、devices用のノードを作ります。 コピーしたinjectノードの msg.urlhttps://api.nature.global/1/appliances から https://api.nature.global/1/devices に変更します。

appliancesをコピーしてdevicesにアクセスするノードを作成

デプロイしてdevicesのinjectノードを実行すると、デバッグメッセージとしてデバイス一覧が出てきます。

devices の中には操作したい家電が紐づいているデバイスがあるはずです。例えば筆者の自宅においてあるNature Remo3は 自宅Remo3 という名前ですが、devicesの結果の中に含まれており、 id フィールドにデバイスを表すIDの文字列が含まれています。

devicesの中身を確認

このデバイス一覧から指定した名前とユーザー名を持つデバイスを抽出するために、新たにchangeノードとdebugノードを追加します。

バイス抽出用ノードを追加

changeノードのJSONataの式は以下の内容です。ここで name="自宅のRemo3" や、 users[ の後の nickname="Kenta Ida" の部分は、それぞれ対象のRemoの名前や、ご自身のユーザー名に置き換えてください。式の内容は、 msg.payload に含まれる要素のうち、name フィールドの値が 自宅Remo3 で、users の中に nicknameKenta Ida かつ superusertrue の要素があるもののid` フィールドの値 という意味になっています。これで目的とするNature RemoデバイスのIDが取得できます。

payload[
    name="自宅Remo3" 
    and 
    users[
        nickname="Kenta Ida" 
        and 
        superuser=true
    ]
].id

入力したJSONata式が正しいかどうかは、debugノードの出力をコピーして、JSONata式エディタのテストタブに張り付けると確認できます。

入力したJSONata式の条件が正しいかどうかのテスト方法

あとはdebugノードの設定を変更して、 debugノードのステータスにデバイスIDが表示されるようにしておくと便利です。

debugノードのステータスにデバイスIDを表示する

さて、ここで取得できたのはデバイス (Nature Remo)の デバイスIDです。ここからさらにデバイスに紐づく家電の情報を抽出し操作します。

まずはNature Cloud API/1/devices/{deviceId}/appliances をつかってNature Remoデバイスに紐づく家電を取得するために、URLの文字列を構築します。 例えばRemoのデバイスIDが aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee の場合、 /1/devices/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/appliances となります。 先ほど作成したデバイスID抽出処理にさらにchangeノードを連結して上記の形式のURLを構築し、再度リクエストを投げるようにします。

URLを構築してリクエストを投げる

changeノードで msg.url に対してJSONata式 "https://api.nature.global/1/devices/" & payload & "/appliances" を設定します。 & はJSONata式で文字列の連結を行う演算子なので、 https://api.nature.global/1/devices/msg.payload の内容と /appliances が連結された文字列となります。

changeノードでURLを構築

デプロイしてdevicesのインジェクトを実行すると、debug3の出力結果として家電に対応する情報が取得できます。

debug3の出力結果に含まれる家電情報

ここからさらに nickname="照明" の家電情報をさきほどデバイス情報を抽出したときと同様にchangeノードを使って抽出し、フロー全体の変数 flow.lightApplianceId として保存しておきます。抽出のJSONata式は、対象の照明家電の名前が 照明 の場合は次の通りです。

payload[
    nickname="照明" 
].id

flow.lightApplianceIdに家電情報のIDを保存

照明のON/OFFをしてみる

これで制御対象の家電のIDが flow.lightApplianceId に入っている状態になったので、ようやく照明のON/OFFを試します。

照明の制御には、 /1/appliances/{applianceid}/light のエンドポイントに対して button パラメータに押したいボタンの名前 ( onoff ) を入れてPOSTします。 このとき、パラメータの送信フォーマットは application/x-www-form-urlencoded にする必要があります。これはhttp requestノードでヘッダに Content-Type: application/x-www-form-urlencoded を付加するように設定すれば可能です。

そこでまずはRemo APIアクセス用のサブフローを変更し、POST時に送信フォーマットを切り替えるようにします。 サブフロー内のchangeノードを編集し、ルールを一旦すべて削除してから msg.headers にJSONata式の値を設定するように変更します。

JSONata式の内容は次の通りです。 Authorization には flow.$parent.accessToken に入っている認証情報を設定します。Node-REDのJSONata式では $flowContext 関数を使って flow にアクセスできます。 flow.$parent.accessToken の内容を取得するには、 $flowContext("$parent.accessToken") とします。

Content-Type には msg.methodPOST の場合は application/x-www-form-urlencoded それ以外の場合は application/json を指定します。 JSONata式では ?:三項演算子による条件分岐がつかえるので、目的の式は (method = 'POST' ? 'application/x-www-form-urlencoded' : 'application/json') と表すことができます。

{
    "Authorization": ($flowContext("$parent.accessToken")),
    "Content-Type": (method = 'POST' ? 'application/x-www-form-urlencoded' : 'application/json')
}

サブフローのchangeノードでmsg.headersを設定

また、Authorization を含むHTTPヘッダを外部から設定するように変更したので、http requestノードのヘッダのリストを空欄にしておきます。

http requestのヘッダを空欄にする

最後にちょっとした便利機能として、Nature Remo Cloud APIの使用回数制限の状況を、サブフローのステータスとして表示しておきます。

追加したchangeノードのJSONata式は以下の通りです。 msg.statusCode はHTTPリクエストに対するレスポンスのHTTPステータスコードが入っています。また、レスポンスヘッダの x-rate-limit-remaining および x-rate-limit-limit にはそれぞれNature Remo Cloud APIの現在の残APIリクエスト可能回数およびAPIリクエスト可能回数の上限が含まれています。 これらの値をすべて文字列として連結して、ステータスの表示内容として返します。

statusCode & " " & headers.`x-rate-limit-remaining` & "/" & headers.`x-rate-limit-limit`

サブフローに便利機能を追加

さて、いよいよ本題の照明をON/OFFするリクエストを送信する部分です。

まず照明ON/OFFを実行するためのinjectノードを追加します。それぞれ msg.payload{"button": "on"}{"button": "off"}JSON を設定するようにします。値の種別はデフォルトで文字列なので、JSONに変更するのを忘れないようにします。

次にURL構築用のchangeノードを追加します。 msg.method にはPOST を設定します。また msg.url は以下のJSONata式で、 https://api.nature.global/1/appliances/{applianceid}/light のURLを構築します。

"https://api.nature.global/1/appliances/" & $flowContext("lightApplianceId") & "/light"

最後にRemo API Requestサブフローのノードとdebugノードをコピペで作って接続すれば完了です。

照明をON/OFFするノードを追加

デプロイして 照明OFF injectノードを実行すると、お部屋の照明が消灯するはずです。逆に 照明ON ノードを実行すると照明が点灯します。 オマケで実装したNature Remo Cloud APIの動作状況ステータスもきちんと出ています。

Nature Remo Cloud APIの動作状況ステータス

これでNode-REDから照明の操作を手動で行えることが確認できました。

つづく

次回はエアコンのオートメーションを試してみます。 その3へ続く

Nature開発者Community

Natureのプロダクトに関する開発者向けの情報を交換する場として、 Nature開発者コミュニティ をDiscord上に立ち上げています。 今回の話題であるNature Cloud APIに関する話をする apiチャネル もありますので、ご興味がある方はぜひご確認いただければと思います。

discord.com


  1. 拡張のソースコード https://github.com/Steveorevo/node-red-contrib-credentials/tree/master サードパーティ製の拡張ですので、内容を確認したうえでお使いください。
  2. 実際にはNatureのメンバーは開発用ホームに入ってたりするので、もっといろいろ引っかかります…