目次
- はじめに
- この記事をおすすめする人
- システム構成
- SpeechRecognizerとは?・使い方
- BLEのMTU制限問題・解決策
- Pythonを用いてラズパイ画面に表示するやり方
- 実装編・UI設計
- 最後に
こちらの記事は修士1年の研究の合間に頑張って作ったものになります..
是非読んでください..!!
1. はじめに
後ろの車にメッセージを送りたくないですか...?
緊急事態で後続車に停止している旨を伝えたり...
感謝のメッセージを伝えたり...
(実はサンキューハザードは正しい使い方ではありません...)
しかし、スマホで文字を打つとなると、運転中に携帯を触ってはいけないため、
中々メッセージを伝えられません...
音声入力でメッセージを伝えよう!!!
本記事では、android端末に音声入力したものが、後ろに設置してあるラズパイにBLE送信され、ラズパイはモニターに表示し、音声入力の文字列が後続車に伝えることができます。
[Android端末] --BLE--> [Raspberry Pi] --HDMI--> [モニター]
ゴールは以下の動画のように、音声入力だけで後続車にメッセージを送ることができます。
使用する際は以下を厳守でお願いします。
絶対に煽り運転に使用しないようにしてください。
使用端末
android端末: Google Pixel 6
Raspberry Pi : Raspberry Pi 3 model B
2. この記事をおすすめする人
- Android端末のBLE通信を実装したい人
- ラズパイのBLE通信を実装したい人
- 後続車にメッセージを送ってみたい人
- Pythonを使ってラズパイGUIで画面表示したみたい人
3. システム構成
① Android端末はアドバタイズ、ラズパイはスキャンし、接続します。
② AndroidのSpeechRecognizerクラスを使用し、「メッセージ入力」をまちます。
③ 「メッセージ入力」が音声入力されると、伝えたいメッセージを受け付けます。
④ 伝えたいメッセージが入力されると、ラズパイにBLE送信されます。
⑤ モニターに出力させ、後続車にメッセージを送ります。
(⑥ モニターを後続車が見ることで、メッセージが伝わります。)
4. SpeechRecognizerとは?
SpeechRecognizerは、Androidプラットフォームで音声認識を実現するためのクラスです。音声入力を受け付け、それをテキストに変換する機能を提供します。
SpeechRecognizerは、マイクからの音声入力を受け取り、音声を解析してテキストに変換します。これにより、ユーザーが音声を使ってアプリケーションと対話したり、音声コマンドを使用して操作したりすることが可能になります。
SpeechRecognizerは、AndroidのSpeech-to-Text(音声認識)APIを使用して実装されています。このAPIは、デバイスの内部音声認識エンジンを使用して音声を解析し、テキストに変換します。音声認識エンジンはデバイスによって異なる場合がありますが、通常はGoogleの音声認識エンジンが使用されます。
実装は結構簡単で、こちらの方を参考にさせていただきました。
5. BLEのMTU制限問題・解決策
使用しているRaspberry Pi 3はBluetoothのバージョンにより、MTU(Maximum Transmission Unit)は23バイトです。
よって23バイト以上の場合はパケットを区切って送る必要があります。
Speech-to-Textでは日本語に設定しているので、
6文字で区切るように設定し、なくなるまで繰り返し送るように実装しました。
そして、送りたいメッセージが送り切ると、endの文字列を送ります。
以下はandroid側の実装です。
private void sendMessageOverBLE(String message) {
int index = 0;
int maxLength = 6;
// メッセージを指定された長さで分割して送信
while (index < message.length()) {
String subMessage;
if (index + maxLength <= message.length()) {
subMessage = message.substring(index, index + maxLength);
} else {
subMessage = message.substring(index);
}
mCharacteristic.setValue(subMessage);
// Bluetooth接続のパーミッションが許可されているかチェック
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
// 接続中のデバイスにキャラクタリスティック変更の通知を送信
if (mDevice != null) {
mBluetoothGattServer.notifyCharacteristicChanged(mDevice, mCharacteristic, false);
} else {
Log.w(TAG, "No connected device to notify.");
}
index += maxLength;
}
// メッセージの送信完了を示す"end"を設定して通知
mCharacteristic.setValue("end");
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (mDevice != null) {
mBluetoothGattServer.notifyCharacteristicChanged(mDevice, mCharacteristic, false);
} else {
Log.w(TAG, "No connected device to notify.");
}
// UIスレッド上でメッセージの表示と保持を更新
runOnUiThread(() -> {
messageTextView.setText(message);
MainActivity.this.message = message;
});
}
以下はラズパイ側の実装です。
messageが「end」ではない場合は、受信したメッセージが途中のデータであるため、バッファに追加して結合を待ちます。
messageが「end」である場合、バッファに格納されているメッセージを結合し、完全なメッセージを作成します。完全なメッセージが「プログラム終了」と等しい場合、プログラムを終了します。それ以外の場合は、完全なメッセージを表示します。
def handleNotification(self, cHandle, data):
#区切られた文字を表示
print('Received notification (raw): ', data)
try:
message = data.decode('utf-8')
except UnicodeDecodeError:
message = data.decode('utf-8', 'replace')
# 受信したデータが"end"である場合、完全なメッセージを結合し処理を行う
if message == 'end':
complete_message = ''.join(self.message_buffer)
print('Received complete message: ' + complete_message)
# 完全なメッセージが"プログラム終了"である場合、プログラムを終了する
if complete_message == 'プログラム終了':
sys.exit()
# 完全なメッセージを表示する
self._display_message(complete_message)
self.message_buffer = []
else:
# メッセージが途中のデータである場合、バッファに追加して結合を待つ
self.message_buffer.append(message)
print('Received notification: ' + message)
6. Pythonを用いてラズパイ画面に表示するやり方
ラズパイの画面上に文字を表示する方法を説明します。
実装は簡単で、先ほど完成したmessageを使い、
以下のように実装しています。
文字サイズ的に、5文字目で改行を挟むようにしています。
def _display_message(self, message):
def _blink():
for _ in range(3): # 3回点滅する
root = tk.Tk()
root.attributes('-fullscreen', True) # フルスクリーンウィンドウ
# メッセージを5文字ごとに改行してフォーマットする
formatted_message = "\n".join([message[i:i+5] for i in range(0, len(message), 5)])
label = tk.Label(root, text=formatted_message, font=("Helvetica", 200)) # 大きなフォントサイズ
label.pack(expand=True)
root.after(1000, lambda: root.destroy()) # 1秒後にウィンドウを閉じる
root.mainloop()
time.sleep(3) # 点滅の間に一時停止
threading.Thread(target=_blink).start()
以下のようにモニターに表示されるようになっています。
7. 実装編・UI設計
実装編
プログラムはGitに公開しているので、そちらをご覧ください。
AndroidのUI設計
UIと言っても、最低限しか作っていません(⌒-⌒; )
- 初期画面
- 初期状態は"Message not entered"と表示されており、接続対象のデバイスにBLE接続するまでこの状態です。
- BLE接続完了後
- 対象デバイスにBLEに接続後、右上に音声入力が出ているように、「メッセージ入力」を待つ画面になります。
- メッセージ入力フェーズ
- 「メッセージ入力」が入力されると画面が黄色に遷移し、音声入力を待つフェーズになります。
- ここで音声入力された文字列がBLEで送信されます。
- 何も話さなければ、3秒ほどでこのフェーズは閉じます。
- メッセージ送信後
- メッセージが送信されると、どんな文字が送られたのかが表示されています。
- 次のメッセージが入力されるまで、文字列は表示され続けます。
8. 最後に
今回はハンズフリーで後続車にメッセージを送るシステムを作成しました!
時間があれば、後続車との連携ができるアドホックネットワークのようなシステムも作ってみたいですね〜!!
良ければ、いいねをお願いいたします!
ありがとうございました!