Google Home と M5Stack と スマホ で双方向リアルタイム日本語通信する実験

M5Stack

スマホブラウザ側の HTML および JavaScript ファイルの作成

スマホやパソコンのブラウザから操作する場合の HTML および JavaScript ファイルを作成します。
これはテキストエディタで作成してください。
(メモ帳以外のプログラム用途エディタを使った方が良いです)

ファイルの拡張子は .html としておいてください。

また、作成した HTML 形式ファイルをスマホや iOS iPad などで表示させる方法は以下の記事を参照してください。

Firebase Realtime Database をスマホで操作およびストリーミング受信する実験

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv='content-type' content='text/html; charset=UTF-8'>
    <meta name='viewport' content='width=210, initial-scale=1.3, user-scalable=yes'>
    <title>Firebase Server-Sent Events Test</title>

    <!-- by Firebase Project Overview (Use Web) -->
    <script src="https://www.gstatic.com/firebasejs/5.5.5/firebase.js"></script>
    <script>
      // Initialize Firebase
      var config = {
        apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        authDomain: "xxxxxxxxxxx.firebaseapp.com",
        databaseURL: "https://xxxxxxxxxx.firebaseio.com",
        projectId: "xxxxxxxxxxxx",
        storageBucket: "xxxxxxxxxxx.appspot.com",
        messagingSenderId: "xxxxxxxxxxxx"
      };
      firebase.initializeApp(config);
    </script>
    <!-- -------------------------------- -->

    <script type="text/javascript">
      var user_path = "test_user2";

      function signIn() {
        let mail_str = document.getElementById("mail_txt_id").value;
        let pass_str = document.getElementById("pass_txt_id").value;

        document.getElementById("firebase_status_id").textContent = "Sign In wait...";

        firebase.auth().signInWithEmailAndPassword(mail_str, pass_str).then(function() {
          document.getElementById("firebase_status_id").textContent = "Sign In Success!";
          //2秒待ってからページを再ロードすることによって、ストリーミング受信するようになる。
          setTimeout("location.reload()",2000);
        }).catch(function(error) {
          let errorCode = error.code;
          let errorMessage = error.message;
          document.getElementById("firebase_status_id").textContent = errorMessage;
          setTimeout("location.reload()",5000);
        });
      }

      function readMessageData( num, id ){
        //text データの読み込み
        firebase.database().ref( user_path + '/message' + num ).on('value', function(snapshot) {
          document.getElementById( id ).textContent = snapshot.val();
          document.getElementById("firebase_status_id").textContent = "data received";
        });
        firebase.database().ref( user_path + '/color' + num ).on('value', function(snapshot) {
          let cl = snapshot.val();
          if( cl == '白' ) { cl = 'white'; }
          else if( cl == 'ホワイト' ) { cl = 'white'; }
          else if( cl == '赤' ) { cl = 'red'; }
          else if( cl == 'レッド' ) { cl = 'red'; }
          else if( cl == '緑' ) { cl = 'green'; }
          else if( cl == 'グリーン' ) { cl = 'green'; }
          else if( cl == '青' ) { cl = 'blue'; }
          else if( cl == 'ブルー' ) { cl = 'blue'; }
          else if( cl == '黄色' ) { cl = 'yellow'; }
          else if( cl == 'イエロー' ) { cl = 'yellow'; }
          else if( cl == 'シアン' ) { cl = 'cyan'; }
          else if( cl == 'スカイ ブルー' ) { cl = 'cyan'; }
          else if( cl == '水色' ) { cl = 'cyan'; }
          else if( cl == '紫' ) { cl = 'magenta'; }
          else if( cl == 'マゼンタ' ) { cl = 'magenta'; }
          document.getElementById( id ).style.color = cl;
          document.getElementById("firebase_status_id").textContent = "data received";
        });
      }

      //message JSONデータの書き込み
      function writeMessage( t_id , field ) {
        let value_str = document.getElementById(t_id).value;
        let json = {};
        json[ field ] = value_str;
        firebase.database().ref( user_path ).update( json );
      }
      //Node JSONデータの書き込み
      function writeNode( field, value_str ) {
        let json = {};
        json[ field ] = value_str;
        firebase.database().ref( user_path ).update( json );
      }

      function signOut() {
        firebase.auth().signOut().then(function() {
          // Sign-out successful.
          document.getElementById("firebase_status_id").textContent = "Sign OUT";
          document.getElementById("message0_id").textContent = "Sign OUT";
          document.getElementById("message1_id").textContent = "Sign OUT";
          document.getElementById("message2_id").textContent = "Sign OUT";
          setTimeout("location.reload()",2000);
        }).catch(function(error) {
          document.getElementById("firebase_status_id").textContent = "Sign OUT error";
        });
      }

      //関数の実行
      readMessageData( "0", "message0_id" );
      readMessageData( "1", "message1_id" );
      readMessageData( "2", "message2_id" );

    </script>
  </head>

  <body style="width:210px; background-color:#000; color:#FFF">
    <FONT size='1'>
    Firebase Realtime Database Test
    <br>
    Connection Status:
    <div id="firebase_status_id" style="border-style:solid; border-width:1px; padding:2px ">
      Cannot Connection.
    </div>
    </FONT>
    <br>
    message0:<br>
    <div id="message0_id" style="border-style:inset; padding:5px ">
      message 0 :???
    </div>
    message1:<br>
    <div id="message1_id" style="border-style:inset; padding:5px ">
      message 1 :???
    </div>
    message2:<br>
    <div id="message2_id" style="border-style:inset; padding:5px ">
      message 2 :???
    </div>
    <hr>
    message0:<br>
    <input type="button" id="black_id" value="W" style="background-color:white; color:black;" onclick="writeNode('color0','white');">
    <input type="button" id="red_id" value="R" style="background-color:red; color:black;" onclick="writeNode('color0','#FF0000');">
    <input type="button" id="green_id" value="G" style="background-color:green; color:black;" onclick="writeNode('color0','#00FF00');">
    <input type="button" id="blue_id" value="B" style="background-color:blue; color:white;" onclick="writeNode('color0','#0000FF');">
    <input type="color" value="#FFFFFF" id="color_picker_id" onchange="writeNode('color0',this.value);">
    <input type="button" id="speed1_id" value="SLOW" onclick="writeNode('speed0','50%');">
    <input type="button" id="speed2_id" value="MIDIUM" onclick="writeNode('speed0','80%');">
    <input type="button" id="speed3_id" value="FAST" onclick="writeNode('speed0','100%');">
    <input type="text" id="message0_write_id" value="Enter text" style="width:11em">
    <input type="button" value="Write" onclick="writeMessage('message0_write_id','message0');">
    <hr>  
    message1:<br>
    <input type="button" id="black_id" value="W" style="background-color:white; color:black;" onclick="writeNode('color1','white');">
    <input type="button" id="red_id" value="R" style="background-color:red; color:black;" onclick="writeNode('color1','#FF0000');">
    <input type="button" id="green_id" value="G" style="background-color:green; color:black;" onclick="writeNode('color1','#00FF00');">
    <input type="button" id="blue_id" value="B" style="background-color:blue; color:white;" onclick="writeNode('color1','#0000FF');">
    <input type="color" value="#FFFFFF" id="color_picker_id" onchange="writeNode('color1',this.value);">
    <input type="button" id="speed1_id" value="SLOW" onclick="writeNode('speed1','50%');">
    <input type="button" id="speed2_id" value="MIDIUM" onclick="writeNode('speed1','80%');">
    <input type="button" id="speed3_id" value="FAST" onclick="writeNode('speed1','100%');">
    <input type="text" id="message1_write_id" value="Enter text" style="width:11em">
    <input type="button" value="Write" onclick="writeMessage('message1_write_id','message1');">
    <hr>  
    message2:<br>
    <input type="button" id="black_id" value="W" style="background-color:white; color:black;" onclick="writeNode('color2','white');">
    <input type="button" id="red_id" value="R" style="background-color:red; color:black;" onclick="writeNode('color2','#FF0000');">
    <input type="button" id="green_id" value="G" style="background-color:green; color:black;" onclick="writeNode('color2','#00FF00');">
    <input type="button" id="blue_id" value="B" style="background-color:blue; color:white;" onclick="writeNode('color2','#0000FF');">
    <input type="color" value="#FFFFFF" id="color_picker_id" onchange="writeNode('color2',this.value);">
    <input type="button" id="speed1_id" value="SLOW" onclick="writeNode('speed2','50%');">
    <input type="button" id="speed2_id" value="MIDIUM" onclick="writeNode('speed2','80%');">
    <input type="button" id="speed3_id" value="FAST" onclick="writeNode('speed2','100%');">
    <input type="text" id="message2_write_id" value="Enter text" style="width:11em">
    <input type="button" value="Write" onclick="writeMessage('message2_write_id','message2');">
    <br>
    <p style="text-align:start; border-style:double; border-color:#FFF; padding:5px;">
      mail:<br>
      <input type="textbox" id="mail_txt_id" value=''><br>
      password:<br>
      <input type="password" id="pass_txt_id" value=''><br>
      <input type="button" value="sign in" onClick="signIn()">
      <input type="button" value="sign out" onclick="signOut();" style="background-color:#000; color:#FFF;">
    </p>
  </body>
