スマートスピーカ

2018年08月08日

Google Home でローカルの MP3 ファイルをプレイリスト再生する方法

はてなブックマークに登録

前回の記事では ほりひろ 様による esp8266-google-home-notifier ライブラリと ESP32 ボード (秋月版)を使って構成した Google Home 用キャストエージェントとその利用例を紹介しました。記事ではあわせて同ライブラリをプライベートにカスタマイズした内容として以下を挙げました

  1. 元の google-home-notifier に存在する ip 関数を追加
  2. 元の google-home-notifier に存在する play 関数 (public)を追加
  3. play 関数による Google Home での MP3 データ再生終了を検知するためのコールバック機構を追加

今回はみっつめの話題について紹介します。

話のきっかけ

Google Home Mini で Spotify など試しているうちに、昔買った音楽 CD をこういう手軽さで聴くことができればと思うようになりました。そのためには楽曲データを MP3 形式等に変換して Google Play Music へアップロードすれば無料で実現できると随所で紹介されています。なかなか魅力的なサービスではありますが、その一方で Google 側の利用規約とのかねあいが気になりました。

手元では日常的に Google の多くの優れたサービスの便宜を享受しており、ひとりのユーザとして同社には敬意と信頼感を持っています。しかし、自分にすべての権利のあるデータであればともかく、自分には所有権しかない楽曲のデータを丸ごとこの規約のもとへ預けることには個人的にためらいがありました。そこで注目したのが google-home-notifier の MP3 データ再生機能です。

google-home-notifier には URL で指定された MP3 データを Google Home へキャストすることのできる play 関数が用意されています。そのため、LAN 上にローカル Web サーバをを立ててそこへ所定の MP3 データを配置し、そのアドレスをこの関数へ渡せば手元の環境からデータを持ち出すことなく Google Home で再生することが可能です。このやり方なら市販の楽曲に対する自分の権利の範囲を踏み越える心配はないでしょう。

ただ、play 関数は一件のデータをワンショットでキャストすることを前提とするもので複数データの連続再生には対応していません。そのため何曲かを順番に流すといったことはできないのが残念です。そこで、この部分に手を加え、所定のプレイリストを参照して複数の MP3 データの順次再生・シャッフル再生ができるようにしてみました。

まずその動作の様子を動画で紹介します。自宅では向かって左側の Raspberry Pi Zero W をローカル Web サーバとして運用しています。このデモでは soundorbis 様によるフリージングル曲を使用させて頂いています。

動画:1分15秒
  

ちなみにここでは以下の IFTTT アプレット (クリックで可読大表示) 経由でローカル Web サーバから再生リスト「demo.txt」内容を読み込んで処理を行っています。
トリガー
アクション

再生リストとその周辺の話題は末尾の「エージェントプログラム」の項に記述しています。
以下、実現に至るまでの経緯を控えます。

対応方法の調査

google-home-notifier の処理内容

まず google-home-notifier の処理を追うことから始めました。以下はそのまとめです。esp8266-google-home-notifier はオリジナルの google-home-notifier が堅実に移植されたライブラリであるためここでは後者をターゲットとしています。

初期設定
  • 呼び出し側から ip() 関数により対象とする Google Home デバイスの IP アドレスと言語が指定されればそれを設定
  • 呼び出し側から device() 関数により対象 Google Home デバイスのデバイス名と言語が指定されればそれを保持

主処理

  • notify() 関数は渡されたテキストを所定の言語で TTS 処理した結果の MP3 ファイルの URL を対象 Google Home デバイスへキャストする
  • play() 関数は指定された MP3 ファイルの URL をを対象 Google Home デバイスへキャストする
  • notify(), play() はいずれも呼び出された時点で対象 Google Home デバイスの IP アドレスが未設定状態であれば前出 device() 関数経由で保持しているデバイス名で mDNS 照会により対象 Google Home デバイスの IP アドレスを得る  =>  GitHub: mdns

キャストの手順

注目すべき点と考え方

