机上の空論主義者

-♰- 有言不実行の自身をブログ名で戒めろ -♰-

【M5stack,Android】物理フリック入力キーボードの途中段階 ~第2弾(Bluetooth Serial通信編)~

こんにちは.

物理フリック入力キーボードについて,8・9月ごろの進捗についてまとめようと思います.
以前紹介した、「前を向いて歩きながら入力できるデバイス」の製作途中段階で,Android端末との通信あり状態のシステムまで作成しました.具体的には,M5stackとAndroidBluetooth Serial(SPP)で通信してデータ送信できるように実装しました.

ume-boshi.hatenablog.jp

ume-boshi.hatenablog.jp


処理手順

今回までに実装した機能は, ↓ のような処理手順で実装しています.

  1. マイコン上で,どの文字が入力されているかを取得
  2. 入力された文字をindexで表現
  3. indexをBluetooth SerialでAndroidに送信
  4. Android側では,受け取ったindexを文字に直して表示
  5. 入力内容を音声で出力

マイコン側は,前回の記事とさほど変化していません.
Android側は今回初登場しますが,このサイトのメモアプリをベースに作っていきました.

f:id:ume-boshi:20201019102748j:plain
入力とアプリテスト様子


実装

SPPでの通信部分

M5stack側はライブラリでかなり単純化されているので,ほとんど困ることはありません.

M5stack側のソースコードはキー入力を取得する箇所を除けば単純です.SerialBT.print("hagehage");でデータを送信できます.wordIndexで数字として送信するのは,送信時に2Byteコードになる場合の処理を考えるのが面倒くさかったからです.「あ」から「ん」までの文字と記号に,およそ60文字分を割り当てました.

ちなみに,まだ濁点や小文字部分は実装していません.

BluetoothSerial SerialBT;

void setup(){
  //各種初期化(省略)
  SerialBT.begin("device_name"); //device_name は自由に指定可能
}

void loop(){
  //キー入力情報を取得してくる処理(省略)
  SerialBT.print(wordIndex); // Androidにデータ送信
}



Android側のSPP通信のソースコードは,前回 ↓ の記事でお世話になったこちらのサイトソースコードを参考にさせて頂きました.

ですので本記事では,M5stackのBluetooth serialと通信するために変更した設定部分と,SPP通信の受信内容処理部分だけを掲載いたします.

public class editor extends AppCompatActivity implements Runnable, View.OnClickListener, TextToSpeech.OnInitListener{
    private static final String TAG = "BluetoothSample";    /* tag */

    private BluetoothAdapter mAdapter;    /* Bluetooth Adapter */
    private BluetoothDevice mDevice;    /* Bluetoothデバイス */

    /* Bluetooth UUID(固定)  ← 一部の既存のアプリで確認することが可能*/
    private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");   /*確認したところ,どのESPを使っていてもBluetoothSerial時のUUIDはこの値でした!!!!!!!!!!!!!!!!!!!!!!!*/
    private final String DEVICE_NAME = "device_name";    /* デバイス名 環境に合わせて変更!!!!!!!!!!!!!!!!!!!!!!!*/

    private String receivedMessage = "";
    private String mFileName = "";
    private BluetoothSocket mSocket;    /* Soket */
    private Thread mThread;    /* Thread */
    private boolean isRunning;    /* Threadの状態を表す */
    private final String hira = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゐゆゑよらりるれろ小濁数半矢わをんー !(、)。\n ";

    // その他変数定義(省略)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 色々な初期化処理(省略)

        // Bluetoothのデバイス名を取得
        // デバイス名は、RNBT-XXXXになるため、
        // DVICE_NAMEでデバイス名を定義
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mStatusTextView.setText("SearchDevice");
        Set<BluetoothDevice> devices = mAdapter.getBondedDevices();
        for (BluetoothDevice device : devices) {

            if (device.getName().equals(DEVICE_NAME)) {
                mStatusTextView.setText("find: " + device.getName());
                mDevice = device;
            }
        }
        connectBluetooth();
    }


    // BluetoothSerialのスレッド処理(connectボタン押下後に実行)
    // このコードだけで動作するわけではありません! ↓ のサイトを参考に実装を進めてください.
    // https://fabkura.gitbooks.io/android-docs/content/article2-2.html
    @Override
    public void run() {
        InputStream mmInStream = null;

        Message valueMsg = new Message();
        valueMsg.what = VIEW_STATUS;
        valueMsg.obj = "connecting...";
        mHandler.sendMessage(valueMsg);

        try {
            // 取得したデバイス名を使ってBluetoothでSocket接続
            mSocket = mDevice.createRfcommSocketToServiceRecord(MY_UUID);
            mSocket.connect();
            mmInStream = mSocket.getInputStream();
            mmOutputStream = mSocket.getOutputStream();

            // InputStreamのバッファを格納
            byte[] buffer = new byte[1024];

            // 取得したバッファのサイズを格納
            int bytes;
            valueMsg = new Message();
            valueMsg.what = VIEW_STATUS;
            valueMsg.obj = "connected.";
            mHandler.sendMessage(valueMsg);

            connectFlg = true;

            while (isRunning) {
                String get = "";

                // InputStreamの読み込み(マイコンからのデータを受信)
                bytes = mmInStream.read(buffer);
                Log.i(TAG, "bytes=" + bytes);
                // String型に変換
                String readMsg = new String(buffer, 0, bytes);

                // null以外なら表示
                if (readMsg.trim() != null) {
                    valueMsg = new Message();
                    valueMsg.what = VIEW_INPUT;
                    get = get + readMsg;

                    Log.i(TAG, "value=" + readMsg); // 入力内容を表示
                    if(Integer.parseInt(get) < 64 && Integer.parseInt(get) >= 0){
                        speechText(hira.charAt(Integer.parseInt(get)));  // 音声出力(次の節で説明)
                        receivedMessage = receivedMessage + hira.charAt(Integer.parseInt(get)); // 過去の入力内容をバッファに保存
                        valueMsg.obj = receivedMessage;
                        mHandler.sendMessage(valueMsg); // 文章の描画処理
                    }
                }
            }
        }

        // エラー処理
        catch (Exception e) {
            valueMsg = new Message();
            valueMsg.what = VIEW_STATUS;
            valueMsg.obj = "Error1:" + e;
            mHandler.sendMessage(valueMsg);

            try {
                mSocket.close();
            } catch (Exception ee) {
            }
            isRunning = false;
            connectFlg = false;
        }
    }

    // その他メソッド(変更がなかったはずなので省略)

おそらく同じ設定で正しく接続できると思いますが,動作しない場合はUUID MY_UUIDDEVICE_NAMEが正しく設定できているか,今一度確認してみてください.

前者は,マイコンAndroidBluetooth設定をした後に,「Bluetooth Pair」などのアプリを用いることでUUIDを再確認できます.Bleutoothデバイス固有の値のはずで,いわゆるMACアドレスのようなものです.ESP32のBleutoothのライブラリではUUIDを自分で設定できますが,BluetoothSerialのライブラリでは固定されるようです.

後者は,M5stack側で設定した「SerialBT.begin("device_name");」の文言と一致していなければなりません.この文言は,AndroidBluetooth設定するときに表示される名前になりますので,ご自身の作品名を表すわかりやすい内容にするべきです.
私はカッコつけて「Future Flick Keyboard」と名乗っています.


Bleutoothの接続はconnectBluetooth()をしたタイミングで行われます.そのため,アプリ起動時にM5stackと接続できなかった場合は,ボタンを用意するなどで任意のタイミングでconnectBluetooth()を呼び出せるようにするべきです(参考サイトで実装してある).connectBluetooth後にはrunスレッドが立ち上がり,接続状況確認とデータ送信処理ができるようになります.