</html>

【簡単な解説】

●9-21行:
ここに Firebase Realtime database のご自分のコードスニペットを貼り付けます。
このコードスニペットは、「ウェブアプリに Firebase を追加」というところを参照して取得します。

スマホブラウザ表示について

Android 8.0 ブラウザ Google Chrome で表示させると、下図のようになります。

最下部の mail とパスワード入力については、Firebase の Authentication でメールアドレスを有効にして、ユーザー UID 作成でパスワードを設定しておいたものを入力します。
これについては以下の記事を参照してください。

Firebase Realtime Database をスマホで操作およびストリーミング受信する実験

また、Color Picker ボタンは iOS ではテキスト入力のみです。

Sever-Sent Events 通信なので、Google Home でメッセージを変更すると、即反映されますし、一旦ブラウザを閉じて、再度ページを表示させても自動的に再接続します。
自動再接続したくない場合は、sign out してください。

編集後記

いかがでしょうか。
上手く動きましたでしょうか?

初めて IFTTT や Firebase を使う方にとっては、とても手順が多い気がすると思いますが、一旦できてしまえば、Google Cloud Platform や Dialogflow を使うよりも遙かに簡単で、理解もしやすいと思います。

ここまでできれば、音声認識を使った様々な IoT コントロールが、自分で自由に作ることができると思います。
そして、Amazon Echo との連携も可能だと思います。
近々挑戦しようと思っています。

