Vuzix Bladeで開発してみた（Bluetooth of Client 編 ）

#はじめに
お世話になります。Apv(@apv23973)です。

前回＜Vuzix Bladeで開発してみた（Bluetooth of Server 編 ）＞
https://qiita.com/apv23973/items/3fa55b8f7bb8337b96b5
に続いてVuzix bladeとスマホを使ったBluetooth通信にチャレンジです。

今回はクライアント側の処理を記述していきます。

クライアント側の処理は基本的なVuzix Bladeの処理＋Bluetooth処理になります。
※基本的処理というのはVuzix Blade特有の処理です。
　参考のURLを見てください。

#実装

```ruby:main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <!--サーバー情報 -->
        <TextView
            android:id="@+id/lblserver_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:textSize="@dimen/txt_sv_size"
            android:text="@string/sv_title"/>

        <TextView
            android:id="@+id/txtserver_name"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:textSize="@dimen/txt_sv_size"
            android:layout_height="wrap_content"/>

        <!--接続状態 -->
        <TextView
            android:id="@+id/lblconnect_status"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:textSize="@dimen/txt_sv_size"
            android:text="@string/con_title"/>

        <TextView
            android:id="@+id/txtconnect_status"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:textSize="@dimen/txt_sv_size"
            android:layout_height="wrap_content"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <!--結果 -->
        <TextView
            android:id="@+id/txt_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/res_title"/>

    </LinearLayout>

</LinearLayout>
```

Vuzix Bladeの画面ファイルです。
Bluetooth通信を行うサーバー（スマホ）と状態、
通信結果を表示する様にしています。

```ruby:Constants.xml
import java.util.UUID;

public interface Constants {

    public static final String BT_DEVICE = "Xperia XZ";
    public static final UUID BT_UUID = UUID.fromString(
            "41eb5f39-6c3a-4067-8bb9-bad64e6e0908");

    public static final int MESSAGE_BTNM = 3;
    public static final int MESSAGE_BT = 0;
    public static final int MESSAGE_TEMP = 2;

}
```

クライアント側もBluetooth接続用の名前と
UUIDを定数として設定するインタフェースを用意します。
UUIDはサーバー側と同じ値にする必要があります。
Bluetooth通信機器を自動で選別する仕組みにしたいところなのですが
今回は固定で機器を指定しています。

```ruby:MainActivity.java
import android.app.Activity;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.TextView;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Set;

public class MainActivity extends Activity {

    static final String TAG = "MainActivity";

    private int REQUEST_ENABLE_BT = 0;

    // Bluetooth
    private BluetoothAdapter bluetoothAdapter;
    private BTClientThread btClientThread;

    private ProgressDialog progressDialog;
    // サーバー名
    private TextView txtSVName;
    // 接続状態
    private TextView txtConSt;
    // 処理結果
    private TextView txtResult;

    final Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {

            String s;

            switch(msg.what){
                case Constants.MESSAGE_BTNM:
                    // サーバー情報を表示
                    s = (String) msg.obj;
                    if(s != null){
                        txtSVName.setText(s);
                    }
                    break;
                case Constants.MESSAGE_BT:
                    // 接続状態を表示
                    s = (String) msg.obj;
                    if(s != null){
                        txtConSt.setText(s);
                    }
                    break;
                case Constants.MESSAGE_TEMP:
                    // 結果を表示
                    progressDialog.dismiss();
                    txtResult.setText((String) msg.obj);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // サーバー名
        txtSVName = (TextView) findViewById(R.id.txtserver_name);
        txtSVName.setText(getString(R.string.MSG003));
        // 接続状態
        txtConSt = (TextView) findViewById(R.id.txtconnect_status);
        txtConSt.setText(getString(R.string.MSG002));
        // 処理結果
        txtResult = (TextView) findViewById(R.id.txt_result);
        txtResult.setText("");

        // 初期値
        if(savedInstanceState != null){
            txtConSt.setText("");
        }

        // Initialize Bluetooth
        // Bluetoothサポートチェック
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if( bluetoothAdapter == null ){
            Log.d(TAG, "This device doesn't support Bluetooth.");
            txtResult.setText(getString(R.string.SYSMSG001));
        }
        // BluetoothをONにする
        if(!bluetoothAdapter.isEnabled()){
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            this.startActivityForResult(intent, REQUEST_ENABLE_BT);
        }

        // ロック表示
        progressDialog = new ProgressDialog(MainActivity.this);
        progressDialog.setTitle("タブレットと通信中");
        progressDialog.setMessage("通信中");
        progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        progressDialog.show();

        // 初期化
        txtResult.setText("");

        // 通信開始
        btClientThread = new BTClientThread();
        btClientThread.start();

    }

    @Override
    protected void onPause(){
        super.onPause();
        if(btClientThread != null){
            btClientThread.interrupt();
            btClientThread = null;
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("", txtConSt.getText().toString());
    }

    public class BTClientThread extends Thread {

        InputStream inputStream;
        OutputStream outputStrem;
        BluetoothSocket bluetoothSocket;

        public void run() {

            byte[] incomingBuff = new byte[64];

            BluetoothDevice bluetoothDevice = null;
            Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
            for(BluetoothDevice device : devices){
                if(device.getName().equals(Constants.BT_DEVICE)) {
                    bluetoothDevice = device;
                    break;
                }
            }

            if(bluetoothDevice == null){
                Log.d(TAG, "No device found.");
                return;
            }

            try {

                bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(
                        Constants.BT_UUID);

                while(true) {

                    if(Thread.interrupted()){
                        break;
                    }

                    try {
                        bluetoothSocket.connect();

                        handler.obtainMessage(
                                Constants.MESSAGE_BTNM,
                                bluetoothDevice.getName())
                                .sendToTarget();
                        handler.obtainMessage(
                                Constants.MESSAGE_BT,
                                getString(R.string.MSG001))
                                .sendToTarget();

                        inputStream = bluetoothSocket.getInputStream();
                        outputStrem = bluetoothSocket.getOutputStream();

                        while (true) {

                            if (Thread.interrupted()) {
                                break;
                            }

                            // 送信
                            String command = "REQUEST";
                            outputStrem.write(command.getBytes());

                            // 応答
                            int incomingBytes = inputStream.read(incomingBuff);
                            byte[] buff = new byte[incomingBytes];
                            System.arraycopy(incomingBuff, 0, buff, 0, incomingBytes);
                            String strRecieveCD = new String(buff, StandardCharsets.UTF_8);
                            handler.obtainMessage(
                                    Constants.MESSAGE_TEMP,
                                    strRecieveCD)
                                    .sendToTarget();

                            // Update again in a few seconds
                            Thread.sleep(3000);
                        }

                    } catch (IOException e) {
                        // connect will throw IOException immediately
                        // when it's disconnected.
                        Log.d(TAG, e.getMessage());
                    }

                    handler.obtainMessage(
                            Constants.MESSAGE_BT,
                            getString(R.string.MSG002))
                            .sendToTarget();

                    // Re-try after 3 sec
                    Thread.sleep(3 * 1000);
                }

            }catch (InterruptedException e){
                e.printStackTrace();
            }
            catch (IOException e) {
                e.printStackTrace();
            }

            if(bluetoothSocket != null){
                try {
                    bluetoothSocket.close();
                } catch (IOException e) {}
                bluetoothSocket = null;
            }
        }
    }
}
```

```
// Bluetoothサポートチェック
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if( bluetoothAdapter == null ){
    Log.d(TAG, "This device doesn't support Bluetooth.");
    txtResult.setText(getString(R.string.SYSMSG001));
}
```

onCreateのイベントでBluetooth通信がサポートされているかチェックしています。

```
// BluetoothをONにする
if(!bluetoothAdapter.isEnabled()){
    Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    this.startActivityForResult(intent, REQUEST_ENABLE_BT);
}
```

続いてBluetooth通信がOFFに設定されている場合はONにする処理を記述しています。

```
BluetoothDevice bluetoothDevice = null;
Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
for(BluetoothDevice device : devices){
    if(device.getName().equals(Constants.BT_DEVICE)) {
        bluetoothDevice = device;
        break;
    }
}
```

ここでサーバー側の機器と名前が一致するものを探しています。
今回は固定で機器を指定していますが、選択式にする時はここを変える必要がありそうです。

```
// 送信
String command = "REQUEST";
outputStrem.write(command.getBytes());

// 応答
int incomingBytes = inputStream.read(incomingBuff);
byte[] buff = new byte[incomingBytes];
System.arraycopy(incomingBuff, 0, buff, 0, incomingBytes);
String strRecieveCD = new String(buff, StandardCharsets.UTF_8);
handler.obtainMessage(
    Constants.MESSAGE_TEMP,
    strRecieveCD)
    .sendToTarget();
```

ここで送信とサーバーからの応答を記述しています。
今回は"REQUEST"という文字列を送信すると
サーバー側から"Hellow World!!"という文字を返す（表示する）ようにしています。

#実行
実行時
![device-2019-04-14-213535.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379857/354c8a27-05d2-26d0-fe64-120dbf219509.png)

サーバー側を以下の画面で立ち上げると
![Screenshot_20190414-184528.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379857/3823401e-017e-0c29-5250-1950d9701df3.png)

サーバー側からの受信結果を表示
![device-2019-04-14-220452.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379857/f87ce2df-93b8-f99a-6770-3d23d63876ec.png)

#さいごに
先に書いた
＜Vuzix Bladeで開発してみた（QRコード読取編）＞
https://qiita.com/apv23973/items/281d3f695ed695de3c54
と今回のBluetoothとを組み合わせると
読取はVuzix Bladeで、処理はスマホ（タブレット）で行う事が出来そうです。

通信も出来た事だし
次は読取った画像をスマホに飛ばして画像処理とかをやってみたいなー
と思う今日この頃です。


#参考
Vuzix Blade(ARスマートグラス)のはじめかた
https://qiita.com/1901drama/items/16f2bed19cf78b575933

Vuzix Blade(ARスマートグラス)のはじめかた その2
https://qiita.com/1901drama/items/89cee4f5a40cb0faccc5

＜Android 間での RFCOMM チャネルの Bluetooth 通信 1/2＞
https://android.keicode.com/basics/bluetooth-rfcomm-socket.php

＜Android 間での RFCOMM チャネルの Bluetooth 通信 2/2＞
https://android.keicode.com/basics/bluetooth-rfcomm-socket-2.php