homebridge-cmd4 設定例

# はじめに
NRI OpenStandiaのアドベントカレンダーですが、業務に関連する話ではなく、完全に趣味の話で、Homebridgeのプラグインの一つであるhomebridge-cmd4を紹介します。
[Homebridge](https://homebridge.io)はHomeKitに対応していない製品とHomeKitを仲介してくれるサーバアプリです。プラグイン形式で様々なIoT製品とHomeKitの仲介をしてくれます。[HomeKit](https://www.apple.com/jp/ios/home/)はAppleのIoTシステムです。HomeKitに対応しているとApple製品のホームアプリやSiriから操作できるようになります。
Homebridgeについては多くの紹介記事等が公開されているので、QiitaやGoogleで検索してみてください。


# homebridge-cmd4とは
[hoembridge-cmd4](https://github.com/ztalbot2000/homebridge-cmd4)はホームアプリの操作等に合わせてHomebridgeを実行しているサーバ上のコマンドを実行するプラグインの一つです。
HomeKitの様々なアクセサリタイプ(スイッチやモーションセンサ、テレビ等)に対応しているのが特徴です。[README](https://github.com/ztalbot2000/homebridge-cmd4)のはじめではすべてのアクセサリタイプをサポートしていると書かれています。実際はカメラは対応してないようなので、完全なすべてではないですがほぼ全てサポートしているようです。
Nature Remoのエアコン操作用プラグイン([NatureRemoAircon](https://github.com/kmaehashi/homebridge-nature-remo-cloud-aircon))やセンサ用プラグイン([remo-sensor](https://github.com/hirosan18/homebridge-nature-remo-sensor))のような専用のプラグインが公開されていないものに、homebridge-cmd4を使うことで個別のプラグイン開発までいかずに対応できます。
今回は私が実際に使っている設定をいくつか紹介します。


# 設定例
## Switch
まずは一番基本的と思われるSwitchの紹介を通して、homebridge-cmd4の設定の全体感を説明します。なお、Switchは名前の通りスイッチアクセサリです。単純なオンオフに対応しています。
私はWoLとSSHでの作業用サーバ(PC)の電源オンオフや床暖房のオンオフ等に使っています。今回はサーバのオンオフする設定を元に設定方法をご紹介します。
ホームアプリ上の見た目は以下です。

![Server.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/7c7e1117-12ff-571e-d391-59030175124d.png)

早速設定ですが、まずはHomebridgeのconfig.jsonからです。
Switchの設定例は以下のとおりです。

``` json:config.json
"platforms": [
    {
        "platform": "Cmd4",
        "name": "Cmd4",
        "accessories": [
            {
                "type": "Switch",
                "name": "Server",
                "displayName": "server",
                "on": "FALSE",
                "polling": [
                    {
                        "characteristic": "on",
                        "interval": 5,
                        "timeout": 10000
                    }
                ],
                "state_cmd": "/patch/to/server.sh"
            }
        ]
    }
]
```

`platforms`中の`platform`や`name`はHomebridgeのプラグインの基本的な設定内容なので、解説は割愛します。
`accessories`の要素についての説明を以下表にまとめます。

| 項目名 | 内容 |
| --- | --- |
| type | アクセサリタイプを指定します。 |
| name | ホームアプリ上で表示される名前等に利用されます。指定がない場合`displayName`の値が利用されます。 | 
| displayName | `state_cmd`で指定されたコマンドの実行時にアクセサリ名としてコマンドに引数で渡されたり、ログ出力等に利用されます。指定がない場合`name`の値が利用されます。 |
| on | `characteristic`と呼ばれるアクセサリのステータスのようなものとなり、利用するものを宣言しておくイメージの設定となります。アクセサリタイプによって利用できる`characteristic`は異なり、アクセサリタイプにより必須のものと任意のものがあります。何が必須なのかなどは設定値の解説等が公開されている[homebridge-cmd4のGitHub Pages](https://ztalbot2000.github.io/homebridge-cmd4/autoGenerated/CMD4_AccessoryDescriptions.html)が参考になると思います。 |
| polling | `characteristic`の状態を確認するポーリングの設定です。ホームアプリでの操作以外でアクセサリの状態が変わっていることを検知します。`interval`はポーリング間隔(秒)、`timeout`はポーリングリクエストのタイムアウト時間(ミリ秒)の指定です。`interval`と`timeout`は指定がない場合のデフォルト値は60秒です。配列で複数の`characteristic`のポーリング設定を記載できます。また、配列とせず、`poling: true`という指定もでき、この場合デフォルトの`characteristic`に対し、ポーリング間隔等もデフォルト値でポーリングを行います。デフォルトの`characteristic`は[homebridge-cmd4のGitHub Pages](https://ztalbot2000.github.io/homebridge-cmd4/autoGenerated/CMD4_AccessoryDescriptions.html)を参照ください。 |
| state_cmd | ホームアプリからの操作やポーリング時に実行するコマンドの指定です。 実行するコマンドはhomebridge-cmd4では1つのアクセサリに対して一つのコマンドを指定します。呼び出しの場面によって4つの引数が渡されてそのコマンドが実行されます。引数の詳細については次に説明するコマンドの実装の中で紹介します。 |

次にhomebridge-cmd4から呼び出されるコマンドについてです。
`config.json`の`state_cmd`の説明で紹介したとおり、homebridge-cmd4は4つの引数でコマンドを実行します。第1引数は`Get`または`Set`が入ります。`Get`はポーリングでのアクセサリのステータス確認時に渡され、`Set`はスイッチオン等のステータス変更時に渡されます。第2引数はアクセサリの`displayName`が入ります。一つのコマンドで複数のアクセサリに対応したい場合や、後ほど説明する`linkedType`というアクセサリタイプを入れ子にする設定を利用する際に使用します。第3引数は`characteristic`が入ります。確認や変更をする対象の`characteristic`が何なのかの判定に利用します。config.jsonの説明の中でも触れたように、アクセサリごとに対応している`characteristic`は異なります。第4引数は第1引数に`Set`が入る場合に変更後のステータスが入ります。
上述のconfig.jsonの設定内容でスイッチオンされた場合のコマンドは次のようになります。

```
/path/to/server.sh Set server On 1
```

オフの場合は第4引数が`0`となり、ポーリングでは第1引数が`Get`となることになります。

この引数に合わせてコマンドを用意します。私は以下のようにしています。[サンプル](https://github.com/ztalbot2000/homebridge-cmd4/tree/master/Extras/Cmd4Scripts/Examples)でBashが使われていたこともあり、Bashで実装しています。
なお、`Get`に対しては標準出力で結果を返す形になります

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

if [ "$1" = "Get" ]; then
    case "$3" in
        "On")
            ping -c3 server > /dev/null
            if [ $? = 0 ]; then
                echo 1
            else
                echo 0
            fi
            ;;
    esac
    exit 0
fi

if [ "$1" = "Set" ]; then
    case "$3" in
        "On")
            if [ "$4" = "1" ]; then
                awake xx:xx:xx:xx:xx:xx
                exit 0
            else
                ssh server 'sudo shutdown -P now'
                exit 0
            fi
            ;;
    esac
fi
```

電源の状態はpingで確認しています。結果を標準出力で返す必要があるため、ping自体の出力は捨てています。


## AirPurifier
次はAirPurifierの設定を紹介します。AirPurifierは空気清浄機用のアクセサリタイプです。
私は[SwitchBot](https://www.switchbot.jp/products/switchbot-bot)を利用して強モードにする操作をできるようにし、ホームアプリのオートメーション設定と組み合わせて外出後に強モードにするようにしています。
1つのスイッチのオンオフでの利用のため、Switchでも良いのですが、せっかく丁度いいアクセサリタイプがあるので、これを利用しています。
ホームアプリ上の見た目は以下です。

![AirPurifier.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/819743a9-bc9d-c7aa-fec6-ce2df8ef935c.png)

``` json:config.json
"platforms": [
    {
        "platform": "Cmd4",
        "name": "Cmd4",
        "accessories": [
            {
                "type": "AirPurifier",
                "name": "Air Purifier",
                "displayName": "air-purifier",
                "active": "ACTIVE",
                "currentAirPurifierState": "INACTIVE",
                "targetAirPurifierState": "MANUAL",
                "polling": [
                    {
                        "characteristic": "active"
                    },
                    {
                        "characteristic": "currentAirPurifierState"
                    }
                ],
                "state_cmd": "/path/to/air-purifier.sh"
            }
        ]
    }
]
```

config.jsonはSwitchの設定と大きくは変わりません。複数の`characteristic`の利用とポーリングする設定になっています。


``` bash:air-purifie.sh
#!/bin/bash

if [ "$1" = "Get" ]; then
    case "$3" in
        "Active")
            if [ `curl -s http://xxxxx/status | jq .data.status` = 1 ]; then
                echo 1
            else
                echo 0
            fi
            ;;
        "CurrentAirPurifierState")
            if [ `curl -s http://xxxxx/status | jq .data.status` = 1 ]; then
                echo 2
            else
                echo 0
            fi
            ;;
        "TargetAirPurifierState")
            echo 0
            ;;
    esac
    exit 0
fi

if [ "$1" = "Set" ]; then
    case "$3" in
        "Active")
            if [ "$4" = 1 ]; then
                curl -s http://xxxxx/jetmode
            fi
            exit 0
            ;;
    esac
fi
```

コマンドの実装もSwitchと同様です。`characteristic`によって処理を分けています。
今回は詳細は割愛しますが、SwitchBotをBluetoothで制御するアプリを別に用意して、シェルスクリプトからはそれを呼び出すことでSwitchBotを動かす構成としています。

なお、AirPurifierでは`Active`が`1 (ACTIVE)`で`CurrentAirPurifierState`が`1 (IDEL)`という組み合わせで待機中という状態を表現できるようですが、私の用途では不要のため、`0 (INACTIVE)`/`0 (INACTIVE)`のオフと`1 (ACTIVE)`/`2 (PURIFYING_AIR)`のオンの2つの状態で利用しています。また、上述の画像ではモードというのもありますが、それも利用していません。

## BatteryService
次に`linkedTypes`という設定についてBatteryServiceというアクセサリタイプの利用例を用いて紹介します。
BatteryServiceはバッテリー残量を示すアクセサリタイプです。バッテリーや電池駆動のアクセサリタイプに対して組み込んで利用するのが通常の使い方かと思います。この組み込むという設定が`linkedTyeps`になります。

私はマンションの呼び出し式オートロック共同玄関のモニター側の操作を行うアクセ
サリの設定内でBatteryServiceを利用しています。
まずはこの遠隔操作を行う仕組みの全体感を紹介します。

![autolock.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/83a4187c-940a-1a38-d0f8-e0fae676c676.png)

基本は[SwitchBot](https://www.switchbot.jp/products/switchbot-bot)を利用した遠隔の解錠操作です。それに加えて、[SensorTag](https://www.tij.co.jp/tool/jp/CC2650STK)の照度センサを利用して、呼び出し時のモニターの点灯を検知して自動で[SwitchBot](https://www.switchbot.jp/products/switchbot-bot)での解錠操作を行うようにしています。この[SensorTag](https://www.tij.co.jp/tool/jp/CC2650STK)の電池がそこまで長期間持たないということもあり、BatteryServiceを利用してホームアプリから残量がわかるようにしています。
なお、いつでもモニターの点灯で自動解錠してしまうと誰でも入れるようになってしまうため、ホームアプリのオートメーションで登録された人が家に近づいたときに照度センサのスキャンを開始するようにしています。

続いてconfig.jsonの説明に入ります。

``` json:config.json
"platforms": [
    {
        "platform": "Cmd4",
        "name": "Cmd4",
        "accessories": [
            {
                "type": "LockMechanism",
                "name": "Entrance",
                "displayName": "open-entrance",
                "lockCurrentState": "UNSECURED",
                "lockTargetState": "UNSECURED",
                "linkedTypes": [
                    {
                        "type": "Switch",
                        "name": "Scan Entrance",
                        "displayName": "scan-entrance",
                        "on": "FALSE",
                        "polling": [
                            {
                                "characteristic": "on",
                                "interval": 5,
                                "timeout": 10000
                            }
                        ],
                        "state_cmd": "/path/to/entrance.sh"
                    },
                    {
                        "type": "BatteryService",
                        "name": "SensorTag Battery",
                        "displayName": "sensortag-battery",
                        "batteryLevel": 50,
                        "chargingState": "NOT_CHARGEABLE",
                        "statusLowBattery": "BATTERY_LEVEL_NORMAL",
                        "polling": [
                            {
                                "characteristic": "batteryLevel"
                            },
                            {
                                "characteristic": "statusLowBattery"
                            }
                        ],
                        "state_cmd": "/path/to/entrance.sh"
                    }
                ],
                "polling": [
                    {
                        "characteristic": "lockCurrentState"
                    },
                    {
                        "characteristic": "lockTargetState"
                    }
                ],
                "state_cmd": "/path/to/entrance.sh"
            }
        ]
    }
]
```

基本の[SwitchBot](https://www.switchbot.jp/products/switchbot-bot)の操作部分はLockMechanismという鍵用のアクセサリタイプを利用しています。
そして、`linkedTypes`でBatteryServiceとSwitchを子アクセサリタイプとして登録しています。`linkedTypes`内でのアクセサリの設定内容は通常の設定と同じです。Switchはオートメーションで登録した人が家に近づいたときにスキャンを開始するスイッチに利用しています。Switchなのでオートメーションだけでなく手動でのオンオフも可能です。

コマンドについては、渡された`characteristic`に合わせて結果を返せばいい点は、上述の他のアクセサリタイプと変わらないため、紹介は割愛します。

ホームアプリ上の見た目は以下です。

![Battery.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/d9cec678-3f99-c1cf-07f2-ad65c15846d4.png)

`linkedTypes`で定義しているSwitchのボタンが横並びで表示されていますが、ホームアプリの設定で、以下のように一つのボタンとして表示することも可能です。

![Battery_Tile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/a84146ae-660c-e730-c015-39b4b02737e4.png)

`statusLowBattery`が`1 (BATTERY_LEVEL_LOW)`を返すようになると以下のようになります。

![Battery_Low.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/31a57426-fd7b-850d-31d9-618080756958.png)


## Television
最後に少し特殊なアクセサリタイプのTelevisionを紹介します。Televisionアクセサリはホームアプリ上からだけでなく、リモートアプリ上からも操作できるようになります。

![TV.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/d6693994-1fa6-e3a5-2d47-25d60e444327.png)

私はテレビはApple TVのディスプレイ専用機になっているので、基本的な操作はApple TVのリモコンやリモートアプリでのApple TVの操作で行っており、Televisionアクセサリではホームアプリ上からの電源のオンオフと入力切替、リモートアプリ上からの音量調整をできるようにしています。
なお、音量調整はリモートアプリ上でiPhone本体の音量ボタンを操作する形となっています。最近のiOSでは上の画像のようにナビゲーションしてくれていますが、以前のバージョンではこれが表示されなかったため、どのように操作すれば音量操作になるのかがわかりにくかったです。。

config.jsonは以下のようになります。

``` json:config.json
"platforms": [
    {
        "platform": "Cmd4",
        "name": "Cmd4",
        "accessories": [
            {
                "type": "Television",
                "configuredName": "TV",
                "displayName": "tv",
                "active": "ACTIVE",
                "activeIdentifier": 1,
                "sleepDiscoveryMode": "ALWAYS_DISCOVERABLE",
                "currentMediaState": "STOP",
                "targetMediaState": "STOP",
                "remoteKey": "SELECT",
                "publishExternally": true,
                "category": "TELEVISION",
                "linkedTypes": [
                    {
                        "type": "InputSource",
                        "configuredName": "Input",
                        "displayName": "Input1",
                        "currentVisibilityState": "SHOWN",
                        "inputSourceType": "HDMI",
                        "isConfigured": "CONFIGURED",
                        "identifier": 1,
                        "targetVisibilityState": "SHOWN",
                        "name": "Input1",
                        "polling": true,
                        "state_cmd": "/path/to/tv.sh"
                    },
                    {
                        "type": "InputSource",
                        "configuredName": "Input",
                        "displayName": "Input2",
                        "currentVisibilityState": "SHOWN",
                        "inputSourceType": "HDMI",
                        "isConfigured": "CONFIGURED",
                        "identifier": 2,
                        "targetVisibilityState": "SHOWN",
                        "name": "Input2",
                        "polling": true,
                        "state_cmd": "/path/to/tv.sh"
                    },
                    {
                        "type": "TelevisionSpeaker",
                        "name": "TVSpeaker",
                        "displayName": "TVSpeaker",
                        "mute": "FALSE",
                        "volumeControlType": "RELATIVE_WITH_CURRENT",
                        "volumeSelector": "INCREMENT",
                        "polling": [
                            {
                                "characteristic": "volumeSelector"
                            }
                        ],
                        "state_cmd": "/path/to/tv.sh"
                    }
                ],
                "polling": [
                    {
                        "characteristic": "active",
                        "interval": 5,
                        "timeout": 10000
                    },
                    {
                        "characteristic": "remoteKey"
                    },
                    {
                        "characteristic": "activeIdentifier"
                    }
                ],
                "state_cmd": "/path/to/tv.sh"
            }
        ]
    }
]
```

`Name`の変わりに`configuredName`が必須となっていますが、使われ方としては`Name`と同じようです。また、`"publishExternally": true`と`"category": "TELEVISION"`が必須になっています。
その他については基本的な記載方法は他のアクセサリタイプと同様ですが、入力切替と音量操作については、それぞれ`InputSource`と`TelevisionSpeaker`を`linkedTypes`で設定する形になります。
`InputSource`はコマンド実行部分も特殊で、以下のように親の`Television`の`activeIdentifier`として、コマンド実行されます。

```
/path/to/tv.sh Set tv ActiveIdentifier 1
```

なお、リモートアプリ上の音量以外の操作は`remoteKey`となっています。

コマンドについては、渡された`characteristic`に合わせて結果を返せばいい点は、上述の他のアクセサリタイプと変わらないため、紹介は割愛します。


# おわりに
Homebridgeの専用プラグインがない製品に汎用的に対応できるプラグインのhomebridge-cmd4の設定例の紹介でした。

今回紹介したアクセサリタイプ以外にも様々アクセサリタイプがあります。READMEにある[Installation Details](https://github.com/ztalbot2000/homebridge-cmd4#installation-details)に従ってインストールすると、サンプルの設定が行われ、以下のように、様々なアクセサリタイプを見てみることができます。

> ![Homekit_screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13224/8b658564-6405-9211-2ab8-1df074cacc5b.png)
> [homebridge-cmd4のREADME](https://github.com/ztalbot2000/homebridge-cmd4)より

設定は、[Installation Details](https://github.com/ztalbot2000/homebridge-cmd4#installation-details)で使われている[サンプルの設定ファイル](https://github.com/ztalbot2000/homebridge-cmd4/blob/master/Extras/config.json)や、あまり詳細は書かれていませんが設定サンプルや設定値の解説等を公開している[homebridge-cmd4のGitHub Pages](https://ztalbot2000.github.io/homebridge-cmd4/autoGenerated/CMD4_AccessoryDescriptions.html)が参考になると思います。本記事で紹介しているアクセサリタイプでも説明しているもの以外の様々な設定項目があります。
コマンドの実装については、あまり種類は多くないですがGitHub上で公開されている[サンプル](https://github.com/ztalbot2000/homebridge-cmd4/tree/master/Extras/Cmd4Scripts/Examples)も参考になると思います。