ファームウェアエンジニアの井田です。前回 に引き続き、Node-RED でNature Cloud APIを触るお話です。
Node-REDのフローのデータ
本記事で使用しているNode-REDのフローのデータはこちらのリポジトリに置いてありますので、適宜参照してください。
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 が現れます。
次に、ストレージグループに追加された credentials ノードを追加し、編集画面を開きます。
Values の下の 追加 ボタンをおして値を追加し、 private の横のドロップダウンリストから ***
を選択し、Nature Cloud APIのアクセストークンを入力します。
また、 to の横のドロップダウンリストから msg
を選択し、accessToken
を入力します。
これで、credentialsノードが実行されたときに msg.accessToken
にアクセストークンの文字列が設定されるようになります。あとは msg.accessToken
に含まれるアクセストークンを http requestノードに渡すだけです。
但し、その前にhttp requestノードで扱えるように、アクセストークンの前に Bearer
(末尾のスペース含む) を付加する必要があります。このために新たにchangeノードを以下の通り追加します。
changeノードでは値の代入のほかに追加・削除といった操作ができますが、今回はデフォルトの値の代入操作を使って、 msg.accessToken
の内容に Bearer
を付加して再代入します。
内容の変換にはJSONata式を使うので、対象の値の形式から JSONata形式 を選択します。 "Bearer " & accessToken
というJSONata式は、単に文字列 "Bearer "
と msg.accessToken
を連結するという意味です。
これで、msg.accessToken
の内容は Bearer (アクセストークン)
になりましたので、最後にhttp requestノードで msg.accessToken
を使うように変更します。
前回有効にした 認証を使用 のチェックボックスからチェックを外します。代わりに ヘッダ リストにヘッダを追加して Authorization に対して msg.accessToken
の内容を入れるように設定します。これで、HTTPリクエスト送信時に、 Authorization: Bearer (アクセストークン) の形式でヘッダが追加され、Nature Cloud APIサーバーの認証を行うことができます。
デプロイして試しに実行すると、アプライアンス一覧が取得できるのを確認できます。
ここまででhttp requestの外のノードから認証情報を設定できることを確認したので、最後に認証情報を現在のフロー全体に適用するようにします。
まず、credentialsノードとBearerを付加するchangeノードをhttp requestのグラフから分離し、新たに別系統のグラフを作成します。
新たに追加したinjectノードの名前を "load Access Token" に変更し、 Node-RED起動の 0.1秒後、以下を行う にチェックを入れます。
次に、Bearerを付加するchangeノードを編集し、設定先を msg.accessToken
から flow.accessToken
に変更します。これでアクセストークンの設定値が現在のフロー全体の設定値となります。
最後に、http requestを行う側のグラフに新たにchangeノードを追加し、 flow.accessToken
の内容を msg.accessToken
にコピーするようにします。これはhttp requestノードがヘッダの値の設定元として msg
しか取れない対策として必要です。
これで再度デプロイしてみると、先ほどと同様にアプライアンス一覧が取得できるのを確認できるはずです。 出来ることは変わっていませんが、他のAPIにアクセスするときも inject accessToken のchangeノードとhttp requestを一緒にコピーすれば、認証情報の取得処理を使いまわすことができます。
Nature Remo APIのリクエスト処理をまとめる
ここまでで認証情報の一元管理ができるようになったので、Cloud APIのアクセス処理のノード2つをまとめて サブフロー にして、ついでにいくつか便利機能をつけておきます。
まず、inject accessToken の名前のchangeノードと、後続の http request ノードの2つを選択し、メニューから 選択部分をサブフロー化 を選びます。
選択していた2つのノードが Subflow1 という名前のサブフローに置き換わるので、ダブルクリックして編集画面を出して、 サブフローのテンプレートの編集 を押します。
サブフローのタブに移動するので、タブの左上の プロパティを編集 を押して、 サブフローのテンプレートを編集 画面を表示し、テンプレートの名前を Remo API Request に変更しておきます。
これでひと段落…とおもいきや、このままではうまく動きません。実はサブフローからは親のフローの flow.xxx
の変数にそのままアクセスできません。
親のフローの変数にアクセスするには、 flow.$parent.xxx
と書く必要があるので、 inject accessToken ノードを編集して、 flow.$parent.accessToken
の値を引っ張ってくるようにします。
ここまででデプロイして実行してみます。アプライアンス一覧は変わらず取得できているはずです。
現在のところアクセス先の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を空欄に変更します。
元のフローにもどり、injectノードのプロパティに msg.method
= GET
と msg.url
= https://api.nature.global/1/appliances
を追加します。
ここまでで一旦デプロイして、アプライアンスの取得ができることを確認します。
目的のアプライアンスの抽出
ここまでで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.url
を https://api.nature.global/1/appliances
から https://api.nature.global/1/devices
に変更します。
デプロイしてdevicesのinjectノードを実行すると、デバッグメッセージとしてデバイス一覧が出てきます。
devices
の中には操作したい家電が紐づいているデバイスがあるはずです。例えば筆者の自宅においてあるNature Remo3は 自宅Remo3
という名前ですが、devicesの結果の中に含まれており、 id
フィールドにデバイスを表すIDの文字列が含まれています。
このデバイス一覧から指定した名前とユーザー名を持つデバイスを抽出するために、新たにchangeノードとdebugノードを追加します。
changeノードのJSONataの式は以下の内容です。ここで name="自宅のRemo3"
や、 users[
の後の nickname="Kenta Ida"
の部分は、それぞれ対象のRemoの名前や、ご自身のユーザー名に置き換えてください。式の内容は、 msg.payload
に含まれる要素のうち、name
フィールドの値が 自宅Remo3
で、users
の中に nickname
が Kenta Ida
かつ superuser
が true の要素があるものの
id` フィールドの値 という意味になっています。これで目的とするNature RemoデバイスのIDが取得できます。
payload[ name="自宅Remo3" and users[ nickname="Kenta Ida" and superuser=true ] ].id
入力したJSONata式が正しいかどうかは、debugノードの出力をコピーして、JSONata式エディタのテストタブに張り付けると確認できます。
あとはdebugノードの設定を変更して、 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を構築し、再度リクエストを投げるようにします。
changeノードで msg.url
に対してJSONata式 "https://api.nature.global/1/devices/" & payload & "/appliances"
を設定します。 &
はJSONata式で文字列の連結を行う演算子なので、 https://api.nature.global/1/devices/
と msg.payload
の内容と /appliances
が連結された文字列となります。
デプロイしてdevicesのインジェクトを実行すると、debug3の出力結果として家電に対応する情報が取得できます。
ここからさらに nickname="照明"
の家電情報をさきほどデバイス情報を抽出したときと同様にchangeノードを使って抽出し、フロー全体の変数 flow.lightApplianceId
として保存しておきます。抽出のJSONata式は、対象の照明家電の名前が 照明
の場合は次の通りです。
payload[ nickname="照明" ].id
照明のON/OFFをしてみる
これで制御対象の家電のIDが flow.lightApplianceId
に入っている状態になったので、ようやく照明のON/OFFを試します。
照明の制御には、 /1/appliances/{applianceid}/light
のエンドポイントに対して button
パラメータに押したいボタンの名前 ( on
や off
) を入れて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.method
が POST
の場合は 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') }
また、Authorization
を含むHTTPヘッダを外部から設定するように変更したので、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ノードをコピペで作って接続すれば完了です。
デプロイして 照明OFF injectノードを実行すると、お部屋の照明が消灯するはずです。逆に 照明ON ノードを実行すると照明が点灯します。 オマケで実装したNature Remo Cloud APIの動作状況ステータスもきちんと出ています。
これでNode-REDから照明の操作を手動で行えることが確認できました。
つづく
次回はエアコンのオートメーションを試してみます。 その3へ続く
Nature開発者Community
Natureのプロダクトに関する開発者向けの情報を交換する場として、 Nature開発者コミュニティ をDiscord上に立ち上げています。 今回の話題であるNature Cloud APIに関する話をする apiチャネル もありますので、ご興味がある方はぜひご確認いただければと思います。
- 拡張のソースコード https://github.com/Steveorevo/node-red-contrib-credentials/tree/master サードパーティ製の拡張ですので、内容を確認したうえでお使いください。↩
- 実際にはNatureのメンバーは開発用ホームに入ってたりするので、もっといろいろ引っかかります…↩