接続状態のとき,runスレッドではmmInStream.read(buffer)でbufferに受信内容を読み込み(byte型),それを一度String型(char型(数値)の配列として保存したかった記憶がある)に変更してreadMsgに保存しています.そして,null文字を取り除いて,receiveMessageに入力内容を保管していっています.この際,事前に用意したhiraというchar型配列から文字を取得してきます.文字は2byte文字ですので,hira.charAt()というメソッドを用いて取得する必要がありました.

今見返すと周りくどいやり方をしているように見えるため,Stringに変換するあたりやget変数あたりは単純化できるかもしれません.


音声出力部分

本作品では,M5stackから送られてきた文字情報を,テキストで表示するだけでなく音声で出力したいと考えています.その実装のために,ひとまず文字が1文字入力されるたびに音声出力するようにしました.音声出力に関係したソースコードは ↓ のようになっています.

public class editor extends AppCompatActivity implements Runnable, View.OnClickListener, TextToSpeech.OnInitListener{
    private TextToSpeech tts;
    // その他変数定義(省略)

    private void onCreate(){
        tts = new TextToSpeech(this, (TextToSpeech.OnInitListener) this);
        // 各種初期化(省略)
    }


    @Override
    public void onInit(int status) {
        if (TextToSpeech.SUCCESS == status) {
            //言語選択
            Locale locale = Locale.JAPAN;
            if (tts.isLanguageAvailable(locale) >= TextToSpeech.LANG_AVAILABLE) {
                tts.setLanguage(locale);
            } else {
                Log.d("Error", "Locale");
            }
        } else {
            Log.d("Error", "Init");
        }
    }

    private void speechText(char readMsg) {
        CharSequence cs = Character.toString(readMsg);
        tts.speak(cs, TextToSpeech.QUEUE_ADD, null, "id:1");
    }

    // その他メソッド(省略)

まず,クラスはTextToSpeech.OnInitListenerを継承する必要があります.そのうえで,onCreateでttsを初期化し,overrideのonInit()の内容を適当なサイトから引っ張ってきます.そして,音声出力用のメソッドをspeechText()を実装し,これを他のメソッドから呼び出すことで喋れるようになるのです.

この機能の実装には,Android端末側で日本語の音声を出力できる設定にしなければなりません.設定アプリの言語設定から,「テキスト読み上げの設定」の項目を設定します.私はこのサイトを参考に設定しました.


ここまでのシステム

GitHubに全体のソースコードを載せています. M5stack側:futureFlickKeyboard/futureKeyboard/futureKeyboard.ino Android側:futureFlickKeyboardApp/

github.com

今回紹介した機能についてのデモ動画です.完成形が見えてきた感じがしますね.


物理フリック入力キーボードのアプリテスト

棒読み感がたまらんです.


おわりに

最終的にはIMEアプリとセットで完成させたいと考えているのですが,実装にどこまで時間をかけられるか心配です...研究も別にあるのでどっちも進捗がでない.

物理フリック入力キーボードですが,Googleのエイプリルフールネタがあるのは知っていたのですが,ほかにも実装したことがある人がいたようです.
二番煎じかもしれませんが,私のシステムは両手を用いて自由な位置に持つことができ,両手であることで入力速度が向上する可能性がある点で差別化できると考えています.また,入力と同時に音声が出力できることで,聴覚障がい者と健常者のスムーズな会話にも貢献できるのではないかと考えてます.未来が捗る.


ちなみに10月現在では,基板を設計・発注・実装までを進めており,両手化に成功しています.あとは入力機能の強化と持ち手の作成段が残っているのですが,完成形についてはGUGEN2020等に提出してからご報告しようかなと考えています.

結構良いものができてきているので,しばしお待ちいただければ幸いです.





f:id:ume-boshi:20201019103956j:plain:w300
いいキースイッチ使っちゃうぞ~~