ETロボコン Bluetoothログ受信

こちらの記事は[ケーシーエスキャロット Advent Calendar 2018](https://qiita.com/advent-calendar/2018/kcscarrot)の24日目の記事です。

今年度もETロボコンに参加したのですが、難所エリアに階段が加わって大変苦労しました。
階段に限らず攻略に際して、段差だったり距離だったりを計測する必要があったのですが、計測するにはログに出力して確認する必要があります。
という訳で、VB.Netを使用してBluetooth 経由でロボットからログを取得・表示するツールを作成してみたいと思います。

### 準備

上記で記載した通り、VB.Netを使用するので、以下をインストールします。

- Visual Studio 2017 Community 2017

### プロジェクトの生成

ファイル > 新規作成 > プロジェクトから、Visual Basicの「Windowsフォームアプリケーション（.NET Framework）」を選択して新規プロジェクトを生成します。
（プロジェクト名などは適宜変更）

### フォームの配置

プロジェクト作成後、フォーム上に以下の図のようにコンポーネント群を配置します。
![frm1.png](https://qiita-image-store.s3.amazonaws.com/0/218385/dfdd5eae-ca55-bf55-6b1f-0a1460fe9ffc.png)

### コード(VB.Net)

コンポーネントを配置したら、コードを記載していきます。

#### ポート情報の取得

コンピュータで使用可能なシリアルポートを列挙して、コンボボックス内に追加しています。

```
    ''' <summary>
    ''' 接続可能なポート番号の取得・設定
    ''' </summary>
    Private Sub initPortName()
        ' ポート番号の取得・ソート
        Dim portNames As String() = SerialPort.GetPortNames()
        Array.Sort(portNames, StringComparer.OrdinalIgnoreCase)

        With Me.cmbPort
            .Items.Clear()

            For Each port In portNames
                .Items.Add(port)
            Next

            .SelectedIndex = 0
        End With
    End Sub
```

#### ポートのオープンクローズ

先で追加したコンボボックスで選択したポートでシリアルポートをオープン・クローズする処理です。

```
    ''' <summary>
    ''' Bluetooth ポートオープン
    ''' </summary>
    Private Sub openBluetooth()
        Try
            With Me.spBluetooth
                'オープンするシリアルポートをコンボボックスから取り出す.
                .PortName = Me.cmbPort.SelectedItem.ToString()

                'ボーレートをコンボボックスから取り出す.
                .BaudRate = 7200

                'データビットをセットする. (データビット = 8ビット)
                .DataBits = 8

                'パリティビットをセットする. (パリティビット = なし)
                .Parity = Parity.None

                'ストップビットをセットする. (ストップビット = 1ビット)
                .StopBits = StopBits.One

                '文字コードをセットする.
                .Encoding = Encoding.UTF8

                'シリアルポートをオープンする.
                .Open()

                Me.btnConnect.Enabled = False
            End With
        Catch ex As Exception
            MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub
```

```
    ''' <summary>
    ''' Bluetooth ポートクローズ
    ''' </summary>
    Private Sub closeBluetooth()
        Try
            Me.spBluetooth.Close()
        Catch ex As Exception
            MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub
```

#### データの送受信

ここでは、シリアルポートから受信したデータをリストへ書き込み、また、データを送信する処理を実現しています。

```
    Private Delegate Sub addRecvLogDataDelegate(recv As String)

    ''' <summary>
    ''' 受信文字列の追加
    ''' </summary>
    ''' <param name="recv">受信文字列</param>
    Private Sub addRecvLogData(recv As String)
        If IsNothing(recv) Then
            Return
        End If

        Me.lstLog.Items.Add(recv)
        Me.lstLog.SelectedIndex = Me.lstLog.Items.Count - 1
    End Sub
```

```
    ''' <summary>
    ''' 指定文字列の送信
    ''' </summary>
    ''' <param name="text">送信文字列</param>
    Private Sub sendText(ByVal text As String)
        Try
            'シリアルポートからテキストを送信する.
            Me.spBluetooth.Write(text.TrimEnd())
        Catch ex As Exception
            MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try
    End Sub
```

#### イベントハンドラ

フォーム上に追加したコンポーネント群のイベントハンドラを追加します。

```
    ''' <summary>
    ''' フォーム ロードハンドラ
    ''' </summary>
    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        initPortName()
    End Sub
```
```
    ''' <summary>
    ''' 接続ボタンクリックハンドラ
    ''' </summary>
    Private Sub btnConnect_Click(sender As Object, e As EventArgs) Handles btnConnect.Click
        If Me.spBluetooth.IsOpen Then
            closeBluetooth()
        Else
            openBluetooth()
        End If
    End Sub
```
```
    ''' <summary>
    ''' 送信ボタンクリックハンドラ
    ''' </summary>
    Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click
        sendText("1")
    End Sub
```
```
    ''' <summary>
    ''' シリアルポート 受信ハンドラ
    ''' </summary>
    Private Sub spBluetooth_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles spBluetooth.DataReceived
        'オープンしていない場合、処理を行わない.
        If Not Me.spBluetooth.IsOpen Then
            Return
        End If

        Try
            'メインスレッドへ受信データを渡す
            Invoke(New addRecvLogDataDelegate(AddressOf addRecvLogData), Me.spBluetooth.ReadExisting())
        Catch ex As Exception
            MessageBox.Show(ex.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

    End Sub
```

#### 起動確認

以上でツールは完了です。
動かして、コンボボックスをクリックすると、シリアルポートが表示されると思います。

![combo.png](https://qiita-image-store.s3.amazonaws.com/0/218385/b76abef7-29ca-cf92-2ae3-08e74df66c7f.png)

### コード (C)

続いて、ロボット側（C）ですが、こちらは[サポートサイト](https://sourceforge.net/p/etroboev3/wiki/problem_and_coping/)に記載があるので、起動開始部分とログ送信部分を抜粋するに留めます。

起動開始部分

```
    // Bluetooth接続
    while(!ev3_bluetooth_is_connected()){
        tslp_tsk(100);
    }

    FILE* bt = ev3_serial_open_file(EV3_SERIAL_BT);

    while (1) {
        uint8_t c = fgetc(bt);
        if (c == '1') {
           // スタート処理
           break;
        }
    }

    fclose(bt);
```

ログ送信部分

```
    const char *pMsg = "Hello EV3!";

    FILE* sp;

    sp = ev3_serial_open_file(EV3_SERIAL_DEFAULT)

    fprintf(sp, "%s\r\n", pMsg);

    fclose(sp);
```

### 動作確認

#### 確認準備

コード部分は完了したので、ログの受信が行えます。
…が、以下の設定を行う必要があります。

- rc.conf.ini ファイルの修正

- ペアリングの設定

ロボット内の設定ファイル(rc.conf.ini)で以下の記載を行います。

```
[Bluetooth]
LocalName=xxxxxx
PinCode=xxxx
[Debug]
DefaultPort=BT
```

あとは、実際にロボットを起動してペアリングを行えば、準備完了です。

#### 動作検証

ロボットおよびツールを起動します。
接続ボタンを押下して、実際に接続が完了すると、接続ボタンがDisable状態になります。
その状態から、開始ボタンを押下すると、ロボット側に「1」が送信されて、ロボット側から送られてくるログがリストボックス上に表示されます。

![log.png](https://qiita-image-store.s3.amazonaws.com/0/218385/e1a04f5e-709e-d248-0c89-7c9a8fc31853.png)

### 最後に

いかがでしょうか。
これでログの受信が行えるようになりました。
ただ、このままだと使い勝手が悪いので、受信したログをファイルに落とせるようにしたり、ログにキーワードを紐付けて、絞り込み表示にしたりすると利便性が高まると思います。