[教學] 以藍牙連接Raspberry Pi 3與LEGO Mindstorms EV3

安裝藍牙相關軟體

sudo apt-get install bluetooth bluez blueman

連接藍牙

因為我是透過SSH連接Raspberry Pi,便使用下面指令進入藍牙設定,如果有把Raspberry Pi用HDMI連接螢幕,應該可以透過GUI連接EV3。

sudo bluetoothctl

完成後會看到以 [bluetooth]# 起頭的狀態,此時便能輸入指令。

agent on
default-agent
scan on

接著打開EV3的藍牙,記得勾選 Visibility

EV3開啟藍牙

EV3藍牙選項

回到Raspberry Pi,搜尋到EV3後,以指令 pair 做配對,這邊可以用 Tab 將MAC address自動補全,這組MAC address稍後也會用到。

pair AA:BB:CC:DD:EE:FF

EV3會出現配對設定畫面,點確認

EV3確認連接

用預設的PIN 1234即可

在Raspberry Pi輸入1234完成配對。
輸入 exit 離開藍牙設定,下圖是操作的截圖可供參考。

Raspberry Pi 藍牙操作截圖

最後一步,利用 rfcomm 指令作 bind ,以利Python可用seiral package操作藍牙介面。

sudo rfcomm bind 0 AA:BB:CC:DD:EE:FF

EV3 藍牙封包

在進行測試之前,先了解EV3傳輸資料的格式,有助於之後編寫Raspberry Pi傳輸的程式。樂高官方提供的文件有講述藍牙的封包格式,這裡摘要需要的部分。

EV3藍牙指令格式

Byte # 意義 設定值
0 ~ 1 指令大小
Little Endian
除去這 2 bytes 後剩餘資料總數
2 ~ 3 訊息計數器
Little Endian
說明看起來是個正向的計數器,但實際上EV3完全沒用到
4 指令型態 EV3是設定0x81,不須等待接收端回覆
5 系統指令 EV3提供多種系統指令,這裡設定0x9E (WRITEMAILBOX)
6 ~ n 資料 根據系統指令的設定決定

下面直接介紹WRITEMAILBOX的格式,其他有興趣就去參考文件吧。

EV3 WRITEMAILBOX格式

Byte # 意義 設定值
6 標題大小 title字數,文件講不包含最後0x00 termination bit,但實際測試有算
7 ~ k-1 標題 title,EV3藍牙相關block的參數
k ~ k+1 訊息大小
Little Endian
訊息字數
k+2 ~ n 訊息 傳遞的訊息,三種型態:字串、數字、邏輯

Raspberry Pi接收測試

EV3的藍牙傳輸方塊有三種型態可供選擇:字串、數字、邏輯,依序測試不同資料型態會接收到什麼。

Python 程式碼

程式碼就不多做解釋,這支程式會把接收到的藍牙資料以16進位全部輸出在螢幕上。

#!/usr/bin/python3
import serial
import time

EV3 = serial.Serial('/dev/rfcomm0')
print("Listening for EV3 Bluetooth messages, press CTRL+C to quit.")

try:
    while 1:
        n = EV3.inWaiting()
        if n != 0:
            s = EV3.read(n)
            print("".join("{:02x} ".format(c) for c in s))
        time.sleep(0.2)

except KeyboardInterrupt:
    pass

EV3.close()
print("\nRX End")

EV3傳輸程式

EV3程式如下圖,傳遞藍牙訊息標題為 abc ,依序會測試字串、數字、邏輯

EV3傳輸程式

字串

在EV3程式中,傳遞訊息為 TEST 。下面是Raspberry Pi接收到的結果。

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17
10 00 01 00 81 9e 04 61 62 63 00 05 00 54 45 53 54 00
Byte # 說明
0 ~ 1 Byte 02到Byte 17,總計16 bytes
2 ~ 3 訊息計數器,EV3傳遞過來總是01
4 0x81,不須等待接收端回覆
5 0x9E,WRITEMAILBOX
6 標題大小, abc 外加 0x00 的zero termination共4 bytes
7 ~ 10 標題, a b c 0x00
11 ~ 12 訊息大小, TEST 外加 0x00 的zero termination共5 bytes
13 ~ 17 訊息, T E S T 0x00

可以觀察到文字都是以ASCII code表示。

數字

在EV3程式中,依序傳遞 012 。下面是Raspberry Pi接收到的結果。

數字 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
0 0f 00 01 00 81 9e 04 61 62 63 00 04 00 00 00 00 00
1 0f 00 01 00 81 9e 04 61 62 63 00 04 00 00 00 80 3f
2 0f 00 01 00 81 9e 04 61 62 63 00 04 00 00 00 00 40
Byte # 說明
0 ~ 1 Byte 02到Byte 17,總計16 bytes
2 ~ 3 訊息計數器,EV3傳遞過來總是01
4 0x81,不須等待接收端回覆
5 0x9E,WRITEMAILBOX
6 標題大小, abc 外加 0x00 的zero termination共4 bytes
7 ~ 10 標題, a b c 0x00
11 ~ 12 訊息大小,4 bytes
13 ~ 16 收到的數字

這邊數字實際上是float type,有興趣可以按按看轉換機

邏輯

在EV3程式中,依序傳遞 TrueFalse 。下面是Raspberry Pi接收到的結果。

數字 00 01 02 03 04 05 06 07 08 09 10 11 12 13
True 0c 00 01 00 81 9e 04 61 62 63 00 01 00 01
False 0c 00 01 00 81 9e 04 61 62 63 00 01 00 00
Byte # 說明
0 ~ 1 Byte 02到Byte 17,總計16 bytes
2 ~ 3 訊息計數器,EV3傳遞過來總是01
4 0x81,不須等待接收端回覆
5 0x9E,WRITEMAILBOX
6 標題大小, abc 外加 0x00 的zero termination共4 bytes
7 ~ 10 標題, a b c 0x00
11 ~ 12 訊息大小,1 byte
13 邏輯值, True1False0

Raspberry Pi傳送測試

有了上一章節的基礎,我們可以知道該怎麼轉換資料讓EV3能看懂,這章節實作以Raspberry Pi傳送資料的測試。

EV3接收程式

這裡我只傳送字串和數字,邏輯就不測試了。如果接收到標題 motor ,在 Port D 上的馬達就會依收到的數字轉動,值的範圍是 -100 到 +100 。如果接收到標題是 abc ,在EV3螢幕上就會顯示收到的字串。

EV3接收程式

Python程式碼

程式碼傳送的資料就依照上一章節實驗結果設計。理論上輸入數字可以看到馬達轉動,輸入字串可以看到EV3螢幕顯示結果。

#!/usr/bin/python3
import serial
import struct
import time

def tx_num(name, msg):
    msg_cnt = b'\x00\x00' # message counter (2 byte), but ev3 core tx don't update this counter
    cmd_type = b'\x81' # command type (1 byte), no reply
    cmd_sys  = b'\x9e' # system command, WRITEMAILBOX: 0x9e
    name_len = struct.pack('B', len(name)+1) # including null termination 0x00
    msg_len  = struct.pack('<H', 4) # float 4 bytes
    cmd_data = msg_cnt + cmd_type + cmd_sys + \
                   name_len + str.encode(name+'\x00') + \
                   msg_len + struct.pack('f', msg)
    cmd_len = struct.pack('<H', len(cmd_data))
    tx_data = cmd_len + cmd_data
    return tx_data

def tx_str(name, msg):
    msg_cnt = b'\x00\x00' # message counter (2 byte), but ev3 core tx don't update this counter
    cmd_type = b'\x81' # command type (1 byte), no reply
    cmd_sys  = b'\x9e' # system command, WRITEMAILBOX: 0x9e
    name_len = struct.pack('B', len(name)+1) # including null termination 0x00
    msg_len  = struct.pack('<H', len(msg)+1) # including null termination 0x00
    cmd_data = msg_cnt + cmd_type + cmd_sys + \
                   name_len + str.encode(name+'\x00') + \
                   msg_len + str.encode(msg+'\x00')
    cmd_len = struct.pack('<H', len(cmd_data))
    tx_data = cmd_len + cmd_data
    return tx_data

EV3 = serial.Serial('/dev/rfcomm0')
print("Ready for transmitting EV3 Bluetooth messages, press CTRL+C to quit.")

try:
    while True:
        msg = input('Enter your message: ')
        try: # float type
            num = float(msg)
            print(''.join('{:02x} '.format(c) for c in list(tx_num('motor', num))))
            EV3.write(tx_num('motor', num))
        except ValueError: # non-float type, treat as string
            print(''.join('{:02x} '.format(c) for c in list(tx_str('abc', msg))))
            EV3.write(tx_str('abc', msg))
        except KeyboardInterrupt:
            break
        time.sleep(0.2)
except KeyboardInterrupt:
    pass

EV3.close()
print("\nTX End")

疑難排解

  • 操作python噴出 [Errno 5] Input/output error 怎麼辦?
    • 重新連接一次藍牙
    • 多試幾次有機會會好
    • 重新開機Raspberry Pi
  • 怎麼重新配對?
    • sudo bluetoothctl 進藍牙指令操作,輸入 remove AA:BB:CC:DD:EE:FF 來移除原有的配對,然後再做一次配對。

參考資料

留言