ちょっと古いBraviaもHey! Siriでチャンネルを変えたりできるようにする。

##はじめに
ほんの少し前のことなんですが。
長年使ってたテレビが遂に壊れてしまったんですよ。地デジが始まる頃に買った『KDL-40X5000』って言うソニーのテレビ。
テレビが映らないのは困るなあってんで、すぐに買いに行ったんです。そしたらこの10年ちょっとで、テレビってえらい進化しているんですね。薄く軽くなったのはもちろん、ベゼルが小さくなったんですよ。なので、設置面積はほとんど変わらずだいぶ大きなテレビを置けました。
今回買ったのは**『KJ-49X8500F』**です。
Android TVなんですねー。
YouTubeとか見られるんですねー。
あこがれの4Kですよー。
画が綺麗になりましたねー。
画面も大きくなりましたよー。
と大満足。

そこにニュースが。
「[ソニーのAndroid TVがAirPlay 2とHomeKitをサポート](https://japanese.engadget.com/jp-2019-12-04-android-tv-airplay-2-homekit.html?guccounter=1&guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&guce_referrer_sig=AQAAAHsEV-mL9LOTzB-CLQWdcICOpvSSkZ8EshTGqhCCYejBJrXkXfY_GtmfzV9n-jNYLwHFBoDhdGZ044d2qmmOitSIPYNZ7OkElCI0JtRVnVf83Mg95-1tEOTmT8jxcTVD1rIeF6BnwlfhO2J6gCORtbOvzYl5Y2ZEaMwwGlmEpu5F)」（engadget）
やったー！テレビがSiriで動かせるぞ！
**ん？**
>アップデートが提供されているのは、2018年モデルのZ9FとA9Fシリーズ、2019年モデルのZ9G、A9G、X950G、X850Gシリーズ。

Gシリーズ？
:tired_face::tired_face::tired_face: **僕の「F」じゃん！ ほんのちょっと前に買ったのに対象外じゃん！**

前置きが長くなりました。
たった一世代で泣いたことがすごく悔しかったので、自力でSiriで操作できるようにしました。
たぶんAndoroid TVの機種であれば、さらに前の機種でも同じだと思います。

##この記事でやることとセールスポイント
+ Raspberry PiにHomebridgeを入れ、braviaプラグインで操作可能にします。
+ homebridge-braviaプラグインのReadMeには書かれていない日本国内のチャンネルの選択方法を記載します。
+ それだけだと出来ないSiriによるチャンネル変更も（力業で）実現します。 
+ BRAVIA（テレビ）が持っているAPIを利用します。赤外線スマートリモコンとかは使いません。

##今回登場する…
###ハードウェア
これらはすべて同じLANに繋がっている必要があります。

+ Raspberry Pi Zero WH （ずっと前に買って転がってた）
 + Raspbian buster
+ Sony Bravia KJ-49X8500F
 + Android 8.0.0にアップデート済み
+ Homeアプリが使えるiOS / iPadOS / macOSどれかの搭載機

###ソフトウェアやら
+ node.js
 + homebridge（nodeModule）
     + homebridge-config-ui-x
     + homebridge-bravia
     + homebridge-cmd4
 + Express（nodeModule）
 + bravia（nodeModule）
 + node-cron（nodeModule）
+ bash
+ jq

とりあえずRaspberry PiのOSなどはインストール済みで、LANに接続しているモノとします。
この記事では **pizero.local** で接続出来るようになっている、と仮定しますね。

##Pi Zeroに最新のnode.jsを入れる
RaspberryPi Zero のCPU は ARMv6 のため、node.js公式からはv11以降、バイナリが配布されていません。現時点での最新はv14.8.0。LTS版はv12系。後述のhomebridgeでもLTS版を推奨していますが、バイナリの配布はARMv7とARMv8だけです。
とは言えbuildするのもめんどうなので、アンオフィシャルビルドから入れちゃいます。
正直ちょっと古くても大丈夫ですが、node.jsはバージョン変更のトラブルが多いので、v12系の方が安心かと。
まあ、趣味の範囲です。

```bash
$ sudo npm install -g n #バージョン管理ツールのnを入れます。
$ sudo n latest #最新のnodeを入れてみます。
$ node -v
v11.15.0 #ちょっと古いのが入りました。 
$ sudo -i
#ミラー先をアンオフィシャルに変更するために環境変数をセットします。
$ export N_NODE_MIRROR=https://unofficial-builds.nodejs.org/download/release/
$ sudo -E n latest #いまセットした環境変数を使って、試しにnode最新版を入れてみます。
$ node -v
v14.8.0  #入りました。今度はちょっと新しすぎます。
$ sudo -E n 12 #12系の中で最新のものをインストールします。
  installing : node-v12.18.3
       mkdir : /usr/local/n/versions/node/12.18.3
       fetch : https://unofficial-builds.nodejs.org/download/release/v12.18.3/node-v12.18.3-linux-armv6l.tar.xz
   installed : v12.18.3 (with npm 6.14.6)
$ node -v
v12.18.3  #LTSバージョンが入りました。 
# どのバージョンがLTSかわからなかったら n lts でも大丈夫。
```
##homebridge および homebridge-config-ui-xを入れる
homebridgeは入っている前提で良いかもしれませんが、簡単に記載します。
また、homebridge-config-ui-xはhomebridgeの設定をWebブラウザから出来るようになるプラグインです。便利なので標準のつもりで入れています。
詳しくはこちら。
[Homebridge & Systemd (Raspbian, Ubuntu, Debian)](https://github.com/oznu/homebridge-config-ui-x/wiki/Homebridge-&-Systemd-(Raspbian,-Ubuntu,-Debian))

```bash
$ sudo npm install -g --unsafe-perm homebridge homebridge-config-ui-x
$ sudo hb-service install --user homebridge
```

これでOKです。
homebridge-config-ui-xとhb-service（httpサーバ）の組み合わせで起動すると、
`http://pizero.local:8581/`
でオシャレ設定画面にアクセス出来ます。
![スクリーンショット 2020-08-18 16.58.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132814/c066ebce-dbc8-f99e-cd60-ec02eea0e842.png)
初期アカウントとパスワードは両方ともadminです。

##プラグイン：homebridge-braviaを入れる
homebridgeにはBraviaを操作するプラグインが複数あります。
今回はそのものズバリな名前の「homebridge-bravia」を使ってみます。

これはhomebridge-config-uiで簡単に入れられます。
プラグインのタブでbraviaを検索。「homebridge-bravia」を見つけたら、インストールを押すだけです。
![スクリーンショット 2020-08-18 16.59.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132814/74f0ecba-b2e2-9185-faf8-1230e6107e64.png)

homebridge-braviaはconfig-uiに対応しているので、主な設定は設定UIで可能です。
とりあえず[設定]を押して設定してみて下さい。
configはあとで手で修正する必要があるのですが、とりあえず__ひな形__が出来るので便利です。

**Name** はSiriに呼びかける名前になるので注意して下さい。
ここで「おばあちゃん」とかわかりにくい名前を付けると
「Hey siri, おばあちゃんを消して」とか言う必要があります。家族がギョッとします。
僕は無難にテレビにしておきました。
**IP / Hostname** は テレビのものを。
Android.localでも大丈夫な気がしたんですが、どうも上手く行きませんでした。
これはいつか検証する必要があります。とりあえずIPアドレスで指定。
**TV Source** は後述。とりあえず適当に選択して下さい。
それ以外は特に注意点は無いので、デフォルト値か空欄でOKです。
**PIN Entry Server Port** と言うちょっと意味がわからないのがありますね。
これもデフォルトの8999のままにしておきます。あとで説明します。

保存したら、上部のメニューから「コンフィグ」を選んで__configファイルを直接編集__します。
###日本国内におけるTV Sourceの設定について
homebridge-braviaのREADMEには、TV Sourceについて以下のように記載があります。

| 入力 | 設定値 |
|:-:|:-:|
| アンテナ | tv:dvbt  |
| ケーブル |  tv:dvbc |
| 衛星  | tv:dvbs  |
これはヨーロッパなどで使われている規格です。日本国内ではこの設定は正しくありません。

| 入力 | 設定値 |
|:-:|:-:|
| 地デジ | tv:isdbt  |
| BS |  tv:isdbbs |
| CS |  tv:isdbcs |
とする必要があります。
詳しく知りたい方は [ISDB (wikipedia)](https://ja.wikipedia.org/wiki/ISDB)をご覧下さい。[^1] （北米では [^北米では]）
[^1]: BSはisdbsでなくisdb **b** s。BS放送とCS放送を区別するためにこう名付けたと思われます。
[^北米では]: 北米ではATSCのはずです。テレビ実機からの取得方法が後述されていますので、実際の値はそちらで取得して下さい


結果、config.jsonのbravia部分は、おおよそ以下のようになります。
tvsourceには地デジとBSを指定しました。僕はCS見てないので。
applicationsの部分はTVから起動出来るアプリです。
下の方にあるsourcesはhomeアプリ内に表示したい入力の一覧です。cecを指定しておくと、HDMIに繋がった機器の名称が並びます。HDMI-CECに対応していない機種がある場合はextInput:hdmiを指定することになります。その場合はHDMI-1などの番号で表示されます。

```config.json
        {
            "tvs": [
                {
                    "name": "テレビ",
                    "ip": "10.0.1.xx",
                    "externalaccessory": false,
                    "soundoutput": "speaker",
                    "tvsource": [
                        "tv:isdbt",
                        "tv:isdbbs"
                    ],
                    "applications": [
                        {
                            "title": "Prime"
                        },
                        {
                            "title": "YouTube"
                        }
                    ],
                    "sources": [
                        "tv:isdbt",
                        "tv:isdbbs",
                        "extInput:cec"
                    ]
                }
            ],
            "platform": "BraviaPlatform"
        },
```
ここまでで、**「Hey siri！ テレビを点けて！」が出来るようになりました！**
テレビのスイッチを付けてから、
右上にある「再起動」ボタンを押してhomebridgeを再起動しましょう！

###テレビの前へ急げ
再起動しましたか？
再起動すると、テレビの画面に数字が表示されるはずです。これはPINコード。この番号を知らない機械からの命令は受け付けないよ！と言う意味です。PINコードの有効期限は60秒！慌ててhomebridgeを設定しているPCに戻って下さい。
少し前に「デフォルトでOK」と書いた「 **PIN Entry Server Port** 」を憶えてますか？デフォルトのままだったら8999なので、
`http://pizero.local:8999/`
にアクセスして、さっき憶えたPINコードを入力して下さい。
間に合わなかったらもう一回homebridgeの再起動をやり直し！

###Hey siri！ テレビを点けて！
さて、無事に立ち上がったでしょうか？
iOSまたはmacOSで、homeアプリを立ち上げましょう。
テレビ（または おばあちゃん）の名前の付いたアイコンが表示されましたか？
iOSの場合は長押し（macOSの場合はコンテクストメニューから「コントロールを表示」）で、テレビを操作するためのUIが表示されるはずです。
![IMG_0044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132814/1bb8797d-93de-6b7f-d75d-a4714a011b80.png)


丸い大きなスイッチで、テレビのON/OFFが出来ます。ボタンを押してみて下さい。テレビが消えましたか？
次に、試しに口に出してみましょう **「Hey siri, テレビを点けて！」**

テレビが点きましたか？ おめでとうございます。

Homeアプリのスイッチの下には、チャンネルが選択出来るメニューが表示されます。
表示されませんか？
チャンネルの一覧を取得するのに少し時間が掛かるようです。
でも、少し待てば表示されます。

いくら待っても表示されない？
右下の歯車を押してみて下さい。
「アクセサリ」の欄に大きな数字があり、それを選ぶと中にチャンネルの名前のアイコンが並んでいますか？
これは**ダメな状態**。この状態だと、並んだチャンネルを選んでもなにも起きません。
どうやらこれは不具合なんじゃ無いかと思います。
[こちらにも報告されていますが、](https://github.com/normen/homebridge-bravia/issues/74)この状態になってしまったら、一度Homeアプリを再起動してみて下さい。
iOSのAppスイッチャーからスワイプして完全に消してしまい、もういちど起動します。
するとメニューの中に現れる（ことが多い）ようです。

###Hey siri！ フジテレビを点けて！
さて。メニューの中に無事チャンネルが現れたでしょうか？
地域にもよりますが、サブチャンネルまで含めて全部表示されるはずです。
メニューから放送局を変更すると、テレビのチャンネルが変わるはずです。やったね！
でも数が多いとちょっと変更するのシンドイですね。
では、口に出してみましょう。 **「Hey siri, フジテレビを点けて！」**

おや。
だめですね。
ダメですよね？

そうなんです。どうもsiriは、メニューの中にある項目を変更出来ないみたいなのです。
どうもこれは、iOSの（homekitの？homeアプリの？siriの？）仕様である様子。
これではせっかく声でテレビを点けても、結局リモコンを手に持つか、homeアプリを立ち上げて小さなメニューをくるくるしなければいけません。
ぐぬぬぬぬ。悔しい。
「Gシリーズ」ではそれが出来るのかどうかサッパリわからないけど、出来ないのは悔しい。。。

そんなわけで、なんとかして**音声でチャンネルが変えられるように挑戦**します。
テレビが点けば良いだけの場合は、ここで記事を読むのをやめることが出来ます。

##nodeでBraviaを操作する
homebridge-braviaでは、iPhoneのhomeアプリからテレビの電源オンオフと、チャンネルの変更が出来ました。
siriに頼んでテレビのオンオフも出来ました。
しかし「Hey siri, チャンネルをTOKYO MXにして！」が出来ない。
ちょっと別のアプローチを取ることにしました。

###bravia node_moduleを入れる
なんと、Braviaを操作できるnodeモジュールが存在します。
[bravia npm](https://www.npmjs.com/package/bravia)
このnodeモジュールでは、テレビに対してかなりいろいろな操作が可能です。
（homebridge-bravia-simpleなどのhomebridgeプラグインはこれのWrapperのようです）
これを使ってみることにします。

まずディレクトリを作ってそこにmoduleを入れます。

```bash
$ mkdir ~/homebridge/tvChannels && cd $_
$ npm init
$ npm install bravia
```
まず、[braviaモジュールのページ](https://www.npmjs.com/package/bravia)を参考に、TVの設定をして下さい。
テレビのリモコンを持ち、ホームを押してから右上の歯車【設定】を選ぶと設定が可能です。
Pre-Shared Key （PSK） と言うのは自分で決める暗証番号のようなもの。
ここでは**8888と設定したと仮定**します。

では実験してみましょう。
次のコードを書いて保存してください。IPアドレスとPSKを自分のモノに変更することを忘れずに。

```javascript:getContentList.js
const Bravia = require('bravia');
bravia = new Bravia('10.0.1.X', 80,  '8888');
bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbt", 'stIdx':0, "cnt": 200 })
  .then(contentList => {
    for (var i = 0; i < contentList.length; i++) {
      var content = contentList[i];
      console.log(
        "[Channel]:" + content.dispNum + "\n" +
        "[Title]:" + content.title +  "\n" +
        "[URI]:" + content.uri + "\n ----");
    }
  });
```

実行します。
`$ node getContentList.js`
チャンネル一覧が出てくれば正常に動いています。
気が付いていると思いますが、3行目のsource にある tv:isdbtをtv:isdbbsにすれば、BS放送の一覧が出てきます。
出力結果の1行目。dispNumの値は三桁のチャンネル番号です。あとでチャンネルを指定する際にこの数字を使うので、自分がみたい局の番号は控えておいて下さい。
また、出力結果の3行目にあるURIがチャンネルを変更するときに必要になるのですが、それはあとで。

3行目で呼び出している**getContentListは、Braviaが持っているAPI**。
その詳細は
[BRAVIA Professional Display Knowledge Center](https://pro-bravia.sony.net/ja/develop/integrate/rest-api/spec/index.html)
ここで公開されています。これは「法人向けブラビア」用に用意されたページのようですが、一般家庭向けのモノでも使えるようです。たぶん。
第二引数にはバージョンナンバーを指定します。指定しなくても動きますが、複数のバージョンが用意されているAPIではこれで切り替えます。
さっきのは、[avContent サービス のgetContentList API](https://pro-bravia.sony.net/ja/develop/integrate/rest-api/spec/service/avcontent/v1_5/getContentList/index.html) を叩きました。
バージョン1.5まで用意されています。バージョン番号を変更して叩き直すと、レスポンス内容が変わるので遊んでみてください。

次は[appControl サービス のgetApplicationList API](https://pro-bravia.sony.net/ja/develop/integrate/rest-api/spec/service/appcontrol/v1_0/getApplicationList/index.html)を叩いてみましょう。

```javascript:getApplicationList.js
const Bravia = require('bravia');
bravia = new Bravia('10.0.1.X', 80,  '8888');
bravia.appControl.invoke('getApplicationList')
  .then(appList => {
    for (var i = 0; i < appList.length; i++) {
      var app = appList[i];
      console.dir(app);
    }
  });
```
インストールされているアプリの一覧が出たりします。
これはアイコン画像とか提供されていてちょっと面白いです。

なお、現在受信している番組の情報は[avContent サービス のgetPlayingContentInfo API](https://pro-bravia.sony.net/ja/develop/integrate/rest-api/spec/service/avcontent/v1_0/getPlayingContentInfo/index.html)で取得出来ます。
日本国外だったりケーブルテレビだったりで、tv:isdbtではない場合はこれで調べることが出来ます。

```javascript:getPlayingContentInfo,js
bravia.avContent.invoke('getPlayingContentInfo','1.0')
  .then(nowPlayContent => {
      console.dir(nowPlayContent);
  });
```

>【メモ】
>実際のところbraviaと言うnodeモジュールは、このAPIを叩いているだけだ。
>APIドキュメントに従って、
> `$ curl -s -X POST -H "Content-Type: application/json; charset=UTF-8" -H "X-Auth-PSK: 8888" -d '{"method": "getApplicationList", "id": 60, "params": [], "version": "1.0"}' http://10.0.1.X/sony/appControl  | jq . `
> とかやると同じ結果を得ることが出来る。憶えておいても損はない。
> またBraviaのAPIは、おおよそテレビで行いたい操作は網羅されている上に、赤外線リモコンをエミュレートするものもある。だいたい何でも出来る。

さてこれで、Raspberry PiからBRAVIAを操作する目処が立ちました。
基本的な流れはこんな感じでしょうか。

1. **フジテレビを点けるように命令**
1. **電源が入っているかを確認**　system ＞ getPowerStatus
1. **入ってなければ電源を点ける**　system ＞ setPowerStatus
1. **フジテレビのURIを取得**　avContent ＞ getContentList
1. **そのURIにチャンネルを変更**　avContent　＞ setPlayContent

あと**現在放送中のチャンネルを知る**　avContent　＞ getPlayingContentInfo
が必要になります。

必要なAPIはすべて存在しているようです。
しかし問題は、一番最初の「点けるように命令」ですね。

##プラグイン homebridge-cmd4を使う
Raspberry PiからBraviaの操作ができることがわかったので、次はhomekitでの操作を考えます。
これには、[homebridge-cmd4](https://www.npmjs.com/package/homebridge-cmd4)と言うプラグインを使うことにしました。homekit対応アクセサリーをエミュレートし、shコマンドを発行出来るモノ、だと思えばよろしいです。
とりあえず入れちゃいましょう。
homebridge-config-ui-xから検索してインストールします。

```bash:手作業で入れるならこうなります
$ sudo npm install -g --unsafe-perm homebridge-cmd4
```

これを使って、homekitアクセサリーの「スイッチ」を作ります。

siriにお願いすると、スイッチがオンになります。
スイッチをオンにするとRaspberry Piがコマンドを発行し、BRAVIAを操作します。
スイッチに「フジテレビ」とか「TOKYO MX」とかの名前を付けておけば、「Hey siri, フジテレビを点けて」が出来るようになるというわけです。

結果としてチャンネルの数だけ「スイッチ」が並んでしまい、**もうどうにかならんのか**と言う状況になるが、今のところこれが唯一の解決方法。
改良方法を思いついたらチャレンジすることとします。

僕はチャンネルを20個用意しました。
20個のボタンが並ぶとhomeアプリが専有されてしまうので、「チャンネル」と言う名前の部屋を作りました。
正直、なんかもうちょっとマシな方法無いのかよ、とは思ってはいます。
![IMG_0047.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132814/6eb4805f-d674-f92e-f2f1-6388d9d472de.jpeg)


homebridge-cmd4でスイッチを作った場合、
**スイッチをオンにした際に発行されるsetコマンド**と、
**定期的に現在の状況を得るgetコマンド**が
発行されます。

setコマンドが発行されたらチャンネルを変更します。
フジテレビが点いている状態でテレビ朝日を選ぶと、一時的に両方ともスイッチがオンになりますが、次にgetコマンドが発行されたタイミングでフジテレビがオフになるわけです。
タイムラグが出てしまいますが、まあ良しとしよう。

##BRAVIAコントロールサーバを作る
先ほどのnodeモジュールを元に、RestAPI形式のサーバにしました。
まあ、扱いやすいので。そのため、nodeモジュールのExpressを利用しました。
一部cron処理したいのでnode-cronも入れました。
これは普通のcrontabを使っても、settimeout()を使っても良いのですが。

```bash
$ cd ~/homebridge/tvChannels
$ npm install express
$ npm install node-cron
```
###nodeサーバスクリプト
次にサーバになるスクリプトを用意しました。
長いし汚いですが全文記載します。
参考に好きなように書き換えて下さい。

<details><summary> **サーバスクリプト（node index.js）はこちらをクリック（長いので折りたたみました）** </summary>
<div>

```javascript:index.js
'use strict';

//必要なモジュール
//Express
var express = require('express');
var app = express();
var port = 3000;
//ブラビア
const Bravia = require('bravia');
var bravia = new Bravia('10.0.1.X', 80,  '8888'); //自分の環境に合わせてください

//ユーティリティ系
const util = require('util');
const fs = require('fs');
const cron = require('node-cron');

//設定ファイルなど 自分の環境に合わせてください
//チャンネルリスト
const channelInfoFile = "/home/pi/homebridge/tvChannels/channelInfo.conf";
//現在のチャンネル
const currentChannelCache = "/home/pi/homebridge/tvChannels/channelCache.txt";
var currentChannel;

//定期実行
//放送局一覧更新（必要なの？） 毎時実行
cron.schedule('0 * * * *', () => refreshCannelList());
//現在チューニングされているチャンネルの情報更新：30秒ごと
cron.schedule('*/30 * * * * *', () => getChannelToBravia());

//API サーバ
//現在チューニングされているチャンネル情報の取得
app.get('/channel',function(req,res){
	getChannel()
	.then(nowChannel => {
		res.json(
			nowChannel
		);
	});
});
//与えられたチャンネルが現在チューニングされているチャンネルかの確認
//cmd4から Get で呼ばれるよ
app.get('/channel/:channel',function(req,res){
	let channel = req.params.channel;
	ckChannel(channel)
	.then(ckResult => {
		console.log(JSON.stringify(ckResult));
		res.json(
			ckResult
		);
	});
});
//与えられたチャンネルに変更する
//cmd4から Set で呼ばれるよ
app.put('/channel/:channel',function(req,res){
	let channel = req.params.channel;	//与えられたチャンネル
	let result = setChannel( channel );	//チャンネル変更
	res.json(
		result
	);
});

app.listen(port);
console.log("START Bravia Server");

var isset = function(data){	//便利関数
	if(data === "" || data === null || data === undefined){
		return false;
	}else{
		return true;
	}
};

//ブラビア操作
//与えられたチャンネルに変更する
function setChannel( channel ){
	let result;
	//保存済みの Channelリストファイルから uri を取得
	util.promisify(fs.readFile)(channelInfoFile, 'utf-8')
	.then(channels =>{
		channels = JSON.parse(channels);
		if( channels[channel] !== undefined ){
			let uri = channels[channel].uri;
			return uri;
		}else{
			throw new Error('Channel:' + channel + ' is not exist.');
		}
	})
	.then(uri=>{	//リストを取得したあとで
		braviaPowerOn()	//テレビの電源がオフなら点ける
		.then(powerOnResult=>{	//電源が点いたあとで
			bravia.avContent.invoke('setPlayContent','1.0',{"uri":uri})	//チャンネルを変える
			.then(setPlayContentResult =>{
				getChannelToBravia(); //チャンネル情報を更新しておく
				result = {"action":"setChannel", "result" : "success", "message" : "Turn Channel to " +channel};
				return result
			})
		});
	})
	.catch((error) => {	//エラー
		result = {"action":"setChannel", "result" : "error", "message" : error};
		console.error(result);
	})
}

//現在チューニングされているチャンネル情報の取得
//キャッシュされた情報から返答
function getChannel(){
	return new Promise(function(resolve) {
		if(isset(currentChannel)){	//現在チャンネルが変数にあればそれを返す
			resolve(currentChannel);
		}else{	//変数がセットされてないときは
			util.promisify(fs.readFile)(currentChannelCache, 'utf-8')	//キャッシュファイルを見る
			.then(cache =>{
				currentChannel = JSON.parse(cache);
				if(isset(currentChannel)){
					resolve(currentChannel);
				}else{	//キャッシュファイルも空だったら
					getChannelToBravia()	//BRAVIAに聞きに行く
					.then(currentChannel => {
						resolve(currentChannel);
					});
				}
			}).catch( err =>{	//ファイルが存在しないとき
					getChannelToBravia()	//BRAVIAに聞きに行く
					.then(currentChannel => {
						resolve(currentChannel);
					});
			});
		}
	});
}

//現在チューニングされているチャンネル情報をテレビに問合せて保存する
function getChannelToBravia(){
	return new Promise(function(resolve) {
		bravia.avContent.invoke('getPlayingContentInfo','1.0')
		.then(currentChannelFromBravia => {
			fs.writeFile(currentChannelCache, JSON.stringify(currentChannelFromBravia, null, 2), "utf-8",(err) => {
				currentChannel = currentChannelFromBravia;
				resolve(currentChannel);
			});
		})
		.catch(function(){	//電源が入ってないと失敗するので無視する
			console.log('[BRAVIA Power Off]');
		});
	});
}

//チャンネル確認リクエスト
function ckChannel(ckChannel){
	return new Promise(function(resolve) {
		let result;
		bravia.system.invoke('getPowerStatus' , '1.0')	//電源チェック
		.then(power =>{
			if(power.status !== 'active'){	//電源が入ってないとき
				result = false;
				let resultJSON = {"result" : result , "checkChannel": ckChannel,"nowChannel": "powerOff"} ;
				resolve( resultJSON );
			}else{		//電源が入っているとき
				getChannel()
				.then(nowPlayContent => {
					if(nowPlayContent.dispNum === ckChannel){
						result = true;
					}else{
						result = false;
					}
					let resultJSON = {"action":"ckChannel","result" : result , "checkChannel": ckChannel,"nowChannel": nowPlayContent.dispNum} ;
					resolve( resultJSON );
				});
			}
		});
	});
}

//電源を取得して OFF なら ON にする
function braviaPowerOn(){
	return new Promise(function(resolve) {
		bravia.system.invoke('getPowerStatus' , '1.0')
		.then(power =>{
			if(power.status === 'active'){
				resolve(power);
			}else{
				bravia.system.invoke('setPowerStatus' , '1.0' , {"status": true})
				.then(result =>{
					resolve(result);
				})
			}
		});
	})
}

// Bravia から Channelリストを取得して保存する
// 一覧はテレビの電源が入って無くても取れる様子
function refreshCannelList(){
	console.log('[Channel List Refresh Start]');
	Promise.all([
		bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbt", "stIdx":0, "cnt": 200}) ,	//地デジリスト
		bravia.avContent.invoke('getContentList', '1.0', { "source": "tv:isdbbs", "stIdx":0, "cnt": 200})	//BS リスト
		//お好みに応じて CS や HDMI なども追加
	])
	.then( ([isdbt,isdbbs] ) =>{
		let channelInfo = makeChannelInfo( [isdbt,isdbbs] );
		fs.writeFile(channelInfoFile, JSON.stringify(channelInfo, null, 2), "utf-8", (err) => {
			if(err) {
				console.log(err);
			}else{
				console.log('[Channel List Refreshed]')
			}
		});
	})
}

// Bravia から取得したリストを成形する
function makeChannelInfo(channels){
	let channelInfo = {};
	for (var i = 0; i < channels.length; i++) {
		for (var x = 0; x < channels[i].length; x++) {
			channelInfo[ channels[i][x].dispNum ] = channels[i][x];
		}
	}
	return channelInfo;
}

```

> 【補足メモ】
> Braviaが返すuriにはtripletStrと言う三つの数字が連結されたものが含まれています。
> これがなんなのかわかりません。ずっと固定なのか定期的に変更されるのかなどの仕様ががわからないですが、チャンネルを変更するたびに問い合わせるのは効率が良いとは思えないので、サーバ上に保存しておいて定期的に更新することにしました。
>
> また、一度作ってみたところ、20個ほどあるすべてのボタンが、自分のステータスがオンかオフかを確認するために、数十秒に一回アクセスしに来ます。。その都度テレビに通信が行くのは精神衛生上よろしくなく、また頻繁にタイムアウトエラーも出てしまっていたので、BRAVIAサーバが定期的に取得しに行き、ボタンたちはキャッシュされたモノを見ることにしました。
（それでもたまにタイムアウトエラーが起きている様子）
登録しているチャンネルの数やサーバに使っているマシンのスペックなどを見ながら確認しに行く間隔を調整してください。（間隔の設定は後述のcmd4のconfigで行う）
</div></details>

さらに、これを常に起動させるためにServiceに登録します。
ホントはユーザー権限で入れるのがお行儀良いのだと思いますが、あんまり気にしない。

`/etc/systemd/system` に以下のファイルを作ります。sudoしてね。

```bash
$ sudo vi /etc/systemd/system/bravia.service
```

```config:bravia.service
[Unit]
Description = Sony BRAVIA Control Server
After = network.target 

[Service]
ExecStart =node /home/pi/homebridge/tvChannels/index.js
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target
```

置いたらサービスに登録します

```bash
$ sudo systemctl start bravia #起動出来るかな？
$ systemctl status bravia #動いてるか確認
$ curl http://localhost:3000/channel #動いていればこれで今受信中の番組情報が出る
$ sudo systemctl enable bravia #大丈夫そうなら次回以降も自動起動するように登録 
```
これで、Raspberry PiがBRAVIA操作サーバになりました:thumbsup_tone2:

###cmd4の設定
続いて、cmd4の設定を行います。
homebridge-config-ui-xに戻って下さい。
画面上部の【コンフィグ】を押しましょう。

このJSONの中に、以下のセクションを追加します。

```json:config.json
       {
            "platform": "Cmd4",
            "name": "Channel",
            "accessories": [
                {
                    "type": "Switch",
                    "name": "NHK",
                    "state_cmd_suffix": "011",
                    "on": false,
                    "state_cmd": "/home/pi/homebridge/tvChannels/bravia.sh",
                    "polling": [
                        {
                            "on": true,
                            "interval": 20,
                            "timeout": 8000
                        }
                    ]
                },
                {
                    "type": "Switch",
                    "name": "Eテレ",
                    "state_cmd_suffix": "021",
                    "on": false,
                    "state_cmd": "/home/pi/homebridge/tvChannels/bravia.sh",
                    "polling": [
                        {
                            "on": true,
                            "interval": 20,
                            "timeout": 8000
                        }
                    ]
                },
                ………以下必要なチャンネルだけ続ける
            ]
       }
```
アクセサリー`accessories`としてスイッチ`"type": "Switch"`をたくさん作っていきます。
チャンネルの名前も`"name":"NHK"`付けてあげます。この名前でチャンネルを決めます。
起動した直後の設定はオフ`"on": false`です。

cmd4はボタンが押されたらSet / 一定間隔でGetが発行させると、前述しました。
そのコマンドは、`"state_cmd"` に書かれたものになります。
引数としてnameを送りますが、それで管理するのはちょっと不安ですので、`"state_cmd_suffix"`としてチャンネル番号を付与して送るようにしました。前述のBraviaServerもその前提で書きました。
また、Getコマンドは上記の`polling`で設定した間隔（秒）で送られます。timeoutはmsです。

この結果、cmd4は以下のようなcmdを実行します。
Set時（Button押したとき） 
`/home/pi/homebridge/tvChannels/channel.sh Get 'Eテレ' 'On' 021`
Get時（20秒に一回） 
`/home/pi/homebridge/tvChannels/channel.sh Set 'Eテレ' 'On' true 021`
引数の数が違うことに注意が必要です。

###起動用スクリプト

最後にcmd4が叩きにくるスクリプトも用意します。
cmd4が直接BRAVIAサーバにリクエストする作りはちょっと知恵を使わないといけないので、Wrapper替わりになるシェルスクリプトを置きます。
なお、jqを使っていますので入ってなかったら入れておいて下さい。
curlも使ってますが、さすがに標準で入っていると思う。まあ念のため書いておく。

```bash
$ sudo apt-get install jq
$ sudo apt-get install curl 
```

以下の起動用スクリプトを
`/home/pi/homebridge/tvChannels/bravia.sh`（cmd4のコンフィグで指定した場所）
において下さい。

```bash:bravia.sh
#!/bin/bash

if [ "$1" = "Get" ]; then
  JSON=$(curl -s http://localhost:3000/channel/${4})
  result=$(echo ${JSON} | jq  '.result')
  if [ ${result} = true ]; then
    echo 1
  else
    echo 0
  fi
   exit 0
fi

if [ "$1" = "Set" ]; then
   if [ "$3" = "On" ]; then
      if [ "$4" = "true" ]; then
        JSON=$(curl -s -XPUT http://localhost:3000/channel/${5})
        echo 1
      fi
   fi
   exit 0
fi

exit 1
```
保存したら実行許可を付けておきましょう。

```bash
$ chmod 0755 /home/pi/homebridge/tvChannels/bravia.sh
```

> 【補足】
> cmd4で叩かれたスクリプトは標準出力に結果を返す（echoする）必要があります。
> 返す結果はcmd4で作ったアクセサリの種類（操作するものの種類）によります。
> スイッチの場合はOnかOffしかありませんので、どちらの状態なのかを返します。「On」という状態がtrueかfalseかを返す、と理解してください。
> 上記スクリプトで`echo 1`となっているものは、On状態がtrue。つまりOn。
> `echo 0`となっているものはOn状態がfalse。つまりOffです。
> Getコマンドでは現在のテレビの状態を調べて、ボタン自身のチャンネルと実際にテレビに映っているチャンネルが一致すればtrueを返すのでonになります。一致してなかったらfalseを返すのでoffになります。
> その結果、20秒経ってGetコマンドが呼ばれた際に、OnのままになっているチャンネルがOffになり、実際のテレビの状態と一致します。
>
> スイッチではなくたとえばボリュームを作った場合は `echo 40`のように操作後の値を返す必要があります。この場合は「ボリュームが40」と言う意味になります。


##最後に
これで、Hey! siriとやりたかったことは実現出来ました。
紆余曲折しちゃったんですが、結局API叩いているので、最初に入れたhomebridge-braviaプラグイン無しで、テレビの電源入れるAPI叩くスイッチ用意すれば良いんじゃないの？とか、これならhomebridgeのプラグインを新しく作るのが良いんじゃないの？とかそんな気もします。

BRAVIAのAPIはいろんなことが出来るので、音量調整とかも可能ですし、リモコンをエミュレートすれば視聴中の番組録画なども出来そうです。

ぜひチャレンジしていただければな、と思ったりもします。