6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

この記事誰得? 私しか得しないニッチな技術で記事投稿!

【Android × Raspberry Pi 】ハンズフリーで後続車にメッセージを送ってみた

Last updated at Posted at 2023-06-27

目次

  • はじめに
  • この記事をおすすめする人
  • システム構成
  • 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. システム構成

全体像.png

① 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. 最後に

今回はハンズフリーで後続車にメッセージを送るシステムを作成しました!
時間があれば、後続車との連携ができるアドホックネットワークのようなシステムも作ってみたいですね〜!!
良ければ、いいねをお願いいたします!
ありがとうございました!

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?