Imports System.IO.Ports
Imports System.IO
Imports System.Text
Imports System.Threading

' UART通信プログラム（Walk用）
' Version 1.00  2019/03/31
' VBforWolk
' by Hajime Sugiyama

Public Class FormVBforWalk
    ' ＵＡＲＴ通信
    ' RX_STARTCODE 0xAA					/* 受信ﾊﾟｹｯﾄのｽﾀｰﾄｺｰﾄﾞ */
    ' TX_STARTCODE 0xAA					/* 送信ﾊﾟｹｯﾄのｽﾀｰﾄｺｰﾄﾞ */
    ' 受信ﾃﾞｰﾀ receivedata() = RX_STARTCODE,CAT,PACNO,DATA0,･･･,DATA7,CHECKCODE
    ' 送信ﾃﾞｰﾀ senddata() = TX_STARTCODE,CAT,PACNO,DATA0,･･･,DATA15,CHECKCODE
    ' RX_STARTCODE･TX_STARTCODE:ｽﾀｰﾄｺｰﾄﾞ、CHECKCODE:誤り検出符号(ｽﾀｰﾄｺｰﾄﾞと全ﾃﾞｰﾀのEXOR
    ' SerialPort("COM14", 9600bps, Parity.Odd, data 8bit, StopBits.One)
    ' LSB first（ＬＳＢから送信）
    '定数宣言
    Private Const RX_STARTCODE As Byte = &HAA               '受信パケットヘッダ
    Private Const TX_STARTCODE As Byte = &HAA               '送信パケットヘッダ
    Private Const SDLENGTH = 12                             '送信データ長
    Private Const RDLENGTH = 12                             '受信データ長
    Private Const REC_PACKET_MAX_NUMBER = 1024              '格納受信パケット最大数
    Private Const SEQUENCE_PACKET_MAX As Integer = 8        'シーケンスデータパケットの最大数

    ' 通信速度9600bpsに十分な受信バッファのサイズを確保
    ' 1秒間以上のデータをバッファに格納可能
    Private packet_no As Byte                               '今回の（送信）パケット番号：要確認
    Private packet_no_last As Byte                          '前回の（送信）パケット番号：要確認
    Private dataposition As Integer                         'パケット中のデータ格納位置

    Private receivebuffer(1024) As Byte                     '受信データバッファ
    Private receivetemp(RDLENGTH - 1) As Byte               '受信データ一次確定バッファ
    Private receivedata(RDLENGTH - 1) As Byte               '受信データ確定バッファ
    Private datasize As Integer                             '受信バッファのデータ数
    Private receive_data_counter As Integer                 '格納受信パケット数
    Private receive_data(RDLENGTH - 1) As Byte              '確定受信データ格納配列
    Private receiveflag As Boolean                          'パケット受信完了フラグ
    Private RState As ReceiveState                          '受信状態変数
    Private message As String                               '受信パケット格納用変数（１６進ASCIIコード文字表示）

    Private sendbuffer(SDLENGTH - 1) As Byte                '送信データバッファ
    Private senddata(SDLENGTH - 1) As Byte                  '送信データ一次格納バッファ
    Private scheckcode As Byte                              '誤り検出用変数（送信）

    Private Enum ReceiveState As Integer                    '受信状態
        HEADER                                              '　ヘッダー検出
        RECEIVEDATA                                         '　データ格納
        CHECKDATA                                           '　誤り検出
    End Enum

    Private Enum OP_MODE_RES As Byte                        '応答の種別
        ACK_RES = &H6                                       '　肯定応答（accept）
        NAK_RES = &H15                                      '　否定応答（busy）
    End Enum

    Private Enum DataCategory As Byte                       '送信データ種別
        OPERATION_MODE = &H1                                '　動作形態指示
        MOTION_COMMAND = &H4                                '　動作指示
        SEQ_DATA_SEND = &H8                                 '　シーケンスデータ送信
        DATA_REQUEST = &H5                                  '　データ要求
        PULSE_INQUIRY = &H7                                 '　パルス幅問合せ
    End Enum

    Private Enum OP_MODE As Byte                            '動作形態の種別
        BUILTIN_PGM = &H1                                   '　内蔵プログラム動作モード
        RX_PGM = &H4                                        '　転送プログラム動作モード
        PC_OPM = &H10                                       '　パソコン操作モード
    End Enum

    Private operation_mode As OP_MODE

    Private Enum OPERATION_ORDER As Byte                    '動作指示の種別
        FORWARD = &H1                                       '　前進
        CLOCKWIZE = &H2                                     '　右回転
        BACKWARD = &H4                                      '　後退
        COUNTERCLOCKWIZE = &H8                              '　左回転
        DANCE = &H6                                         '　踊り
        BRAKE = &HF                                         '　停止
    End Enum

    '送信用シーケンスデータ
    '{スタートコード，シーケンスデータ送信，パケット番号，（動作種別，回数）・・・，チェックコード（別途計算）｝
    Private packet_data(,) As Byte = {
        {&HAA, &H8, &H0, &H6, &H2, &H1, &H4, &H6, &H2, &H4, &H4, &H0},  '踊り（２回）、前進（４歩）、踊り（２回）、後退（４歩）
        {&HAA, &H8, &H1, &H6, &H2, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '踊り（２回）、終了
        {&HAA, &H8, &H2, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '
        {&HAA, &H8, &H3, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '
        {&HAA, &H8, &H4, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '
        {&HAA, &H8, &H5, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '
        {&HAA, &H8, &H6, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0},  '
        {&HAA, &H8, &H7, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0, &H0}   '
    }

    'ポート番号を探索しオープンするクラスFormSetupSerialPortのインスタンスを作成する
    Private SetupSerial As New FormSetupSerialPort()

    Public GetPort As Boolean 'ポート番号取得状況　true:取得済　false:未取得

    'シリアルポートオブジェクト
    ' ポート番号(COM14)はダミー
    Public port As SerialPort = New SerialPort("COM14", 9600, Parity.Odd, 8, StopBits.One)

    'コントロールを操作するスレッド
    Private ExecuteControlThread As Thread

    'フォームコントロール開始時の処理
    Private Sub FormVBforWalk_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        GetPort = False                                     'ポート未取得
        SetupSerial.ShowDialog(Me)                          'オーナーウィンドウにこのウィンドウMeを指定する
        SetupSerial.Dispose()                               'FormSetupSerialPortを開放する
        If GetPort = False Then                             'ポートが取得できなければ
            Me.Close()                                      'このフォームを閉じる
        End If
        Initialize()                                        '初期化処理
    End Sub

    '初期化処理
    Private Sub Initialize()
        '画面表示の初期化
        '内蔵シーケンス実行画面表示
        DisplayBuiltinPGM()
        '動作形態を内蔵シーケンス実行状態にする
        operation_mode = OP_MODE.BUILTIN_PGM
        '通信の初期化
        packet_no = 0                                       'パケット番号初期化
        receive_data_counter = 0                            '受信データに位置カウンタ初期化
    End Sub

    'シリアルポートオープン
    Public Sub PortOpen()
        'Dim test As Boolean
        'シリアルポートハンドラーの登録
        AddHandler port.DataReceived, AddressOf DataReceivedHandler
        port.Open()                                         'ポートオープン
    End Sub

    'シリアルポートクローズ
    Private Sub PortClose()
        port.Close()
    End Sub

    'シリアルデータ送信
    Private Sub SendSerialData()
        Dim i As Integer
        '送信データを送信バッファへ格納
        For i = 0 To SDLENGTH - 1
            sendbuffer(i) = senddata(i)
        Next
        port.Write(sendbuffer, 0, SDLENGTH)                 'バイトデータの送信
        packet_no_last = senddata(2)
    End Sub

    'データ受信イベント処理
    Private Sub DataReceivedHandler(
        sender As Object,
        e As SerialDataReceivedEventArgs)
        'CType(式,指定型)：式を指定型に変換
        Dim sp As SerialPort = CType(sender, SerialPort)
        datasize = sp.Read(receivebuffer, 0, 1024)          '受信データと受信データ長の取得
        getReceiveData()                                    '確定データ取得処理
    End Sub

    '確定データ取得処理
    Private Sub getReceiveData()
        Dim counter As Integer                              '受信バッファのデータ位置
        Dim buffer As Byte                                  '受信データ（バイト形式）一時格納バッファ
        Dim checkcode As Byte                               '誤り検出用変数
        Dim i As Integer                                    '局所的なカウント用変数
        For counter = 0 To datasize - 1
            buffer = receivebuffer(counter)                 '受信データの取出し
            Select Case RState
                Case ReceiveState.HEADER                    'ヘッダー検出状態
                    If buffer = TX_STARTCODE Then
                        dataposition = 0                    'パケット中のデータ位置先頭
                        receivetemp(dataposition) = buffer  '受信パケット一次確定バッファへの格納
                        RState = ReceiveState.RECEIVEDATA   'データ格納状態へ
                    Else
                        RState = ReceiveState.HEADER        'ヘッダー検出状態へ
                    End If
                Case ReceiveState.RECEIVEDATA               'データ格納状態
                    dataposition = dataposition + 1         'パケット中のデータ位置を一つ増やす
                    receivetemp(dataposition) = buffer      '受信パケット一次確定バッファへの格納
                    If dataposition = (RDLENGTH - 2) Then   'パケットの最後のデータ
                        RState = ReceiveState.CHECKDATA     '誤り検出状態へ
                    End If
                Case ReceiveState.CHECKDATA                 '誤り検出状態
                    '誤り検出コードの算出
                    checkcode = 0
                    For i = 0 To (RDLENGTH - 2)             '誤り算出
                        checkcode = checkcode Xor receivetemp(i)
                    Next
                    If buffer = checkcode Then              '誤りなし
                        For i = 0 To (RDLENGTH - 1)         'パケットデータを受信確定バッファへ格納
                            receive_data(i) = receivetemp(i)
                        Next i
                        '受信データ記録用変数へ１６進ASCIIコード文字で受信確定パケットを追記
                        message = Microsoft.VisualBasic.Right("0" & Hex(receive_data(0)), 2)
                        For i = 1 To 11
                            message &= ";" & Microsoft.VisualBasic.Right("0" & Hex(receive_data(i)), 2)
                        Next i
                        'コントロールを操作するスレッドを生成する
                        Me.ExecuteControlThread = New Thread(New ThreadStart(AddressOf Me.ThreadProcControl))
                        Me.ExecuteControlThread.Start()     'DisplayUpdate()を実行する
                        receiveflag = True                  '受信完了フラグを立てる
                        receive_data_counter += 1           '格納受信パケット数を増加する
                    Else                                    '誤りあり
                        'データ異常であれば前回のデータを保持する
                    End If
                    RState = ReceiveState.HEADER            'ヘッダー検出状態へ
            End Select
        Next
        buffer = 0                                          '受信データ一時格納バッファクリア
    End Sub

    'メソッドへの参照
    Delegate Sub DisolayUpdateDelegate()

    Private Sub ThreadProcControl()
        'Invokeメソッド（Controlクラス（System.Windows.Forms名前空間）のメソッド）
        '   指定したデリゲートにメソッドへの参照を登録する
        '   【目的】他のスレッドからコントロールを操作することをできるようにする。
        '   【背景】スレッド間での悪影響を防止するため、コントロールはそれを管理している
        '           スレッド以外からは通常操作できないようになっている。
        '　（通信の処理はコントロールの操作処理と別のスレッドで処理される）
        '　（スレッド：並行して実行される処理の実行単位）
        '　（コントロール：ＧＵＩ画面上の部品）
        '　（デリゲート：引数と戻り値を使用可能な「メソッドへの参照」）
        Invoke(New DisolayUpdateDelegate(AddressOf DisplayUpdate))
    End Sub

    'デリゲートに登録するメソッド
    Sub DisplayUpdate()
        Select Case receive_data(1)                         '送信データ種別
            Case DataCategory.OPERATION_MODE                '動作形態指示
                DisplayOperationMode()
            Case DataCategory.MOTION_COMMAND                '動作指示
                DisplayPcOPM()
                DisplayMotionCommandButton()
            Case DataCategory.SEQ_DATA_SEND                 'シーケンスデータ送信
                SendAndDisplaySeqData()
        End Select
        If TextBoxReceivedData.Text = "" Then
            Me.TextBoxReceivedData.AppendText(message)
        Else
            Me.TextBoxReceivedData.AppendText(vbCrLf & message)
        End If
    End Sub

    '動作形態画面表示
    Private Sub DisplayOperationMode()
        Select Case receive_data(3)
            Case OP_MODE.RX_PGM
                DisplayRxPGM()                              'シーケンスデータ送信・実行画面表示
            Case OP_MODE.BUILTIN_PGM
                DisplayBuiltinPGM()                         '内蔵シーケンス実行画面表示
            Case OP_MODE.PC_OPM
                DisplayPcOPM()                              'パソコン操作画面表示
        End Select
    End Sub

    'シーケンスデータ送信・実行画面表示
    Private Sub DisplayRxPGM()
        '歩行操作ボタン表示消去
        ButtonForward.Hide()
        ButtonClockwize.Hide()
        ButtonBackward.Hide()
        ButtonCounterClockwize.Hide()
        ButtonDance.Hide()
        ButtonStop.Hide()
        'タイトル表示
        LabelTitle.Text = "送信シーケンス実行モード"
        '実行画面表示
        ButtonOPmode.Text = "ＰＣ操作モードへ"              '動作モード変更ボタン表示更新
        LabelTx.BackColor = Color.DarkGray                  'データ送信状態表示
        LabelTx.ForeColor = SystemColors.GrayText
        LabelTx.Show()
        ButtonPGsend.Show()                                 'シーケンスデータ送信ボタン表示
        '動作形態をシーケンスデータ送信・実行状態へ
        operation_mode = OP_MODE.RX_PGM
    End Sub

    '内蔵シーケンス実行画面表示
    Private Sub DisplayBuiltinPGM()
        '歩行操作ボタン表示消去
        ButtonForward.Hide()
        ButtonClockwize.Hide()
        ButtonBackward.Hide()
        ButtonCounterClockwize.Hide()
        ButtonDance.Hide()
        ButtonStop.Hide()
        LabelTx.Hide()
        ButtonPGsend.Hide()
        'タイトル表示
        LabelTitle.Text = "内蔵シーケンス実行モード"
        '動作モード変更ボタン表示更新
        ButtonOPmode.Text = "送信モードへ"
        '動作形態を内蔵シーケンス実行状態へ
        operation_mode = OP_MODE.BUILTIN_PGM
    End Sub

    'パソコン操作画面表示
    Private Sub DisplayPcOPM()
        'データ送信状態表示・シーケンスデータ送信ボタン表示消去
        LabelTx.Hide()
        ButtonPGsend.Hide()
        '歩行操作ボタン表示
        ButtonForward.Show()
        ButtonClockwize.Show()
        ButtonBackward.Show()
        ButtonCounterClockwize.Show()
        ButtonDance.Show()
        ButtonStop.Show()
        CommandButtonBackColorInit()
        ButtonStop.BackColor = Color.Aqua
        'タイトル表示
        LabelTitle.Text = "パソコン操作モード"
        '動作モード変更ボタン表示更新
        ButtonOPmode.Text = "内蔵モードへ"
        '動作形態をパソコン操作状態へ
        operation_mode = OP_MODE.PC_OPM
    End Sub

    Private Sub DisplayMotionCommandButton()
        Select Case receive_data(3)                         '動作指示の種別
            Case OPERATION_ORDER.BRAKE                      '停止
                CommandButtonBackColorInit()
                ButtonStop.BackColor = Color.Aqua
            Case OPERATION_ORDER.FORWARD                    '前進
                CommandButtonBackColorInit()
                ButtonForward.BackColor = Color.Aqua
            Case OPERATION_ORDER.CLOCKWIZE                  '右回転
                CommandButtonBackColorInit()
                ButtonClockwize.BackColor = Color.Aqua
            Case OPERATION_ORDER.BACKWARD                   '後退
                CommandButtonBackColorInit()
                ButtonBackward.BackColor = Color.Aqua
            Case OPERATION_ORDER.COUNTERCLOCKWIZE           '左回転
                CommandButtonBackColorInit()
                ButtonCounterClockwize.BackColor = Color.Aqua
            Case OPERATION_ORDER.DANCE                      '踊り
                CommandButtonBackColorInit()
                ButtonDance.BackColor = Color.Aqua
        End Select
    End Sub

    'シーケンスデータの送信と画面表示
    Private Sub SendAndDisplaySeqData()
        If receive_data(2) < SEQUENCE_PACKET_MAX - 1 Then   '送信パケットが残っている
            If receive_data(2) = packet_no_last + 1 Then    '　前回受信したパケット番号と連続している
                Select Case receive_data(3)                 '　　受信した応答データは
                    Case OP_MODE_RES.ACK_RES                '　　　肯定応答（accept）
                        SendSequenceData(receive_data(2) + CByte(1))'次データ送信
                    Case OP_MODE_RES.NAK_RES                '　　　否定応答（busy）
                        SendSequenceData(receive_data(2))   '　　　　シーケンスデータパケットの再送
                End Select
            Else                                            'パケット番号が抜けている
                SendSequenceData(packet_no_last + 1)        '　　シーケンスデータパケットの再送
            End If
        ElseIf receive_data(2) = SEQUENCE_PACKET_MAX - 1 Then '受信パケットは最終送信パケットへの応答
            Select Case receive_data(3)                     '　受信した応答データは
                Case OP_MODE_RES.ACK_RES                    '　　肯定応答（accept）
                    LabelTx.Text = "送信完了"               '　　　送信完了表示
                    LabelTx.ForeColor = Color.DarkSlateBlue
                    LabelTx.BackColor = Color.Aqua
                Case OP_MODE_RES.NAK_RES                    '　　否定応答（busy）
                    SendSequenceData(receive_data(2))       '　　　シーケンスデータパケットの再送
            End Select
        End If
    End Sub

    'シーケンスデータの送信
    '　二次元配列に格納したシーケンスデータを送付する
    '　引数：パケット番号
    Private Sub SendSequenceData(ByVal data_no As Integer)
        Dim i As Integer
        '配列データを送信バッファに格納
        For i = 0 To SDLENGTH - 2
            senddata(i) = packet_data(data_no, i)
        Next i
        CalculateCheckCode()                                'チェックコード算出
        SendSerialData()                                    'パケットの送信
    End Sub

    'シーケンスデータの送信（ボタン押下イベント処理）
    Private Sub ButtonPGsend_Click(sender As Object, e As EventArgs) Handles ButtonPGsend.Click
        SendSequenceData(0)                                 '先頭パケットの送信
    End Sub

    '動作形態設定コマンドパケットの送信（ボタン押下イベント処理）
    Private Sub ButtonOPmode_Click(sender As Object, e As EventArgs) Handles ButtonOPmode.Click
        Select Case operation_mode
            Case OP_MODE.BUILTIN_PGM                        '内蔵シーケンス実行モード設定パケット送信
                SendCommand(DataCategory.OPERATION_MODE, OP_MODE.RX_PGM)
            Case OP_MODE.PC_OPM                             'パソコン操作モード設定パケット送信
                SendCommand(DataCategory.OPERATION_MODE, OP_MODE.BUILTIN_PGM)
            Case OP_MODE.RX_PGM                             'シーケンスデータ送信・実行設定パケット送信
                SendCommand(DataCategory.OPERATION_MODE, OP_MODE.PC_OPM)
        End Select
    End Sub

    '送信データの初期化（スタートコード・パケット番号・その他データクリア）
    Private Sub SendDataInitialize()
        Dim i As Integer                                    '局所的なカウント用変数
        '送信データの格納   senddata(0 to 11) 
        senddata(0) = TX_STARTCODE                          'スタートコード
        senddata(2) = packet_no                             'パケット番号
        For i = 3 To 10                                     'その他データを0（チェックコードは除く）
            senddata(i) = 0 '
        Next
    End Sub
    '誤り訂正符号の算出・設定
    Private Sub CalculateCheckCode()
        'スタートコードを含むbyteデータのXOR（排他的論理和）
        scheckcode = 0
        For i = 0 To SDLENGTH - 2
            scheckcode = scheckcode Xor senddata(i)
        Next
        senddata(SDLENGTH - 1) = scheckcode
    End Sub

    'コマンドパケット送信
    '　引数：送信データ種別、歩行種別
    Private Sub SendCommand(command_category As Byte, command_type As Byte)
        SendDataInitialize()                                '送信データ初期化
        senddata(1) = command_category                      '送信データ種別設定
        senddata(3) = command_type                          '歩行種別設定
        CalculateCheckCode()                                'チェックコード算出
        SendSerialData()                                    'パケット送信
        If packet_no < &HFF Then                            'パケット番号の増加
            packet_no = packet_no + 1
        Else
            packet_no = 0
        End If
    End Sub

    '前進コマンドの送信（ボタン押下イベント処理）
    Private Sub ButtonForward_Click(sender As Object, e As EventArgs) Handles ButtonForward.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.FORWARD)
    End Sub

    '右回転コマンドの送信
    Private Sub ButtonClockwize_Click(sender As Object, e As EventArgs) Handles ButtonClockwize.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.CLOCKWIZE)
    End Sub

    '後退コマンドの送信（ボタン押下イベント処理）
    Private Sub ButtonBackward_Click(sender As Object, e As EventArgs) Handles ButtonBackward.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.BACKWARD)
    End Sub

    '左回転コマンドの送信（ボタン押下イベント処理）
    Private Sub ButtonCounterClockwize_Click(sender As Object, e As EventArgs) Handles ButtonCounterClockwize.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.COUNTERCLOCKWIZE)
    End Sub

    'ダンスコマンドの送信（ボタン押下イベント処理）
    Private Sub ButtonDance_Click(sender As Object, e As EventArgs) Handles ButtonDance.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.DANCE)
    End Sub

    '停止コマンドの送信（ボタン押下イベント処理）
    Private Sub ButtonStop_Click(sender As Object, e As EventArgs) Handles ButtonStop.Click
        SendCommand(DataCategory.MOTION_COMMAND, OPERATION_ORDER.BRAKE)
    End Sub

    '動作指示ボタンの背景色初期化
    Private Sub CommandButtonBackColorInit()
        ButtonForward.BackColor = SystemColors.Control
        ButtonClockwize.BackColor = SystemColors.Control
        ButtonBackward.BackColor = SystemColors.Control
        ButtonCounterClockwize.BackColor = SystemColors.Control
        ButtonDance.BackColor = SystemColors.Control
        ButtonStop.BackColor = SystemColors.Control
    End Sub
End Class