ポイントは、上記「キャストの手順」の最後の「load() が終わったら各仮想チャネルを close してコネクションを切断」という部分にあります。該当箇所のソースコードを引用します。

  • google-home-notifier.js (#L88-L96)
                  :
    var onDeviceUp = function(host, url, callback) {
      var client = new Client();
      client.connect(host, function() {
        client.launch(DefaultMediaReceiver, function(err, player) {
    
          var media = {
            contentId: url,
            contentType: 'audio/MP3',
            streamType: 'BUFFERED' // or LIVE
          };
          player.load(media, { autoplay: true }, function(err, status) {
            client.close();
            callback('Device notified');
          });
                  :
    

このように、Google Home 側の DefaultMediaReceiver へ再生対象データの引き渡し(player.load())を終えるとプログラムは直ちにデバイスとの接続を閉じそのまま終了しています。あとは Google Home まかせということになりますが、Google Home はデータ再生中に別のデータのキャストが指示されると現在の再生の終了を待つことなくあっさり新しいデータの再生を開始します。そのため、単に google-home-notifier を繰り返し実行しても、その都度データ再生が中断され別データの再生に移るだけで、プレイリスト再生のように「一件の再生が終了〜次のデータを再生」という動きにはなりません。

一方、手元で上記の player.load() 後も接続を維持した状態で観察したところ、Google Home 側でデータ再生が終了すると前掲の仮想チャネル「urn:x-cast:com.google.cast.media」経由でデバイス側からプログラムへ下記要領のメッセージが送出されることを確認しました。

{"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"IDLE","currentTime":0,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"currentItemId":1,"idleReason":"FINISHED"}],"requestId":0}

このように、接続を維持していればプログラムはデバイス側の再生終了の捕捉が可能であることがわかりました。 この件を含めここまでの一連の事情を踏まえると、google-home-notifier でデータの連続再生を実現するために必要な措置は大きく次のふたつであることがわかります。

  • デバイスへの再生指示を終えたあともデバイスとの接続を維持する
  • デバイスから上記の再生終了メッセージを受け取ったら次のデータの再生を指示する

なお、Google Cast の仕様にはデータの連続再生を行うためのネイティブの機構が存在するのではないかとも思うのですが、手元では今のところその方面の具体的な情報に行き着いておらず、いずれにせよここではあくまでも現行の google-home-notifier の機能を拡張することにしました。

実装まわりの話題

方針

実装にあたっては次の方針を基本としました。

  • 複数 MP3 データの連続再生はあくまでも拡張機能として取り回す。従来の TTS 機能および単一の MP3 再生機能との互換性を保ち両者の処理を衝突させない
  • デバイスからの再生終了メッセージを受け取るためのコネクション維持と監視は他の処理を止めないためにバックグラウンドタスク内で実施する
  • バックグラウンドタスクから再生終了通知を受け取るためにコールバック機構を設置する。ライブラリ側へ追加するのはこの「一件の MP3 データ再生が終了した場合、または中断・スキップされた場合にコールバック関数経由で通知する」処理にとどめ、それ以外の処理はアプリケーション側の要件としてエージェントプログラムへ実装する
  • つまりライブラリ側の改変は必要最小限の内容とする

処理のイメージ

左側のフローは手元での p8266-google-home-notifier とエージェントプログラムによる処理内容を要約したものです。右側はそこに調査から得られた情報を加味して MP3 データのリスト再生機能を追加したイメージで、ざっくり、赤文字の「キャスト処理」「再生監視タスク」の部分をライブラリ側の処理要素と想定しています。なお灰色のブロックは右フローとの共通部分です。 (クリックで可読大表示)

Google Home からのメッセージについて

上記右フローの「再生監視タスク」パート内の条件分岐部分でも触れていますが、データ再生の監視中には Google Home デバイスから仮想チャネル経由でさまざまなメッセージが送られてきます。手元での観察結果から、今回の実装では以下のメッセージを利用しています。

  • (右フロー中の「※1」) データの再生終了は、前掲のとおり当該デバイスからの「urn:x-cast:com.google.cast.media」チャネル経由での以下のメッセージによって判定できる。これを受け取ったらエージェント側は次の曲の再生へ移行する
    {"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"IDLE","currentTime":0,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"currentItemId":1,"idleReason":"FINISHED"}],"requestId":0}
  • (右フロー中の「※2」) デバイスが他のアプリ等とのキャストセッションを開始した場合には「urn:x-cast:com.google.cast.tp.connection」チャネルから以下のメッセージが届く。これを受け取ったらエージェント側は現在の連続再生処理を停止する
    {"type":"CLOSE"}
    • なお、Google Cast 機構においてはデバイス側との接続の維持・確認のために上記チャネル上で相互に PING, PONG メッセージの応酬を継続的に実施することが流儀のひとつ。バックグラウンドタスクでの待機中は 5秒毎に PING を送ることにしたが、タイミングによってはデバイス側から PING が届くこともある。その場合は PONG を送る
      {"type":"PING"}
      {"type":"PONG"}
  • (右フロー中の「※3」) ユーザが「OK, Google。ストップ」「OK, Google。やめて」等の再生停止を求めるフレーズを発した場合は「urn:x-cast:com.google.cast.media」チャネル経由でデバイスから下記要領のメッセージが届く。これを受け取ったらエージェント側は連続再生を停止する
    {"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"PAUSED","currentTime":1.880214,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"activeTrackIds":[],"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":388729138}
  • (右フロー中の「※4」) ユーザが「OK, Google。スキップ」「OK, Google。次の曲」等のフレーズで次データへのスキップをリクエストした場合は「urn:x-cast:com.google.cast.media」チャネル経由でデバイスから下記要領のメッセージが届く。この状況でのステータスは「"playerState":"PLAYING"」かつ、「"requestId":」の値が 0 以外となる。 これを受け取ったらエージェント側は次のデータの再生へ移行する
    {"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"PLAYING","currentTime":15.665338,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"activeTrackIds":[],"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":388729139}
    なお「"requestId":0」のメッセージがデータの再生開始時点で一度発行されるため上記と混同しないよう注意
    {"type":"MEDIA_STATUS","status":[{"mediaSessionId":1,"playbackRate":1,"playerState":"PLAYING","currentTime":1.384362,"supportedMediaCommands":15,"volume":{"level":1,"muted":false},"activeTrackIds":[],"currentItemId":1,"repeatMode":"REPEAT_OFF"}],"requestId":0}

ソースコード

カスタマイズした esp8266-google-home-notifier

オリジナルの esp8266-google-home-notifier ライブラリのコードへプライベートに手を加えています。変更箇所は「#ifdef TANABE」部分で、fork した GitHub リポジトリへ反映しています。変更内容はこの記事の冒頭、および前回の記事で触れたとおりです。

$ git clone -b private https://github.com/mkttanabe/esp8266-google-home-notifier.git

また、同リポジトリの examples/esp32/SimpleUsage/ 下に play 関数用のサンプルスケッチを加えました。どちらもオリジナルの SimpleUsage.ino のコピーを小さく書き替えたものです。

このふたつの追加サンプルを実行した様子の動画です。
  

エージェントプログラム

Arduino core for the ESP32 環境で作成したエージェントの Adruino スケッチです。前回分と互換性のある機能拡張版です。

  • GoogleHomeNotifierESP32AgentEx - github.com/mkttanabe

    GoogleHomeNotifierESP32Agent.ino の 以下の箇所を環境にあわせて書き替える
    //------- ユーザ定義 ------------------

    // Google Home デバイスの IP アドレス, デバイス名
    // 有効にすれば指定 IP アドレスを直接使用
    // 無効にすれば指定デバイス名で mDNS 照会
    //#define USE_GH_IPADRESS

    IPAddress myGoogleHomeIPAddress(192,168,0,110);
    #define myGoogleHomeDeviceName "room02"

    // WiFi アクセスポイント
    #define ssid "ssid"
    #define password "pass"

    // Beebotte 情報
    #define mqtt_host "mqtt.beebotte.com"
    #define mqtt_port 8883
    #define mqtt_topic "test01/msg"
    #define mqtt_pass "token:" "token_************"

    // MP3 データを配置する Web サーバ
    #define dataServer "192.168.0.127"
    #define dataServerPort 80
    // MP3 データの URL
    #define MP3DataFmt "http://" dataServer "/sound/%s.mp3"
    // MP3 再生リストのパス
    #define MP3ListFmt "/sound/list/%s.txt"
    //-------------------------------------

    上の記述の場合、自分の Beebotte アカウントの Channel "test01", Resource "msg" のメッセージの data キーの値に応じて以下が行われる
    • data 値の先頭文字がアスタリスクであれば音声合成用のテキストとみなして google-home-notifier の TTS 処理にかける
      • 例) {"data":"*こんにちは"} => 「こんにちは」と発話
    • data 値の先頭文字がアスタリスクまたは "@" でなければ MP3 ファイル名とみなし所定の Web サーバの URL に編集して再生
      • 例) {"data":"file01"} => "http://192.168.0.127/sound/file01.mp3" をキャスト
    • data 値の先頭文字が "@" であれば MP3 データ再生リスト名とみなし所定の Web サーバの URL に編集して読み込み、記述されている各 MP3 データエントリを再生
      • 例) {"data":"@demo"} => "http://192.168.0.127/sound/list/demo.txt" を再生リストとして読み込んで処理
        • 下記の指定があればシャッフル再生を行う
          {"data":"@demo,1"} , {"data":"@list01,ランダム"}
        • 上記以外ならリスト上の記述順に再生
      • 再生リストの記述例
        demo.txt
        # 各行の '#' 以降はコメントとして扱われる
        # 空行は無視、有効行先頭末尾のスペース・タブは除去される

        # soundorbis 様によるフリー BGM 作品より
        #
        # 【フリーBGM】リコーダージングル【01〜05】
        # https://www.youtube.com/watch?v=ztm1CSZEpY8
        #
        # 利用規約
        # https://www.soundorbis.net/license
        #

        demo/[1]nc150689
        demo/[2]nc150690
        demo/[3]nc150691
        demo/[4]nc150692
        demo/[5]nc150693

    なお、上の「ユーザ定義」箇所において「#define USE_GH_IPADRESS」が無効な場合は前回版と同様に mDNS 照会によりデバイス名から IP アドレスを取得するが、このやや時間のかかる照会のコストを軽減するために、今回の拡張版においては一旦取得した IP アドレスを ESP32 のフラッシュメモリ領域 (SPIFFS) へ記録し次回はまずそれを参照する処理を加えている。当該アドレスへアクセスできない場合や当該アドレスのデバイスを Google Home と識別できない場合はあらためて照会と記録を行う。詳細は DeviceAddress.cpp を参照のこと

こういった経緯を経て形になったのが冒頭のデモ動画で稼働しているエージェントです。Raspberry Pi Zero W のローカル Web サーバとペアでとても快適に利用しています。


(tanabe)
klab_gijutsu2 at 09:54|この記事のURLComments(0)
2018年08月03日

Google Home を拠点間の双方向コミュニケーションに利用する

はてなブックマークに登録

ESP32 版 google-home-notifier のこと

google-home-notifier の ESP8266 移植版「esp8266-google-home-notifier」の作者である ほりひろ 様が、今年(2018年)6月に ESP32 への対応を実施されました。node.js プログラムであるオリジナルの google-home-notifier を実行可能なもっとも小振りなプラットフォームはラズベリーパイですが、さらにコンパクトで消費電力の少ない ESP32 ボードで自由に Google Home へのキャストを実現できることには大きな魅力があります。自宅ではこの素晴らしいライブラリを使って MQTT メッセージをトリガーにキャスティングを行う内容のエージェントを構成し様々な要件で利用させて頂いています。

「声のアバター」によるやりとり?

そんな中で先日ふと、google-home-notifier の「Google Home で任意の音を出せる機能」と、Google Home 側の「音声で所定の処理を実行できる機能」の組み合わせを、離れた拠点間での定型的なコミュニケーションに利用できるのではないか? と思い立ちました。図のようなイメージです。

双方のエージェントがそれぞれ MQTT ブローカー上の別のトピックを Subscribe しておき、必要に応じて相手のトピックへメッセージを Publish することで現地の Goole Home が所定のアナウンスを行います。その内容に対する所定のフレーズでの応答で相手へ向けての処理を発動する形にしておけば、いわば「声のアバター」を通じて両者間のやりとりが成立すると考えました。同じく音声による連絡方法であっても電話とは異なりこのやり方には直接的な拘束感やある種の重さがないため双方ともより手軽により淡々と利用できるでしょう。

試作のシナリオ

いろいろ使途がありそうですが、手元ではまず個人的に現在もっとも身近なテーマである「高齢者世帯の安否確認」を想定したシナリオを形にしてみました。以下の内容です。

A:見守り側
B:高齢者世帯側

  • A => Google Home A:「ねえ Google、安否を確認」
    • Google Home A => A:『はい、これから声をかけてみます』

  • Google Home B => B:『身のまわりは大丈夫ですか?問題がない場合は "ねえ Google、順調です" と話しかけて下さい。もし何かご相談があれば、"ねえ Google、コールして" と話しかけて下さい』

    • パターン 1: B => Google Home B:「ねえ Google、順調です」
      • Google Home B => B:『それはよかったです。さっそく大丈夫と伝えておきますね』
        • Google Home A => A:『先方から "元気です"と音声連絡がありました。メールを確認して下さい』
        • A あてにメールが届く


    • パターン 2: B => Google Home B:「ねえ Google、コールして」
      • Google Home B => B:『はい、すぐに電話をするようにと伝えます』
        • Google Home A => A:『先方から "電話してほしい"と音声連絡がありました。メールを確認して下さい』
        • A あてにメールが届き携帯電話が数回コールされる

相手が高齢者でもあるため Google Home からのアナウンスにはとりわけ「聞き取りやすさ」が求められます。定型の文言なので毎回音声合成を行う必然性もなく、ここでは、google-home-notifier の notify 関数から呼び出される Google 翻訳 TTS を使うのではなく、複数のオンライン TTS サービスを試した中で個人的にもっとも自然に感じられた音声を収録した MP3 データを play 関数で再生することにしました。

動作の様子

後出のプログラムリソース一式を使って二組の「Google Home Mini + エージェント」間でやりとりを行った様子の動画です。

(パターン 1: 「安否を確認」 〜 「順調です」 1分17秒

(パターン 2: 「安否を確認」 〜 「コールして」) 1分16秒

リソース

esp8266-google-home-notifier のカスタマイズ

前述のように手元では作成したエージェントをさまざまな要件で利用しており、この間にオリジナルの esp8266-google-home-notifier ライブラリのコードへ何点かプライベートに手を加えています。変更箇所は「#ifdef TANABE」部分で、fork した GitHub リポジトリへ反映しています。

$ git clone -b private https://github.com/mkttanabe/esp8266-google-home-notifier.git

変更内容

  1. 元の google-home-notifier に存在する ip 関数を追加
    • Google Home デバイスアドレスの mDNS 照会に比較的時間がかかるため IP アドレスの直指定もできると嬉しいため
  2. 元の google-home-notifier に存在する play 関数 (public)を追加
    • 手元では TTS 機能だけでなく MP3 再生機能も必要であるため
  3. play 関数による Google Home での MP3 データ再生終了を検知するためのコールバック機構を追加
    • 元の google-home-notifier にもないが欲しいと思っていた機能。理由は後日あらためて、、

エージェントプログラム

Arduino core for the ESP32 環境で作成したエージェントの Adruino スケッチです。MQTT ブローカーに Beebotte を使用しています。

  • GoogleHomeNotifierESP32Agent - github.com/mkttanabe
    GoogleHomeNotifierESP32Agent.ino の 以下の箇所を環境にあわせて書き替える
    //------- ユーザ定義 ------------------
    // Google Home デバイスの IP アドレス, デバイス名
    // 有効にすれば指定 IP アドレスを直接使用
    // 無効にすれば指定デバイス名で mDNS 照会
    #define USE_GH_IPADRESS

    IPAddress myGoogleHomeIPAddress(192,168,0,121);
    #define myGoogleHomeDeviceName "room01"

    // WiFi アクセスポイント
    #define ssid "ssid"
    #define password "pass"

    // Beebotte 情報
    #define mqtt_host "mqtt.beebotte.com"
    #define mqtt_port 8883
    #define mqtt_topic "test01/msg"
    #define mqtt_pass "token:" "token_************"

    // MP3 データを配置する Web サーバとデータパス
    #define dataServer "192.168.0.126"
    #define dataServerPort 80
    #define MP3DataFmt "http://" dataServer "/sound/%s.mp3"

    //-------------------------------------

    上の記述の場合、自分の Beebotte アカウントの Channel "test01", Resource "msg" のメッセージの data キーの値に応じて以下が行われる.
    • data 値の先頭文字がアスタリスクであれば音声合成用のテキストとみなして google-home-notifier の TTS 処理にかける
      • 例) {"data":"*こんにちは"} => 「こんにちは」と発話
    • data 値の先頭文字がアスタリスクでなければ MP3 ファイル名とみなし所定の Web サーバスペースの URL を編集して再生
      • 例) {"data":"file01"} => "http://192.168.0.126/sound/file01.mp3" をキャスト

用意した IFTTT アプレット

今回の試作では、トリガーに Google Assistant サービス、アクションに WebHooks サービスをアサインしたみっつの IFTTT アプレットを使っています。 ※ いずれもクリックで可読大表示

  • 問い合わせフレーズ「安否を確認」対向のアプレット
    アクションで Beebotte 上の相手側 "test02/msg" トピックへ "AnpiQuery.mp3" のキャスト再生を指示するメッセージを Publish
    トリガー
    アクション
  • 応答フレーズ「順調です」および「コールして」に対向のアプレット
    アクションで次項の Google Apps Script による Web アプリコードを所定のパラメータを添えて実行
    トリガー
    アクション
    トリガー
    アクション

Google Apps Script による Web アプリコード

上のふたつの応答用アプレットから呼び出している GAS コードです。

  • 「順調です」の場合
    アプレットから渡される res パラメータの値は「safe」。 Beebotte 上の相手側 "test01/msg" トピックへ "AnpiSafe.mp3" のキャスト再生を指示するメッセージを Publish して「元気です」と Gmail を送信
  • 「コールして」の場合
    アプレットから渡される res パラメータの値は「doCall」。まず Twillio 経由で相手の携帯電話を数回コールしてから Beebotte 上の相手側 "test01/msg" トピックへ "AnpiDoCall.mp3" のキャスト再生を指示するメッセージを Publish して「電話がほしい」と Gmail を送信


/*
  AnpiResponse
  2018-07
*/
var ADMIN = "***********@gmail.com";

function doGet(e) {
  return  ContentService.createTextOutput("??");
}

function doPost(e) {
  return doIt(e);
}

function doIt(e) {
  var msg, mp3Name;
  var res = e.parameter.res;
  var where = e.parameter.where;

  if (res == "safe") {
    msg = "元気です";
    mp3Name = "AnpiSafe";
  } else if (res == "doCall") {
    msg = "電話がほしい";
    mp3Name = "AnpiDoCall";
    doPhoneCall(); // twilio
  } else {
    return  ContentService.createTextOutput("res not found");
  }

  doPublish(mp3Name); // MQTT

  // gmail
  doSendMail(ADMIN, "Anpi", ADMIN, ADMIN,
           "安否連絡 [" + res + "]", 
           curDate() + " " + curTime() + "\n" +
           where + " より 「" + msg + "」と連絡がありました");

  return  ContentService.createTextOutput("OK");
}

function curDate() {
  var d = new Date();
  return d.getFullYear() + '-' +
    ('00' + (d.getMonth()+1)).slice(-2) + '-' + ('00' + d.getDate()).slice(-2);
}

function curTime() {
  var d = new Date();
  return ('00' + d.getHours()).slice(-2) + ':' +
    ('00' + d.getMinutes()).slice(-2) + ':' +  ('00' + d.getSeconds()).slice(-2);
}

function doPhoneCall() {
  var url = "https://api.twilio.com/2010-04-01/Accounts/****************/Calls";
  var data = "To=%2B************&From=%2B***********0&Url=http://demo.twilio.com/docs/voice.xml&Timeout=10";
  var options = {
    method: "POST",
    headers: {
      "Authorization":"Basic QUMxZjdhYTIzZWM2YTdkNWM*************************",
      "Content-Type":"application/x-www-form-urlencoded"
    },
    payload: data,
    muteHttpExceptions: true
  };
  var response = UrlFetchApp.fetch(url, options);
}

function doPublish(mp3Name) {
  var url = "https://api.beebotte.com/v1/data/publish/test01/msg";
  var data = '{"data":"' + mp3Name + '"}';
  var options = {
    method: "POST",
    headers: {
      "X-Auth-Token":"token_****************",
      "Content-Type":"application/json"
    },
    payload: data,
    muteHttpExceptions: true
  };
  var response = UrlFetchApp.fetch(url, options);
}

function doSendMail(from, fromName, to, cc, subject, body) {
  GmailApp.sendEmail(
    to,
    subject,
    body,
    {
      from: from,
      name: fromName,
      cc: cc
    }
  );
}

現行の Google Home ではサポートされていない「能動的な発話」を擬似的に実現する google-home-notifier を利用しなければできないことであり、こういった使い方も同プログラムの実用的な応用例のひとつではないかと思います。さらに発展させることもできるでしょう。面白い時代になりました。



余談:「うるさいアラーム」機能を DIY した話

手元での google-home-notifier の利用例をもうひとつ紹介します。 Google Home のアラーム機能は手軽で何かと便利なので目覚まし用に使ったりしていました。ところがプリセットのアラーム音がいささか上品すぎるため手元では音量を最大にしても目覚めにつながらないケースが何度かありました。同じ経験をした方はおそらく少なくないのではないかと思います。

ちなみに、Google は 2018-02-01 のアップデートで Google アシスタントのアラーム音を変更可能としたようですが、この機能を利用できるのは現時点では言語設定が英語の場合のみで日本語環境では利用できません。

後日の対応に期待しつつ、前掲のエージェントを利用して耳障りでうるさいアラームを鳴らす機能を自作してみました。

まず、アラーム設定用・解除用の下のふたつの IFTTT アプレットを用意しました。所定のシートの A1 セルを使って音を鳴らす時刻の設定・解除を行う内容です。設定用のアプレットでは Google Assistant トリガーのオプション "Say a phrase with both a number and a text ingredient" での "a number" と "a text" を使って時・分のふたつの音声指示要素を受け入れています。
トリガー
アクション
トリガー
アクション

あわせて当該シートに以下の GAS コードを付与、スクリプト実行のトリガー「時間主導型」の「分タイマー」で「1分ごと」に doNotify() 関数の呼び出しを設定しました。シートに記録された時刻と現在の時刻が一致すると、Beebotte 上の "test01/msg" トピックへ "noise01.mp3" のキャスト再生を指示するメッセージが Publish されます。


// Beebotte へ MQTT メッセージを Publish
function doPublish(mp3Name) {
  var url = "https://api.beebotte.com/v1/data/publish/test01/msg";
  var data = '{"data": "' + mp3Name + '"}';
  var options = {
    method: "POST",
    headers: {
      "X-Auth-Token":"token_*************", 
      "Content-Type":"application/json"
    },
    payload: data,
    muteHttpExceptions: true
  };
  var response = UrlFetchApp.fetch(url, options);
}

function doNotify() {
  // シートをチェック。A1 に時刻が設定されていれば騒音を鳴らす
  var spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadSheet.getSheets()[0];
  var val = sheet.getRange("A1").getValue();
  Logger.log(val);
  if (val == 0) {
    return;
  }
  var hm = val.split("_");
  Logger.log("set => " + hm[0] + ":" + hm[1]);  
  if (hm[0] >= 24 || hm[1] >= 60){
    //Logger.log("invalid");
    return;
  }
  var dt = new Date();
  var min = dt.getMinutes();
  var h = dt.getHours();
  if (String(h) == hm[0] && String(min) == hm[1]) {
    sheet.getRange("A1").setValue("0");
    doPublish("noise01");
  }
}

動作の様子: 動画 32秒

 

このように単機能でごくシンプルなものですが、手元では結構役に立っています。


(tanabe)
klab_gijutsu2 at 18:26|この記事のURLComments(0)
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント