やりたいこと
「次のページ」と言ったら,パワーポイントを次のページに移動させたい.
具体的には,パワーポイントをつかってプレゼンするとき,次のような場合に声で操作できたら便利だと考えました.
また,プレゼンしている人が「次のページお願いします」と言うと別の人がパソコンを操作してページ送りする・というのをたまに見かけますが,これを自動化するというイメージです.
(ちょっとツッコミどころが多いかもしれませんが,つくってみたくなったんです.スルーしてください...)
システム概要
- スマホのマイクでキーワード(「次のページ」など)の声を拾い, スマホ上のGoogle Assistantで音声認識する
- IFTTTからWebhook出力して,Beebotteにメッセージを保存する
- RaspberryPiがBeebotteからメッセージを取得する
- RaspberryPiからArduino MicroにI2C通信でデータを渡す
- Arduino MicroからUSB接続したPCに対してキーボード操作する
「1」のGoogle Assistantというのは,音声アシスタントとかAIアシスタントと呼ばれるものです(Apple製品でいうところのSiri, AmazonでいうところのAlexaです."Google Assistant"という名前はざっくりとしたネーミングで分かりにくいですが).ここではスマホアプリ版を使います,手軽に使えるので.これがシステム全体の入力部分となります.
一方,システム全体の出力部分であるパワーポイント用のPCにはArduino Microを接続します(図の「5」の部分).このマイコンは,キーボードやマウスのフリをするという機能があります(エミュレーション).この機能はちょっと珍しくて,面白いです.
Arduino Microには,「I2Cで特定のデータが入力されると,キーボードの右キーを押す」というプログラムを書いておきます.
次に「1」と「4」の間のところで,インターネット上のGoogle AssistantとArduino Microをどうやって接続するかですが,
Arduino Microはネット接続できないので,ネット接続はRaspberryPiにお任せして,そこからI2CでArduinoにデータを送るようにします.
RaspberryPiからインターネット上のデータを取得しに行くのは簡単にできます.
が,逆に,インターネット上の外部からRaspberryPiに対してデータを渡すというのは面倒だったりなにかと不安だったりします(RaspberryPiをwebサーバーにして公開するなど).
そこで,BeebotteというMQTTブローカーを使用します.外部からRaspberryPiに直接データを送るのでなく,Beebotteというデータ置き場を一旦置いておき,RaspberryPiからそこへデータを取りに行くようなイメージです.(図の「3」)
このあたりはこちらのページを参考にさせて頂きました:
Google AssistantとRaspberry Piで自宅の家電を操作する
では,残る最後の部分として,Google AssistantからBeebotteにどうやってデータを送るかですが,IFTTTのWebhook出力を使います.
IFTTTというのは2つのサービスを連携させるもので,ここではGoogle Assistantで特定の音声を認識したことをトリガーとしてWebhook出力を自動で実行します.
なお,このシステムでは,「次のページ」などを言う前に,Google Assistantアプリでマイクボタン(音声入力開始)をタップする必要があります.(…サラっと書きましたが,実際に使う上でこの点は致命的ですね)
以下,詳細です.
1. スマホのGoogle Assistantで声を拾う
スマホでGoogle Assistantを起動します.
・iPhoneの場合はGoogle AssistantアプリをApp Storeからインストールします
・Androidの場合はアプリがはじめからインストール済みのようです
参考:Google Assistantヘルプ
次に,音声入力を開始して話しかければよいのですが,音声入力を開始する方法は2通りあります.
・「OK Google」または「ねぇ Google」と話しかける
・マイクアイコンをタップする
いちいち話しかけるのは面倒なので,タップします.
(いちいちタップするというのも面倒なんですけど…)
2. IFTTTからWebhook出力してBeebotteにメッセージを保存する
2-1. IFTTT
IFTTTでは,次のように設定します:
If 「Google Assistantに特定のキーワードで話しかける」 Then 「BeebotteのURLにWebhookをPOSTする」
ここでは,「次のページ」というキーワードを言うと,作動するようにしました.
なお,その際,「ピ」という声で返事してくれます.かわいい.(返事は無しにもできます)
「token」の部分については次の項目のBeebotteの画面からコピーします.
webhookの内容としては,"next_page"というコマンド名にしました
2-2. Beebotte
こちらのページを参考に設定します.
Google AssistantとRaspberry Piで自宅の家電を操作する(再掲)
この画面に表示されるtokenを前の項目のURLのWebhook部分に入力します.
3. RaspberryPiがBeebotteからメッセージを取得する
次の項目でRaspberryPiのプログラムをまとめて書きますので,ここは説明省略.
4. RaspberryPiからArduino MicroにI2C通信する
4-1. RaspberryPiとArduino Microの接続
下図の通り,I2CのSDA,SCLとGNDを接続します.
4-2. RaspberryPi
RaspberryPiで動かすプログラムです.
Beebotteからデータを受け取り,I2Cで送信します.
import paho.mqtt.client as mqtt
import json
import smbus #for I2C
import time #for sleep
i2c = smbus.SMBus(1)
TOKEN = "token..............." #beebotteのtokenをここに入力する
HOSTNAME = "api.beebotte.com"
PORT = 1883
TOPIC = "KeyboardForPowerpoint/keyboardCmd"
ADDRESS_ARDUINO = 0x04
OPEC_RIGHT_BUTTON = 0x1c
DELAYTIME_BETWEEN_I2C = 0.5 #[second]
DELAYTIME_BETWEEN_CMD = 0.5 #[second]
def right_button():
print("--right_button (next_page)--")
i2c.write_byte(ADDRESS_ARDUINO, OPEC_RIGHT_BUTTON)
time.sleep(DELAYTIME_BETWEEN_I2C)
i2c.write_byte(ADDRESS_ARDUINO, OPEC_RIGHT_BUTTON)
time.sleep(DELAYTIME_BETWEEN_I2C)
def on_connect(client, userdata, flags, respons_code):
print('status {0}'.format(respons_code))
client.subscribe(TOPIC)
def on_message(client, userdata, msg):
print(msg.topic + " " + str(msg.payload))
data = json.loads(msg.payload.decode("utf-8"))["data"][0]
data = {key:value.strip() for key, value in data.items()}
if data["cmd"] == "next_page":
right_button()
client = mqtt.Client()
client.username_pw_set("token:%s"%TOKEN)
client.on_connect = on_connect
client.on_message = on_message
client.connect(HOSTNAME, port=PORT, keepalive=60)
client.loop_forever()
Beebotteのデータのコマンド部分が"next_page"だったときには,"right_button"に対応するデータをI2Cで送信します.(「right_buttonに対応するデータ」というのは自分で適当に決めたもので,Arduino Micro側と対応がとるだけのものです)
上記プログラムでは,データを2回送信していますが,深い意味はありません.(当初は「ボタンの種類」と「押す回数」の2種類を送信することも考えていましたが,結局ボタンの種類だけにしました)
4-3. Arduino Micro
Arduino Microに書き込むプログラムです:
#include <Mouse.h>
#include <Keyboard.h>
#include <Wire.h>
#define RIGHT_BUTTON 0x1c
int SLAVE_ADDRESS = 0x04; //I2Cのアドレス『0x04』
int opecodeReceived = 0;
int operandReceived = 0;
int opecode = 0;
int operand = 0;
void ReceiveMassage(int n){/*setupの後、終了するまで繰り返し呼び出される関数*/
int cmd = Wire.read(); //文字を読む
Serial.println("received ..."); //シリアルポートにcmdを出力し表示する
Serial.println(cmd); //シリアルポートにcmdを出力し表示する
if(opecodeReceived == 0){
opecode = cmd;
opecodeReceived = 1;
digitalWrite(LED_BUILTIN, HIGH);
}else if(operandReceived == 0){
operand = cmd;
operandReceived = 1;
OperateKeyboard(opecode, operand);
digitalWrite(LED_BUILTIN, LOW);
}
}
void RequestMassage(){
Wire.write("reply test OK");
}
void setup() {
pinMode(2,INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
//Mouse.begin();
Keyboard.begin();
Serial.begin(9600); //シリアル通信の初期化しシリアルモニタへ文字列を出力できるようにする 9600はボーレート(通信速度)
Serial.println("--start--");
Wire.begin(SLAVE_ADDRESS); //I2C接続を開始する
Wire.onReceive(ReceiveMassage); //I2Cで受信したときに呼び出す関数を登録する
Wire.onRequest(RequestMassage); //I2Cでリクエスト受信したときに呼び出す関数を登録する
}
void right_button(){
Serial.println("right button .....");
Keyboard.press(KEY_RIGHT_ARROW);
delay(100);
Keyboard.releaseAll();
}
void loop() {
}
void OperateKeyboard(int opecode, int operand){
switch(opecode){
case RIGHT_BUTTON :
Serial.println("right button ...");
right_button();
break;
}
opecodeReceived = 0;
operandReceived = 0;
opecode = 0;
operand = 0;
delay(100);
}
"Keyboard.press(KEY_RIGHT_ARROW);"の部分で,キーボードのエミュレーションをしています.
データを2回受信し,それぞれ別の変数に格納していますが,今回は2回目のデータは無視しています.
5. Arduino MicroからUSB接続したPCに対してキーボード操作する
接続しておくだけでよいです.
おわりに
今回使ったArduino Microはキーボードだけでなく,マウスのエミュレータとしても使用できます.パワーポイントだけでなく,いろんなものに使えて便利です.
パソコンを操作する場合には,今回のようにUSB端子からハックするより,パソコン上で動かすソフトウェアをつくった方がスマートかもしれません.
が,「パソコンではないけど,USB端子があって,汎用のUSBマウスやキーボードで操作できる」ような機器にも使えるというのが,今回つくった仕組みのユニークなところです.