さいきん流行りの IoT 機器。 多くの家電がネットからコントロールできるようになった。 IFTTT を使うと、 そういった機器を手軽に連携できるので便利。 IoT 機器同士だけでなく、 (私が管理する) WWW サーバを IFTTT がアクセスするように設定したり、 あるいは逆に私のサーバが IFTTT をアクセスする (トリガーを送る) こともできるので、 思いのままに IoT 機器を制御できる。
例えば、 人感センサで照明を点灯/消灯させる場合、 防犯用ライトなら人の動きを感知したときだけ点灯し、 人の動きが無くなれば速やかに消灯する、 といった単純なルールで充分だが、 部屋の照明となると人の動きが無くなったからと言ってすぐに消されては困る。 部屋を退出したことを確認してから消灯して欲しいし、 時間帯、あるいは在宅/不在時に応じて (さらにはその時々の天気に応じて)、 適切な点灯/消灯制御を行いたい。
つまり、 部屋の外にも人感センサを設置し、 部屋の中で人の動きが検知できなくなった後、 部屋の外で人の動きを検知すれば、 部屋を出ていったと判断し部屋の照明を消灯する。 さらに、 特定のスマホが LAN (家庭内 Wi-Fi) に接続していないときは不在とみなし、 部屋の外で人の動きを検知しただけでは照明を点灯しないけど、 そのスマホが LAN に接続した直後は帰宅したとみなし、 夜間であれば人の動きを検知したら速やかに点灯するなど。 私自身のサーバ (以下、「自サーバ」と略記) を IFTTT と連携させれば、 いくらでも複雑な制御ルールを設定できる。
IFTTT (IF This Then That) は、 その名の通り特定の条件 (This) が満たされたとき特定の動作 (That) を行わせることができる。 IoT 機器の多くは IFTTT との連携をサポートしているので、 例えば「This」として、 「人感センサが人の動きを検知」を設定し、 「That」として、 「照明をオン」を設定すれば、 単純な防犯用ライトが実現できる。
この連携に自サーバを絡めるには、 「This」および「That」を自サーバと結び付ければ良い。 それには IFTTT の 「webhooks」を用いる。
「This」は、 IFTTT の特定の URL をアクセスするだけ。 例えばこんな感じ:
senri:~ $ curl https://maker.ifttt.com/trigger/light_on/with/key/dD-v7GCx46LnWaF1AD9nwSUeA_N1ALvDHKS57cP1_Md Congratulations! You've fired the light_on event
「light_on」の部分は任意に定めることができる。 「with/key/」以降の部分はユーザごとに IFTTT が割当てる認証用キー。 このキーが他人に漏れると勝手に操作されてしまうので適切な管理が必要。 そして、 「https://maker.ifttt.com/trigger/light_on/with/key/... へのアクセスがあった」(This) ならば、 「照明をオン」(That) を行う、 というルールを設定することで、 自サーバから照明を点灯させることが可能になる。
いっぽう 「That」 は、 IFTTT に自サーバをアクセスさせる。 例えば 「https://www.gcd.org/ifttt へ POST メソッドでアクセス」させる。 POST の body として json データを送るよう設定することができて、
{"magic": "0svikYKbcsxDbkty", "type": "Motion detected", "CreatedAt": "{{CreatedAt}}", "DeviceName": "{{DeviceName}}"}
などと設定する。 「"magic": "0svikYKbcsxDbkty"」は認証用。 https://www.gcd.org/ifttt は誰でもアクセスできるので、 "magic" の文字列が一致しないリクエストは無視する。 「"type"」は 「This」の機器の種類 (この例では人感センサ) を伝えるために設定。 「{{CreatedAt}}」と 「{{DeviceName}}」は、 「This」の機器が IFTTT へ送信したデータ。 例えば人感センサが検知 (This) すると IFTTT が次のようなアクセスを www.gcd.org へ行ってくれる (That)。
POST /ifttt HTTP/1.1 Content-type: application/json host: www.gcd.org content-length: 134 x-newrelic-id: ZW1uPtmAO9tRDSFGGvmp x-newrelic-transaction: VGhpcyBpcyBmYWtlIHgtbmV3cmVsaWMtdHJhbnNhY3Rpb24uCg== Connection: close {"magic": "0svikYKbcsxDbkty", "type": "Motion detected", "CreatedAt": "November 19, 2019 at 09:15AM", "DeviceName": "廊下センサ" }
この IFTTT からのアクセスを受信することで、 人感センサが人の動きを検知したことを自サーバが知ることができる。 そして自サーバにおいて様々な条件を加味した後、 前述した 「https://maker.ifttt.com/trigger/light_on/with/key/...」 へアクセスすれば照明を点灯することができる。
以上で、 IoT の連携に自サーバを絡ませることができるようになった。 ところがこの方法は、いかんせん遅い。 人感センサ ⇒ IFTTT ⇒ 自サーバ ⇒ IFTTT ⇒ 照明 などと IFTTT とのやりとりを 2度も行うため、 人の動きを検知してから照明が点灯するまで 6秒ほどかかってしまう。 部屋に入るまで 6秒も待てないので、 暗いままの部屋に入る羽目になる。 なお、 点灯するのは素早さが肝要だが、 消灯するのは数秒程度の遅れなら全く問題にならない。
まず後半 「自サーバ ⇒ IFTTT ⇒ 照明」 の IFTTT をショートカットする。 私は自室の照明を点灯/消灯するために IoT 対応赤外線リモコン Nature Remo-1W2 を使っている。 赤外線でコントロールできる家電ならなんでも IFTTT で連携できるので便利。
Remo は IFTTT との連携ができるだけでなく、 同じ LAN 内にある自サーバから直接アクセスできる (Local API)。 例えば次のように http://Remo-XXXXXX.local/messages (「XXXXXX」は Remo の個体ID) へ POST メソッドでアクセスすると Remo から赤外線が発射され、 部屋の照明を点灯させることができる。 ただし Remo-XXXXXX.local の名前解決には Bonjour (DNS Service Discovery, RFC 6763) が必要。私はメンドーなんで LAN のネームサーバに固定アドレスで登録済。
senri:~ $ curl -X POST "http://Remo-XXXXXX.local/messages" -H "Accept: application/json" -H "X-Requested-With: curl" -H "Expect: " -d '{"format":"us","freq":36,"data":[3353,1720,402,1288,400,...中略...,460,402,449,383]}'
ただし Remo はアイドル状態が続くとスリープに入る。 スリープ状態だと http アクセスに反応しないので、 アクセスに失敗した時はリトライしなければならない。 私はメンドーなんで 1分に一度くらいの頻度で Remo に GET メソッドでアクセスを行って、 スリープ状態へ入らないようにしている (ping を打ち続けてもスリープになることを防げない)。
POST の body 中の "data" は「中略」しているが、実際には 539個の数字が並ぶ赤外線の波形データ。 昔懐かしい PC用学習リモコン (13年前! 当時は IoT なんて言葉は無かった) と仕組み的には同じで、 家電付属のリモコンが発射する赤外線を受信して記憶し、 同じ波形を発射することができる。 まず、 家電付属のリモコンを Remo に向けて「点灯」ボタンを押すと、 「点灯」ボタンに対応する赤外線の波形が Remo に記憶される。 次に、 http://Remo-XXXXXX.local/messages へ GET メソッドでアクセスすると、 記憶された赤外線波形データが返される:
senri:~ $ curl -i "http://Remo-XXXXXX.local/messages" -H "Accept: application/json" -H "X-Requested-With: curl" HTTP/1.0 200 OK Server: Remo/1.0.77-g808448c Content-Type: application/json {"format":"us","freq":37,"data":[3353,1720,402,1288,400,...中略...,460,402,449,383]}
この波形データをそのまま POST メソッドで送信すれば、 Remo から (家電付属のリモコンと) 同じ赤外線が発射される仕組み。 これで後半 (自サーバ ⇒ IFTTT ⇒ 照明) の IFTTT をショートカットして、 人感センサ ⇒ IFTTT ⇒ 自サーバ ⇒ 照明 という経路になって 2秒ほど短縮できた。 でもまだ 4秒もかかっている。 残る前半の IFTTT もショートカットできないか?
あいにく私が使っている人感センサ DCH-S150 には Remo みたいな Local API は無さそう。 しかも既に製造中止でファームウェアのバージョンアップも期待できそうにない。 仕方ないので人感センサが IFTTT (正確に言えば D-Link のサーバ) とどんな通信を行っているか tcpdump で調べてみた。
人感センサが動きを感知すると、 まず LAN のネームサーバ 192.168.18.1 に対して api.dch.dlink.com. の A レコードの問合わせを行い、 その 0.1 〜 0.4 秒後に 54.200.253.23 (api.dch.dlink.com.) へ https アクセスしている。
11:02:00.282700 IP (tos 0x0, ttl 64, id 28255, offset 0, flags [DF], proto UDP (17), length 63) 192.168.18.134.52276 > 192.168.18.1.53: [udp sum ok] 84+ A? api.dch.dlink.com. (35) 11:02:00.401084 IP (tos 0x0, ttl 64, id 9685, offset 0, flags [DF], proto TCP (6), length 60) 192.168.18.134.33116 > 54.200.253.23.443: Flags [S], cksum 0x7197 (correct), seq 2008959912, win 5840, options [mss 1460,sackOK,TS val 60124797 ecr 0,nop,wscale 1], length 0
動きを感知したら直ちに照明を点灯させたいので、 0.1 〜 0.4 秒とはいえできればネームサーバ問合わせの段階で点灯させたい。 ところが、 動きを感知していないときも 30分に一度くらいの頻度で api.dch.dlink.com. の A レコードの問合わせを行っているようだ。 人の動きがないのに照明が勝手に点灯するのはよろしくないので、 api.dch.dlink.com. への https アクセスがあったら、 Remo に「点灯」の赤外線を発射させるようにしてみた。 なお、人感センサが https アクセスしたことを自サーバで知るには、 iptables の NFLOG を使うと簡単に実現できる。
これで 人感センサ ⇒ 自サーバ ⇒ 照明 という経路になって、 さらに 2秒ほど短縮できた。 まだ 2秒ほどのタイムラグがあるが、 人感センサが動きを検知するのに 1秒ほどかかっていて、 Remo の Local API をアクセスしてから赤外線を発射して実際に照明が点灯するまでにも 1秒ほどかかっているようなので、 これ以上の短縮は無理?。
IFTTT は便利だが、 便利さを追求していくと IFTTT を使わない方が実用になる、というオチとなった。 IoT 機器は、 Nature Remo のような Local API を公開しているものを使うようにしたい。