ちぎっては投げるブログ

Programming, Android, RaspberryPi, Digital Devices, Kinkuma Hamster...

Amazon Echoで話しかけた言葉をIFTTTに通知する方法

この記事はスマートスピーカーIFTTT 大喜利 アドベントカレンダー2017の18日目です(前日の21時に急遽登録しました)。

qiita.com

Echoに話しかけた内容を取得できない?

Amazon Echoは、前回のAmazon EchoとRaspberry Piで部屋の照明を制御する #おうちハック - ちぎっては投げるブログで使ったように、IFTTTと連携することで出来ることの幅が大きく広がるが、ひとつ不満がある。 それは、任意の言葉をトリガーにできるが、その言葉やその言葉の一部を取得する方法がないという点である。

f:id:mczh:20171217210409j:plain

これが出来ると、話しかけた内容を他のサービスに食わせたり、そのうち数字部分だけを使って処理したりと多様な処理が可能になるのだが、なぜだかEchoのIFTTTトリガーは入力の取得に対応していない。(おそらく。ちなみに、Google Homeでは出来るようだ)

そこで、なんとかならないか?と考えて思いついたハック的な方法は、「やることリスト」もしくは「買い物リスト」を流用する方法である。

やることリスト、買い物リストはIFTTT側で入力された言葉を[AddedItem]として使用できる。これによって入力を取得し、他のサービスに投げればよい。

前回のBeebotteのBodyに使えばRaspberry Piにも通知できるので、いろいろ夢が広がる。

デメリットとして、「やることリスト」もしくは「買い物リスト」が本来の意味を失う。

ツイートするIFTTTレシピ例

とりあえず、話しかけた内容をTwitterに投稿するレシピのスクショを以下に示す。

f:id:mczh:20171217210411j:plain

これでAlexaに「こんにちは を買い物リストに追加して」と話しかけると、Twitter側には、

「こんにちは」ってアレクサに話しかけたなう

と表示されることになる。

Slackの無料枠を最大限利用するために、特定チャンネルのメッセージを定期削除するようにした

この記事はSlack Advent Calendar 2017の17日目の記事です。

Slack Advent Calendar 2017 - Qiita

Botを作ると言ったな、あれは嘘だ

f:id:mczh:20171216225030p

Botの定義とは?という話もあるが、いわゆる話しかけるとそれに答えてくれるものをbotとするなら、今回の記事はbotではない。だが、自動でなにかしてくれるのは全部botと呼ぼうよ、と思えばbotを作ったと言えなくもない(詭弁)。  

古いメッセージを削除するbotを作る

Slackは無料での利用だと、最大1万メッセージまで利用できる。1万メッセージを超えると、古いものから閲覧検索が出来なくなる。(削除されるわけではないので、有料プランにするとまた見られるようになる)  

情報を貯めておくチャンネルと垂れ流すチャンネルがある

私のSlackの運用は、色々話をする雑談チャンネル系と、共有カレンダー等の情報を自動で流すお知らせチャンネル系と、保存しておきたい情報をメモしておく保存系チャンネルに分かれている。

  無料枠である1万メッセージを超えてしまうと、保存しておく必要のないチャンネルメッセージも、保存しておきたいチャンネルメッセージも区別せずに古い順に見えなくなっていく。  

そこで、雑談チャンネルや自動投稿チャンネルについて、一定期間より前の投稿を自動削除する仕組みを作ることで、保存しておきたいチャンネルのメッセージ寿命の延命を試みる。    

Slack Appを作成する

Slack API: Applications | Slack

Create New Appからアプリケーションを作り、トークンを取得する。  

特徴で「権限」を選択し、「スコープ」でそれっぽい権限を付与していく。 ここで、「それっぽい」がよくわからなかったので、適当に付与したが、実際には何が必要で何が不要だったのかの検証までは出来ていない。 おそらく、  

ユーザーのパブリックチャンネルにアクセスします
channels:history
ユーザーのパブリックチャンネルに関する情報にアクセス
channels:read
あなたのパブリックチャンネルを修正
channels:write

あたりが必須だと思う。  

Rubyスクリプトの作成

  削除は、以下のRubyスクリプトで行う。 Gemとして、slack-apiを使っているので、

gem install slack-api

が必要。  

require 'slack'
  Slack.configure do |config|
  config.token = "取得したトークン"
