NatureRemoでテレビをマクロ操作しようとした話

#はじめに

この半年ほど、TVデバイス向けのアプリ開発をしていました。
開発にあたっては当然リモコンで操作をするわけですが、
設定変更やテストをするにあたって、リモコンからの入力をPCから、
さらにはマクロ化できないかと思い試行した結果をまとめました。

#NatureRemo

TVを操作するには赤外線を送出するデバイスが必要です。
Obnizあたりで作成を試みたのですが、InfraredLEDが信号を出してくれなかったので
敢え無く挫折...
結局市販のNatureRemoを使用することになりました。

https://www.youtube.com/watch?v=mz6wDhFo7QE

*なお今回は廉価版のNatureRemoMiniを使用しています。

NatureRemoはAPIを公開しており、
https://developer.nature.global/

普通に使う分にはCloud API(http://swagger.nature.global/)
を利用してsignalsをPostすればOKです

<参考>
https://qiita.com/sohsatoh/items/b710ab3fa05e77ab2b0a

ところが、cloudAPIにはLimitationがあるため

>サーバは 5分以内に30回 以上のリクエストを受けるとその後ステータスコード 429 を返す。

マクロに使うには適さなそうです。
(障害もあるし)

ということでもう一つのAPI 
LocalAPI(http://local.swagger.nature.global/ )を使います

# IRKit
LocalAPIには
>GET messages
>POST messages

の二つのAPIがあります。
パッとこれだけ置かれているので使い方がわからなかったのですが、

実装の元であるIRKitに書いてありました。
http://getirkit.com/

Localからテレビに信号を送る流れは以下のようになります。

1. NatureRemoのIPを調べる
   公式サイトを参考にIPを調べます
   https://nature.global/jp/faq/043

2. リモコンボタンのデータを調べる
   messagesAPI(GET)を叩いて、すかさずリモコンの任意のボタンを押します。
   terminalに送信データが表示されるのでメモります。

3. データ送信
   messagesAPI(POST)に送信データをつけて叩きます。

# マクロ化する

マクロ化するために、ボタンとテレビ信号の対応表を作ります。
NatureRemoにリモコンを登録している場合は、前述のcloudAPIで
>/1/appliances

を叩くと
こんな感じでデバイスごとのボタン一覧を返してくれます

```appliances.json
  "type": "TV",
  "nickname": "TV_1",
  "image": "ico_tv",
  "settings": null,
  "aircon": null,
  "signals": [],
  "tv": {
    "buttons": [{
      "name": "power",
      "image": "ico_io",
      "label": "power"
    }, {
      "name": "select-input-src",
      "image": "ico_input",
      "label": "source"
    }, {
      "name": "tv-schedule",
      "image": "ico_tv_guide",
      "label": "schedule"
    }, {
      "name": "mute",
      "image": "ico_mute",
      "label": "mute"
    }, {
      "name": "ch-1",
      "image": "ico_number_1",
      "label": "1"
    }, {
      "name": "ch-2",
      "image": "ico_number_2",
      "label": "2"
    }
  }
//続く...
```

このデータに信号データも入っていてくれれば話は早いのですが、
存在しないようなのでLoaclAPIで一つ一つデータを調べ... 

```tv_1.json
{
"power":{"format":"us","freq":36,"data":[3450,1800,392,486,394,1359,394,481,386,488,384,486,386,487,392,478,395,481,394,478,392,481,393,480,390,482,385,486,386,1366,391,480,393,481,391,482,395,475,387,487,394,477,386,487,392,478,397,477,394,1359,397,474,395,478,394,477,397,476,398,478,395,476,393,481,392,476,395,1358,395,482,388,1365,399,1354,399,1357,394,1356,394,485,417,461,394,1358,397,478,396,1359,391,1356,395,1357,393,1360,397,477,396,1357,396,65535,0,9272,3458,1795,399,483,394,1367,409,461,392,478,393,479,396,479,393,476,396,478,394,476,396,478,394,482,392,477,398,476,393,1354,396,482,391,480,393,479,394,478,393,476,396,479,393,481,393,480,394,477,393,1356,396,477,396,478,394,476,399,477,392,476,399,476,397,477,394,477,395,1357,393,486,396,1360,385,1365,395,1357,393,1355,395,486,395,481,388,1369,396,479,384,1364,394,1361,384,1364,395,1361,387,487,385,1363,395,65535,0,9279,3456,1801,391,478,394,1358,393,481,390,480,394,478,394,477,395,479,396,480,391,476,396,479,395,475,395,479,394,480,384,1367,393,483,395,483,396,483,396,482,394,484,388,491,396,483,386,495,394,482,397,1363,394,473,399,476,386,488,393,479,392,479,395,475,398,475,397,480,392,1370,380,481,391,1359,394,1356,394,1361,399,1356,391,477,395,478,397,1352,398,477,396,1355,397,1356,398,1355,396,1356,394,483,389,1357,396]},
"ch-1":{"format":"us","freq":36,"data":[3450,1802,396,478,394,1355,394,488,392,483,396,484,395,484,392,486,393,484,392,488,393,484,385,494,385,494,382,500,389,1360,396,479,394,476,394,482,392,481,391,478,385,489,394,477,394,482,389,481,392,1361,383,1367,392,479,393,481,391,1361,392,483,396,484,385,491,388,493,392,486,393,484,395,484,393,487,392,1363,392,482,390,1361,392,482,390,1356,387,489,392,480,395,1357,384,1369,393,481,395,1354,396,1357,393]},
"ch-2":{"format":"us","freq":36,"data":[3454,1797,386,492,386,1370,393,477,397,477,396,478,392,480,397,475,394,476,396,478,387,487,394,477,392,481,385,485,396,1356,394,482,392,476,394,481,393,477,397,476,394,482,391,478,394,480,393,476,398,1357,394,1357,391,488,391,486,392,1366,391,478,396,481,382,485,394,481,394,1354,399,477,385,489,383,485,387,1366,392,480,393,1358,394,483,388,491,394,485,395,484,394,1358,389,1364,399,477,392,1357,397,1354,394,65535,0,9734,3448,1806,395,475,395,1358,392,486,395,484,392,487,393,485,392,487,385,493,392,489,391,485,392,490,390,485,385,495,392,1359,398,477,389,484,394,476,394,483,382,485,396,479,385,491,382,485,386,491,389,1363,389,1357,387,489,383,485,394,1361,391,488,390,486,393,485,392,489,392,1365,392,478,394,478,396,474,394,1362,392,479,394,1354,387,487,384,486,399,476,395,478,392,1357,386,1371,386,487,384,1365,396,1357,394]},
続く...
```
buttonのnameをkeyにdataを紐付けたjsonを作ります。


あとはこんな感じでシナリオ書いて順々にsendするようにすればOKです
(適正なsend_spanがわからないので適当ですが)

```send.js
var fs = require('fs');
var tv_1 = JSON.parse(fs.readFileSync('tv_1.json', {
    encoding: 'utf-8'
}));
var webclient = require("request");

const SEND_SPAN = 300;

const senario = ['power', 'cha-1', 'vol-up', 'vol-up', 'vol-up', 'vol-up', 'vol-up', 'vol-up', 'vol-up'];
doSenario(0, senario);

function doSenario(i = 0, list){
  if (list.length - 1 < i) return;
  sendData(list[i]);

  setTimeout(()=>{
    doSenario(i + 1, list);
  }, SEND_SPAN);
}

function sendData(key){
  const data = tv_1[key];
  console.log("データ", data);
  webclient.post({
    url: "http://hogehoge/messages",
    headers: {
      "content-type": "application/json",
      "X-Requested-With":'',
    },
    body: JSON.stringify(data)
  }, function (error, response, body){
    if(error){
     console.log("エラー", error);
    }
  });
}
```

# まとめ
localAPIを使えばマクロ的にNatureRemoを操作することができました。

しかし、万が一シナリオ実行中に何らかの原因で一部信号が届かなかった場合、
その信号を無視してシナリオが実行されるため操作がめちゃくちゃになってしまいます。

NatureRemo単体ではテレビが現在どういったステータスなのかを把握することが
できないため、画像解析等のアプローチを試していきたいと思います。