今回はここまでです。
ではまた・・・。

 

コメント

  1. juchang より:

    mgo-tec 様

    「凄い!」の一言では済まされない私の何十倍もご苦労をされた事と思います。
    どうにか動作確認させていただきました。
    初心者のトラブルであまり参考になりませんが成功までの経緯を報告させていただきます。
    1. コンパイルエラー
     ESP8266-google-tts ライブラリーのインストール忘れによりコンパイルエラーとなる。
    2. 15行目、データベースシークレットの入力ミス
     auth.uid と勘違いして入力したためデータ転送ができない。
    3. 38行目、const char displayName = ” デバイス名 ” を見落とし
     Google Home との接続ができない。
    後は、テキスト通りの入力で動作OKです。
    これからも素晴らしい作品の発表をお待ちしております。

    • mgo-tec mgo-tec より:

      juchangさん

      いつも動作させていただき、本当に感謝しております。

      そうなんです!
      これは、パッと見た目は地味ですが、実は凄いんです!!!
      凄いんですけど、Twitter での評判はイマイチでした。
      これの凄さは、実際に自分で動作させてみて分かるんです。
      その点、juchangさんに試していただいて、ホントに嬉しいですね。
      (^^)
      これを応用すれば、とんでもないことができそうですよね!

      ところで、コンパイルエラーやデータベースシークレット、Google Home のデバイス名などは、スルーしてしまう人がいると思います。
      記事を修正して、強調表示しておきたいと思います。

      ご指摘ありがとうございました。
      今後とも、よろしくお願いいたします。
      m(_ _)m

      • mgo-tec mgo-tec より:

        juchangさん

        写真を投稿いただき、ありがとうございます。
        バッチリですね。
        ケースがコンパクトになっていて Good ! です。
        これなら、M5Stack 要らずですね。
        こんな風に作っていただけるとは、メチャメチャ光栄です。
        皆さんに見て頂くために、このコメント欄に貼らせていただきますね!
        (^^)
        jcg_googlehome_firebase_m5stack01.jpg

        • juchang より:

          mgo-tec 様

          早速掲載いただきありがとうございます。

          「 よく解りません 」「 お役に立てそうにありません 」
          我が家の Google Home はなかなか相手にしてくれません。
          メッセージ1番が一番繋がるように感じるのですが気のせいでしょうか。
          「 サンキューベリマッチ 」と発声すると、アルファベットが表示され、「 サンクスベリマッチ 」と応えてくれます。
          秋の夜長、なかなか寝付けそうにありません。

          • mgo-tec mgo-tec より:

            juchangさん

            こちらこそ、いろいろありがとうございます。

            Google Home は IFTTT を使うと認識率が下がります。
            ご存知だと思いますが、Dialogflow を使うと格段に認識し易くなりますが、設定がとても面倒で、テストモードでしかできません。
            IFTTT は設定が「簡単」ですから、結局は「IFTTT でいいや」っていう感じですね。
            IFTTT の使い方のポイントは、IFTTT が認識し易いようなフレーズにご自分で設定するしかありません。
            例えば、
            「M5Stack メッセージ1スピード50」
            という設定にしても認識してくれません。
            最初の単語フレーズに、世間的によく使われる単語にするところがポイントです。
            Dialogflow では M5Stack は認識してくれますが、IFTTT はダメでした。
            また、最初に「メッセージ」が入ると、カラーなのかスピードなのかを認識する時間がかかって、認識できなくなるようです。
            ですから、
            「スピード1番50パーセント」
            というように工夫して IFTTT が認識してくれるように設定しなければなりません。
            ご自分が Google Home に喋る「喋り方」もいろいろ工夫しなければならないところが、ちょっと面倒です。

            そして、IFTTT の欠点として、Applet を修正すると、5~10分は反映されませんので、気長に待つことです。
            待てなければ、Applet を削除して、新たに作り直すと時間が短縮されます。

            また、私も「メッセージ1番」が一番よく繋がりますが、カラーが認識しない場合、「色」というフレーズ Applet も作って置くと良いかもしれません。
            いずれにしても、IFTTT は簡単ですが認識悪いということで割り切るしかありませんね。

  2. マッキー より:

    お世話になります。
    動作はほぼ動作してるのですが、2,3うまくいきません。
    1 color1番 赤と言うとメッセージ1番に「赤」と入り、redと変換して  くれない。メッセージとスピードは所望の動作しております。
    2 9841でそっくりさんを作ったのですがボタンABCが押されたままとなります。もちろん
    const uint8_t buttonA_GPIO = 35;
    const uint8_t buttonB_GPIO = 34;
    const uint8_t buttonC_GPIO = 25;
    宣言してもダメです。ソースコードでは記述がないのでたぶんM5stackの
    ライブラリーでやっておられると思いますが、9841で動作できる改善方法を教えてください。

    • mgo-tec mgo-tec より:

      マッキーさん

      いつもブログご覧いただき、そして試していただき、ありがとうございます。
      ところで、9841 とはいったいどのボードでしょうか?
      おそらく、ILI9341 ボードのことだろうと思いますので、それでお話を進めさせていただきます。

      まず、1番の回答:
      記事の文章でも注意事項として書いてありますが、IFTTT の Applets を入力する時に、自動的に半角スペースが入力されてしまう場合があるので、Applets の Body 部分に半角スペースが入っていないか再度確認してみてください。
      そして、シリアルモニタをみて、しゃべった単語が以下のように、半角スペースが一切無く変換されているか確認してみてください。

      data: {"path":"/","data":{"color1":"赤"}}
      

      私の場合は、赤の前に半角スペースがあって、検知してくれないことがありました。
      その場合は、たいてい、IFTTT の Applets の Body に半角スペースが入っています。
      あとは、color と 1 の間にスペースが入っていて、color1 フィールドにメッセージが入らないことが考えられます。

      その他、Google 192-211行で、Firebase に入力された単語とif文で条件分けする単語が合っているか、再確認してください。
      半角スペースが入っている場合はそれを含めて見直してみて下さい。
      Google Home からは、単語ごとに半角スペースが入って来ますので、そこも注目しておくと良いと思います。
      Firebase の「赤」が入っているのに、[red]と変換してくれないのは、半角スペースが原因の可能性があります。

      2番の回答:
      申し訳ございませんが、ここの記事では M5Stack で使うこと限定で書いています。
      別途 ILI9341 ボードを使う方用にはプログラムを組んでおりません。
      ライブラリも M5Stack 用です。
      ですから、ILI9341 ボードで組みたい場合は、以下の記事
      https://www.mgo-tec.com/blog-entry-select-box-news-m5stack-esp32.html/5#title11-4
      のサンプルスケッチを参考にして組んでください。
      当記事のサンプルスケッチにこの記事の4-16行を追加し、46-47行をこの記事の60-69行(65行は不要)に挿し替えれば動くかもしれません。
      そして、ボタンが押されているということは、GPIO入力が常時 GND レベルになっているということですので、回路に問題があるかもしれません。
      テスターで測りながら、ボタンが押されていない時の GPIO 入力は 3.3V。
      ボタンが押されたときのGPIO入力が 0V になっているか確認してみてください。
      それぞれのボタンが独立して電圧レベルが変化していることが重要です。
      どこかのボタンが押されたら、他のボタンの GPIO 入力が変わってしまう場合は、回路が間違えているということです。

      とりあえず、これでもう一度ご確認ください。

タイトルとURLをコピーしました