end
# name=>day
channel_names = {"チャンネル1" => 何日分を残すかの日数, "チャンネル2" => 以下略}
channel_backup_names = ["バックアップを書き出しておきたいチャンネル名"]
client = Slack::Client.new
channel_names.each{|channel_name, date|
  puts "#{channel_name},#{date}"
  File.open("/tmp/#{channel_name}.txt", "a") do |f| 
    channel = client.channels_list["channels"].find { |c| c["name"] == channel_name }
    begin
      history = client.channels_history(channel: channel['id'], latest: (Time.now - (60 * 60 * 24 * date)).to_i, count: 1000)['messages']
      history.each do |h|
        sleep 2
          if channel_backup_names.include?(channel_name)
            f.puts history.to_json
         end
         puts client.chat_delete(channel: channel['id'], ts: h['ts'])
       end
  end while !history.empty?
end
}

  必要なのはトークンと、チャンネル名と、そのチャンネルのメッセージを何日前まで残すかの情報である。

sleepを入れないとAPIアクセスしすぎでアクセス制限がかかるので、ちゃんとwaitしながら負荷をかけないように気を付けよう。

一応、/tmpの下にバックアップとして書き出している。/tmpの下なので放っておくと消えるので、とりあえず必要ならDropboxにアップロードしておくことにする。   

バックアップ周りは無駄なファイルオープンをしているので直したいが、この記事はアドベントカレンダーの締め切り前日に書いているのでひとまず後回しにする。  

Dropboxへのアップロードは以下を参考。   denpa-shinbun.com  

メッセージ数の変化

実行後にメッセージ総数が減るかを確認したが、減っていない。

調べたところ、最大48時間かかるとの記述を見つけたので後日確認したところ、ちゃんとメッセージが減っていることを確認した。

  このスクリプトをcronで動かすようにすれば、定期削除の完成である。

crontab -e

  ところで、privateチャンネルのメッセージは削除できなかった。権限の問題かと思い、いろいろ試してみたのだが、成功していない。

Amazon EchoとRaspberry Piで部屋の照明を制御する #おうちハック

この記事は、おうちハック Advent Calendar 2017の6日目の記事です。

qiita.com

おうちハックと私

一人暮らしを始めてから、部屋を好き勝手いじれるようになったこともあり、スマートホームに憧れていろいろと自作してみた時期がある。

実際に作ってみたもの

  • 朝に天気予報を喋ってくれる
  • 朝になると電気をつけてくれる
  • 朝に出かけると電気を消してくれる
  • 帰ってくると電気をつけておかえりなさいと言ってくれる
  • ごみの日に今日は燃えるごみの日だよ、と教えてくれる
  • ハムスター監視システム

お喋り系は、喋るだけでなく対話もやってみようと思い、マイクからGoogleの認識エンジンに投げるスクリプトまでは作ったが、うまく運用できていない。 また、ハムスターを飼っているので、夜中にちゃんと活動しているのか?を監視するシステムを作った。(ハムスターは夜行性)

上記のシステム群は、全部Raspberry Piで実装した。電子工作はまったくできない人間なので、単なる24h起動可能なLinuxマシンとして便利に利用している。

作りたかったけどまだ出来ていないもの

  • 話しかけると応答がある
    音声認識まではやったんだけど…
  • 冷房の自動ON/OFF
    電子工作出来ないなりにセンサーで温度を取得まではやったんだけど…
  • ハムスターの自動給餌
    モーター買うまではやったんだけど…
  • これらの設定を管理するWEBサイト

今回の記事では照明の制御を書くはずだった

上記に書いたように、照明制御はボチボチ作っているので、今回のAdventカレンダーはそれをネタに書こうと思っていた。

ところが、ちょうどよく5日ほど前にAmazon Echo Dotの招待メールが届いた。せっかくなので、Amazon Echoからスマートホームの仕組みに対応していない「一般的なシーリング」をつけたり消したりする仕組みをラズベリーパイで作ろうとしてみたところ、あっさりできたので、今ホットなEchoネタをAdventカレンダーネタにしようと思う。

f:id:mczh:20171205222218j:plain

Amazon Echo(日本語版)とRaspberry Piの通信

すでにRaspberry Piでの赤外線通信によるシーリングライトのON/OFF制御の仕組みは作っていたので(詳細は後述)、なんとかしてAmazon EchoからRaspberry Piに通知を送信できればやりたいことが実現できる。

Amazon Echo => Raspberry Pi =赤外線=> シーリング

Amazon Echoスキルの自作検討

しっかりと調べたわけではないのだが、基本的にAlexaのスキル(=アプリ)は、AWSのLambda経由で動かすようだ。 Lambdaというのは、登録したスクリプトを あるトリガーによって実行してくれるAmazonのクラウドサービスのひとつである。 AWS Lambdaの利用は、一定条件までは無料であるが、AWSのサービスを使うのはうっかりして無料枠を超えて課金してしまいそうで、貧乏な個人ではあまり使いたくない。(AWSはクレカ登録が必要だったと思う)

既存サービスの利用検討

なので、自作のスキルを作る、という作戦はいったん諦めて、既存のスキルやサービスの組み合わせでの実現を考えた。

既存サービスを調べてみると、AlexaはIFTTTのトリガーに対応していることがわかった。さらに、IFTTTは日本語でEchoに話しかけてもちゃんと扱える。これなら、Echoに日本語で話しかけて、IFTTTのアクションを起こすことが出来そうだ。

IFTTTとは?

IFTTTは、トリガーとアクションを定めると自動実行してくれるサービスである。 トリガーやアクションは豊富に提供されている。今回は、トリガーにAlexaを用いる。

問題はアクションで、IFTTTには大量のアクションがあるが、個人のLANに設置されたRaspberry Piにアクセスするアクションは当然ない。

代替手段としては、

  1. Raspberry Pi側をサーバ化し、IFTTTからRaspberry Piに通知を行う
  2. IFTTTは外部サーバに通知し、Raspberry Piは外部サーバにデータを取りに行く

の2パターンが思いつく。

1.のほうは外部サービスは不要であるが、Raspberry Piのサーバ化が必要であり、だいぶ面倒である。 外部からアクセスできるように固定IPにするかDDNSに登録するかが必要である。さらにルータのポートを開ける必要もあり、セキュリティや脆弱性対策の保守なども気になってくる。

一方、2.は外部サービスこそ必要なものの、Raspberry Piが自分から外に取りに行くため、Raspberry Piに対して外からのアクセスは不要である。よって、セキュリティ面のリスクを低くすることができる。そのため、今回は面倒なこと考えずにさくっと作りたいので2.を採用する。(アドベントカレンダーになんとか間に合わせる必要もある)

Raspberry Piが外部サービスからデータを取ってくる

私がこうした実装を雑にやるためによくやる方法は、TwitterやGmail、Dropboxなどの有名どころのサービスを間借りする方法である。

IFTTTのアクションには有名どころはだいたいあるので、たとえば「Dropboxに特定ファイルを追加する」をアクションとすれば、Raspberry PiはひたすらそのファイルがDropboxに追加されるのを監視すれば、一応の通信は実現できる。Dropboxのファイルが存在するかしないかが通信手段となる。

イメージとしては、以下のようになる。

Amazon Echo => IFTTT =ファイル追加=> Dropbox <=ファイル監視&削除=>Raspberry Pi =赤外線=> シーリング

今回もこの超雑実装で行こうかと思ったが、IFTTTについて調べているうちに、もっと良さそうなサービスがあることを知ったので、そちらを試すことにした。

Beebotte

その良さそうなサービスが、Beebotteである。

Beebotte

Beebotteは、MQTTというプロトコルにおけるブローカーの役割を果たしてくれるサービスである。

50,000 Messages/dayの条件で無料で使えるので、そんなに何度もEchoに話しかけるわけもなく十分すぎる無料枠である。

MQTTとは?

MQTTは、IoT機器向けに作られたプロトコルで、センサーからサーバにデータを送信するような場合を想定した作りになっている。

送信側はPublisherとして情報を送信し、中継するサーバであるBrokerを経由して、受信側であるSubscriberが情報を取りに行く。(私の解釈ではそう思っている、あっているだろうか?)

qiita.com

今回のシステムでいえば、IFTTTがPublisherであり、Raspberry PiがSubscriberになる。

通信のイメージとしては、以下のようになる。

Amazon Echo => IFTTT => Beebotte <=>Raspberry Pi =赤外線=> シーリング

Beebotteの設定

サインアップ&ログインしたら、Channelを作る。

f:id:mczh:20171205183524p:plain

ここでは、

  • channel: ifttt
  • resource: data

としたが、自分でわかりやすい文字列なら何でもよい。

SoS(Send on Subscribe)というのは、図の右にも記載されているが、SubscriberがオフラインのときにPiblisherからBeebotteへ送信されてきたデータを保存しておき、Subscriberがオンラインになったときに(on Subscribeなときに)、送信(Send)する機能である。情報の取りこぼしを防ぐ仕組みだと思うが、今回は不要である。

設定したらChannel Tokenが取得できるのでメモしておく。

IFTTTの設定

続いて、IFTTTの設定である。

サインアップ&ログインしたら、新しいAppletを作る。

IFTTT helps your apps and devices work together - IFTTT

f:id:mczh:20171205183926j:plain

f:id:mczh:20171205183944j:plain

this(トリガー)にAlexaを設定し、認識させたい文章(=電気消して)を登録する。

f:id:mczh:20171205184332p:plain

lower case(小文字)となっているが、日本語も入力できる。

続いて、that(アクション)にWebHookの設定をする。

f:id:mczh:20171206185933p:plain

同様にして、「電気つけて」アプレットも作成する。BodyのOFFの部分はONにする。

この設定は、Beebotteの指定したチャンネルに対して、Bodyで記載したJSON形式のデータを送信(Post)する処理を意味する。

Raspberry Piの設定

irMagicianを使った照明制御

Raspberry PiにirMagicianを接続する。

irMagicianはUSB接続の学習リモコンだと思えばよい。組み立て済みで、半田もブレッドボードもいらず、ただUSBに挿すだけで使えるため、電子工作出来ない勢の強い味方である。

ちなみに、irMagicianの接続ケーブルは以下のフレキシブルケーブルがおすすめである。

非常に固く、照明に向けて赤外線を発信する向きの固定ができる。

我が家は棚の金網部分に結束バンドでRaspberry Piをぶら下げているので、以下のような配置になっている。

f:id:mczh:20171207211622p:plain

irMagicianのコマンド

irMagicianは、以下のリポジトリのコマンドで簡単に制御できる。

github.com

irMagicianにシーリングの赤外線リモコンを向けて、

python irmcli.py -c

で赤外線を記録し、

python irmcli.py -p

で再生されるので、動作を確認する。

問題がなければ、

python irmcli.py -s -f light_on.json

でファイルに書き出すと、

python irmcli.py -p -f light_on.json

でいつでも再生できる。

このjsonを照明オンとオフ用の両方を保存しておく。

Beebotteからデータを受信するRubyスクリプト

Rubyで書くのが楽なので、Rubyで書いた。 Rubyのインストールは各々で調べて実施してほしい。

私の環境は以下。

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [armv7l-linux-eabihf]
$ which ruby
/home/pi/.rbenv/shims/ruby

また、beebotteのGemを使っているのでインストールする。

$ gem install beebotte

以下がスクリプトである。

require "rubygems"
require 'beebotte'

s = Beebotte::Stream.new({token: "取得したチャンネルのトークン"})
s.connect()

s.subscribe("ifttt/data")

s.get { |topic, message|
  if message.include?("ON")
    `python /home/pi/irmcli/irmcli.py -p -f /home/pi/irmcli/light_on.json`
    puts "ON"
  elsif message.include?("OFF")
    `python /home/pi/irmcli/irmcli.py -p -f /home/pi/irmcli/light_off.json`
    puts "OFF"
  end
  puts "Topic: #{topic}\nMessage: #{message}"
}

見ての通り、Beebotteに対してSubscribeして、getでデータの受信を待ち、受信したらBodyに記載された内容にON/OFFを含むかを判定し、irMagicianのコマンドを叩いているだけである。

実行結果を以下に示す。

$ ruby receive_beebotte.rb
#ここでアレクサに話しかける
ON
Topic: ifttt/data
Message: {"data":"ON","ts":1512468551686,"ispublic":false}
# ここでアレクサに話しかける
OFF
Topic: ifttt/data
Message: {"data":"OFF","ts":1512468572795,"ispublic":false}

無事に取得できているようだ。

常駐させる

さて、ここまで出来たら、このスクリプトをサービス化して常駐させる。

$ sudo vi /lib/systemd/system/receive_beebotte.service
[Service]
ExecStart=/home/pi/.rbenv/shims/ruby /home/pi/irmcli/receive_beebotte.rb
Restart=always
Type=simple

[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload # 再読み込み
sudo systemctl start receive_beebotte.service # サービスの開始
sudo systemctl enable receive_beebotte.service # 自動起動の有効化

この状態でEchoに話しかけて照明がオンオフ出来れば成功である。

実際の動画

完成品の動画は以下。三脚を使わなかったので手ブレがひどい。

音声の読み上げはGoogle翻訳を使っている。

ここでお気づきのことと思うが、「トリガー」という文言が語尾に必要である。これは、IFTTTの仕様なのでどうしようもない。

本来は、語尾に「トリガー」がついても違和感のない言葉を選ぶべきだっただろう。たとえば、「電気消してトリガー」よりかは、「ライトオフトリガー」のほうがまだマシに思える。

おわりに

以上、と言いたいところだが、なぜか再起動後にサービスが自動起動しないのであとで調べる。

最近すっかり更新せずにいたため、やる気を出すために初めてAdventCalendarに参加したが、時間がなくて雑な記事になってしまった。

時間を見つけて微修正していく予定である。

また、SlackのAdventCalendarにも登録しているので、そちらに向けても作業をしていく。

アドベントカレンダーにコメントしていたペット監視の話は?

Adventカレンダーに登録した時点ではまだ記事を書いていないつもりでいたが、ブログを読み返したらすでに書いていた…。

denpa-shinbun.com

何も追記しないのも微妙なので、実際の監視動画を本記事に載せることでお茶を濁す。

おわかりいただけただろうか…? カメラの固定位置に悩んだ結果、ドアップとなってしまった。 とはいえ元気なようで、何よりだ。