Google AssitantでIFTTTの変数入力を再現する

# 経緯という名のポエム
[IFTTTのGoogleアシスタント統合が変更](https://ifttt.com/explore/google-assistant-changes)されて1週間ほどが経ちました。
変更というのは穏当な表現で、実際の利用者の感覚からすると「改悪されてしまった」というのが正直なところです。一応課金ユーザーなのに悲しい。

理由はGoogle側のプラットフォーム変更によるものとのことで、またいつものGoogle仕草だなあという感想です。~~そんなだからSTADIAにもユーザーが集まらないんだよ~~

さて、この変更のうち最も大きな影響が、**変数入力機能**の削除です。
変数入力という名前のとおり、Googleアシスタントへの命令文中の一部を変数として扱うという機能です。

変数を使うと `タスクを追加 hoge` とGoogleアシスタントに話かけるだけで、hogeという文字列を取り出して別のサービスに連携できます。
この取得した値を使って `hogeタスク` を作ったり `hogeメール` を送らせるもよし、
APIに `hogeパラメーター` を渡して好きに料理するもよし、とまさに応用範囲無限大のステキ機能だったのです。

しかし変更後のIFTTTでは、Googleアシスタントにはもはや固定の命令文しか設定できません。
IFTTTをハブに構築した数々の仕組みが瓦解した瞬間です。ゆゆしいですね。

なんとかIFTTTなしでもこの仕組みを再現せねばなりません。


# 買い物リスト、タスクリストを再現する

変数入力の最もポピュラーな使われ方がこれだったと思います。
我が家の環境では

```
Google Homeに「買い物 hoge」とお話
→ IFTTTでhogeを変数として取り出し
→ Todoistの買い物リストにhogeを追加
```

ということをやっていました。
Googleアシスタント側にもショッピングリスト機能もあるにはありますが、使い勝手は正直悪く、Todoistを愛用していました。
Todoistなら自分以外の家族ともスムーズに同期できて、APIにも欲しいものが揃っています。


## IFTTTなしでの再現手順
1. 適当なサーバーを用意する
私は稼働中のRaspberry Pi 3を使って相乗りにしましたが、AWSとかでも、なんでもいいはず。
1. Googleアシスタントの設定を変更
メモとリスト > メモとリストのプロバイダを選択 > Google Keep を選んでください。
1. Google Keep側でいいかんじのタイトルのメモを作る。今回は「買い物」
適当に1件か2件程度アイテムを入れて「チェックボックスを表示」付きで保存します。
1. 用意したサーバーに https://github.com/airosB/keep2todoist をCloneしてインストール
Google Keepの指定したリストの中身をTodoistに同期してくれるやつです。
同期は一方向で、同期したアイテムはKeepから消えます。
※最初私の環境ではインストールに失敗したので、自分用にforkしてDockerfile等を修正しました。上のリポジトリは修正後のものです。
1. keep2todoistのREADMEを読みつつ設定ファイルを書く
TodoistのAPIトークンを発行してコピペします。
Googleのアプリパスワードを作成してコピペします。
同期元（Google Keepメモ）と同期先（Todoistプロジェクト）の名前を書きます。
同期元⇔同期先のペアは複数指定できます。たとえば買い物リストとタスクリストを同時に同期できます。
またKeep側のメモタイトル、Todoist側のプロジェクト名には、どちらも日本語を指定できます。
1. keep2todoistを実行した状態で、アシスタントに `OK Google, 買い物リストにhogeを追加して` とお話する。
1分ほど待つと同期が走り、Todoist側にhogeが追加されます。
1. **[オプショナル]** Google Keepのメモを間違って消さないよう、「bot管理中 消すな！」みたいにわかりやすいラベルをつけておくとよい
1. **[オプショナル]** メモを共有したい人（例：家族）がいる場合、追加で以下の3つを行う
共有相手のメモとリストのプロバイダをGoogle Keepに変更
Google Keepのメモを共有相手と同期
Todoistプロジェクトを共有相手と同期


Todoist使いたくないよって人は、keep2todoist参考に自分用の同期スクリプトを書けば同じことができるかと思います。
サーバーを用意するのが嫌だったら、Google Apps Scriptで同等機能を書いて1分ごとに起動させればいいのかな。


# 数値入力を再現する

IFTTTには変数に数値だけを受け取るような機能もあって、これも便利でした。
`OK Google, チャンネルをfugaにして` でTVのチャンネルを切り替えるみたいな感じです。
我が家では

```
Google Homeに「子供の体温 fuga」とお話
→ IFTTTでfugaを変数として取り出し
→ 家BOTの体温記録APIにfugaを送信
→ Google Spreadsheetに時刻付きで体温記録
```

ということをやっていました。
検温は毎朝のルーチンなので両手が塞がっていても記録できるのは強いのです。
ちなみに体温値は「さんじゅうろくてんご」みたいな小数点付き数値がそのまま使えていました。


## IFTTTなしでの実現方法
これは色々な方法が考えられると思います。
たとえば先程のkeep2todoistを改造して体温記録APIに送るようにしてもよいですが、
`OK Google, 体温リストに36.5を追加して` なる呼び出しはちょっと変な感じがします。

しかしよく考えてみると、「何らかの数値を受け取って動作する家電」さえあれば、その値をAPIに送ることはできるのでは？と気づきました。
幸運にも世の中には、人体の体温とだいたい同じ範囲で数値設定できる家電があり、それはエアコンと呼ばれています。
今回はこれを活用する方針としました。

※エアコン以外でも、色をRGBで指定できるライトとかは目的外使用ができるかも？とか考えてます（試してはいない）

1. [Home Assistant](https://www.home-assistant.io/)を導入する
Home Assistant (hass) というのは、Raspberry Piにインストールできる自分専用のIFTTTみたいなものだと思ってください。
フルカスタマイズ運用可能で、ちょっとyamlを書けば好きに家電を生やすことができます。
そして家電操作時の挙動として「REST APIを叩く」だったり「シェルスクリプト実行」だったりが使えます。今回の用途にピッタリです。
インストール方法等は[公式ドキュメント](https://www.home-assistant.io/getting-started/)がとてもわかりやすいのでそちらをどうぞ。
1. Home AssistantをGoogleアシスタントと連携
[ここを読んで設定](https://www.home-assistant.io/integrations/google_assistant/)。課金機能を使って一瞬で終わらせてもいいし、無料で頑張ってもよいです（ステップを追うだけなので簡単）。
無料でやる場合でも、HTTPS公開やSSL証明書の自動更新方法についての[詳細なガイダンス](https://www.home-assistant.io/blog/2017/09/27/effortless-encryption-with-lets-encrypt-and-duckdns/)があります ※※
自分以外の家族がいる場合は、その人の分も連携設定をしておいてください ※※※
1. Home Assistant上で「子供の体温」という名前のエアコンを作成
ここはyamlを書いてそういう名前のエアコンを無から生やします。実際の家にエアコンがなくても問題ありません。
1. 「エアコンの設定温度が変更されたら、設定温度をパラメータにして体温記録APIを叩く」というAutomationを書く
これもyamlを書くか、Home AssistantのWebUIでぽちぽち入力します。
1. アシスタントに `OK Google, 子供の体温 fuga度` とお話して記録


>※※訂正：上記で案内されているDuckDNSを使った方法ですが、DuckDNS自体の不安定さにより、これを書いているいままさに現時点（10/4）でデバイスの同期ができなくなっています。[同様の報告多数](https://www.reddit.com/r/homeassistant/comments/xt38qj/google_assistant_integration_suddenly_not_working/) 。
対策ですが、[この記事](https://peyanski.com/connecting-cloudflare-tunnel-to-home-assistant/)のとおりにfreenomとcloudflareを使ってDNS設定することで安定稼働できています。ルーターのポート開放とかも不要になりました。freenomで `利用不可` って出る場合は[ここを](https://qiita.com/larvestiny/items/e0e65c6c74f6c641a88b)。


>※※※追記：家族のGoogleアカウントからもHome Assistant連携することは可能ですが、なぜか接続エラーが頻発しました。
そこで代表者1名が連携して「家の部屋」にHome Assistantの家電を追加しておき、家族には家のメンバーとして家電を共有する形にしたところ安定しています。

部屋は「リビング」とか「寝室」とか選べるんですが、それだと同じ部屋の既存の家電とまとめられてしまって使いづらかったりしたので、
うちでは存在しない部屋（地下室）にHome Assistant家電を全部ぶちこんでいます。
</div>


<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31679/3d5fac0a-c04f-60b4-316c-8460d401dfdd.png" alt="Googleアシスタントへの命令"> <img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31679/5045ea6e-86d1-3b43-424a-e20628d0d4d1.png" alt="家Botへの記録">
↑実際の記録風景


## サンプル設定ファイル集

Home Assistantの設定ファイルを正しく書くのは最初ちょっと苦労します。
私が調べたり試行錯誤した結果のサンプルを置いておきます。だいたいこんな感じで数値入力エアコンくんは動くはず。


```yaml:configuration.yaml
automation: !include automations.yaml
climate: !include climates.yaml
rest_command: !include rest_commands.yaml
google_assistant: !include google_assistant.yaml
```


```yaml:climates.yaml
# 独自エアコン定義
- platform: generic_thermostat
  name: child_body_temperature
  unique_id: fugafuga
  # 実際のデバイスは存在しないので、嘘のヒーターIDとセンサーIDを適当に指定
  heater: switch.dummy_heater
  target_sensor: sensor.dummy_temperature
  initial_hvac_mode: "heat"
  min_temp: 32
  max_temp: 42
  target_temp_step: 0.1
```



```yaml:automations.yaml
# エアコン操作時にAPIを叩くオートメーション
- id: hogehoge
  alias: 体温セットされたら記録実行
  description: ""
  mode: single
  trigger:
    - platform: state
      entity_id: climate.child_body_temperature
  condition:
    # 30度以上で記録開始
    - condition: numeric_state
      entity_id: climate.child_body_temperature
      above: 30
      attribute: temperature
  action:
    - service: rest_command.record_body_temperature
      data:
        body_temperature: >
          {{ state_attr('climate.child_body_temperature', 'temperature') }}
    - service: climate.set_temperature
      # そのままだと同じ計測値が連続したとき記録できないので、毎回値をゼロにリセットしておく
      data:
        temperature: 0
      target:
        entity_id: climate.child_body_temperature
```



```yaml:rest_commands.yaml
# 体温記録API
record_body_temperature:
  url: 'https://path/to/temperature/recorder/api'
  method: POST
  headers:
    Content-Type: "application/json"
    Authorization: !secret my_api_token
  payload: '{ "value":"{{ body_temperature }}" }'
```


```yaml:secrets.yaml
my_api_token: Bearer hogehogehoge
```


```yaml:google_assistant.yaml
project_id: myid12345
service_account: !include service_account.json
report_state: true
expose_by_default: true
exposed_domains:
  - climate
entity_config:
  climate.child_body_temperature:
    name: 子供の体温
    aliases:
      # 聞き間違えにも反応できるようにする
      - 子供の太陽
```




# あとがき&余談
みなさんは、家電操作のための詠唱をIFTTTを使って短縮する、みたいなこと やってませんでした？
あるいは、Google Homeが聞き間違えて変な応答を返すのが嫌になり
IFTTTで「何もしないアプレット」を作って、特定ワードにはアシスタントが反応しないよう応答を潰したりとか？

**それ、Home Assistantで実現できます。**

しかもGoogleアシスタント設定の「ショートカット」「ルーチン」を使わずに可能です。
これはつまり、もう家族全員分のルーチン設定をメンテして回らなくていいってことです。

やり方は簡単で、まずそういう名前（短縮詠唱なり潰したい命令文なり）のカスタム家電を作ります。
家電操作の詠唱短縮なら、アクションに[RESTCommand](https://www.home-assistant.io/integrations/rest_command/)を指定してNature Remoとかの好きなAPIを叩きます。
命令文を潰すなら「何もしないイベント」を作って呼び出します。

これを使うと、Googleアシスタント側の予約語と衝突した場合もHome Assistantの家電名が優先されます。
つまりアシスタントの挙動を上書きできるので、好き勝手やるのに持ってこいの状態となります。

他にも色々遊べるので、IFTTT難民の方はぜひHome Assistant導入されるとよいと思います。
私はこれを使って126あったIFTTTのアプレットを2つにまで減らしました。
IFTTTの障害とか遅延で家電操作できなくなる現象からも開放されるのでハッピーです。

ではでは 素敵なIoT生活を。