從一開始,網路就一直是 Raspberry Pi 很重要的功能。而到了 Raspberry Pi 3 Model B,更是直接內建有線網路、WiFi 無線網路、藍芽等通訊功能,讓網路的應用更加容易與標準化。在這次的範例中,我們將透過 Android 手機上的應用程式與藍芽機制來控制連結在 Raspberry Pi GPIO 上的 LED 燈泡。這個範例總共會有兩個程式代碼,第一個是手機端的應用程式 (App),另外一個則是 Raspberry Pi 的 Python 程式,可用來接受手機應用程式的指令,並根據指令開啟或關閉 LED 燈泡。
在物聯網的世界裡,所有的設備都必須具備連網的能力。連結的方式有很多種,對 Raspberry Pi 來說,TCP/IP 協定跟藍芽協定算是兩種比較主要的方式。有線網路跟 WiFi 無線網路使用 TCP/IP 協定,而藍芽當然就是使用藍芽協定。兩者各有優缺點,不過最主要的差別之一在於藍芽通常用於小區域的面積,大多數手機或行動裝置所使用的 Class 2 範圍限制在 10 公尺之內。而使用 TCP/IP 協定的有線網路或 WiFi 無線網路範圍都遠大於 10 公尺。TCP/IP 協定透過所謂的路由技術,甚至可以直接連結全世界各地的電腦設備。事實上,我們目前所使用的網際網路就是架構在 TCP/IP 協定之上。
雖然 TCP/IP 協定擁有很強大的功能,但是在 TCP/IP 協定的世界中,兩個設備想要順利地溝通往往還需要其他額外的設備,像是 IP 分享器、交換器、路由器等。藍芽相對來說就簡單多了,只要兩個設備都支援藍芽就可以直接進行連線。為了因應物聯網的快速成長,新版的藍芽協定也可以支援 TCP/IP 協定直接連網,不過使用的是 IPv6,而且並不是所有的藍芽裝置都已經支援新版的功能。總結來說,不管是硬體或軟體的方便性,都使得藍芽在小範圍的應用上不失為一個好選擇,而且目前對大多數的裝置來說,藍芽也僅適用於小範圍內的兩個設備之間的連結。
設備配對
在兩個設備利用藍芽進行溝通之前,必須先經過所謂的配對,也就是我們需要將用來控制 Raspberry Pi 的 Android 手機與被控制的 Raspberry Pi 進行配對。
因為每種手機的介面不太一樣,所以在手機部分須根據實際狀況稍作調整。
- 手機端開啟: “設定” -> “藍芽”。如果藍芽功能尚未開啟,請記得在此一併開啟。
- Raspberry Pi 端: 藍芽 -> “Add Device” (新增設備)。
Raspberry Pi 新增藍芽配對
- Raspberry Pi 端: 畫面上會出現手機的名稱,選取後按下 “Pair” (配對)。
Raspberry Pi 藍芽設備清單
- Raspberry Pi 端: 畫面上出現需至手機進行確認的提示訊息。
Raspberry Pi 藍芽設備配對提醒訊息
- 手機端: 確認 Raspberry Pi 的藍芽配對請求。
- Raspberry Pi 端: 畫面上出現配對成功的提示訊息,按下 OK 關閉訊息。
Raspberry Pi 藍芽設備配對完成訊息
- Raspberry Pi 端: 確認 Raspberry Pi 已經完成與手機的配對。
Raspberry Pi 已配對藍芽設備
Python 虛擬環境設定
如果你尚未依照在 Raspberry Pi 3 Model B 建立 Python 3.6 環境的說明安裝 Python 3.6 的虛擬環境,請務必先予以正確設定再進行下列步驟。
- 選取 Terminal 應用程式
- 初始虛擬環境相關設定1source /opt/miniconda3/bin/virtualenvwrapper.sh
- 回到之前建立的虛擬環境1workon rpi
- 安裝藍芽功能函式庫1sudo apt-get install libbluetooth-dev
- 修改 bluetooth 服務的啟動指令,加上相容模式的參數1sudo nano /etc/systemd/system/dbus-org.bluez.service
將123...ExecStart=/usr/lib/bluetooth/bluetoothd...
改成123...ExecStart=/usr/lib/bluetooth/bluetoothd -C... - 重新載入服務的設定檔1sudo systemctl daemon-reload
- 重新啟動 bluetooth 服務1sudo systemctl restart bluetooth
- 在虛擬環境中安裝 pybluez 套件1pip install pybluez
- 確認 pybluez 套件已正確安裝1pip list
應可看到類似下列訊息123...PyBluez (0.22)...
主從式 (C/S) 架構
雖然 Raspberry Pi 已經與手機完成配對,但是兩者之間還沒有辦法開始進行溝通,而必須利用特定的一組程式開啟溝通的管道。這兩個分別執行在 Raspberry Pi 與手機上的程式,必須透過一定的機制才能夠順利溝通。一種很常見、也是比較單純的機制就是所謂的主從式架構。在解釋主從式架構之前,必須先釐清有兩種完全不一樣的架構-Client/Server 和 Master/Slave 架構-中文都叫作主從式架構,而我們在這裡講的是前者。Client/Server (簡寫 C/S) 架構中文翻成主從式架構其實並不是很適當,因為溝通的雙方並沒有主、從這樣的階級關係,而是主動 (Client) 、被動 (Server) 之分。
接下來我們用一張圖來看看何謂主從式 (C/S) 架構:
雖然主從式架構讓雙方 (如銀行服務員與顧客) 可以開始進行溝通,但是實際的溝通方式與內容可就不關主從式架構的事情了。在現實生活中,人與人之間的溝通沒有辦法標準化,不過對於電腦來說標準化卻是很重要的。所以手機上的程式如何讓 Python 程式了解開啟/關閉 LED 燈泡的要求,需要在開始寫程式之前先講清楚、說明白。在這裡我們採用最簡單的方式:直接說英文。當手機應用程式”說出” on 時,Python 程式就開啟 LED 燈泡。而當手機應用程式”說出” off 時,Python 程式就要關閉 LED 燈泡。如果手機應用程式說出其他英文字句、甚至是非英文時,Python 程式就當作對方胡言亂語而不加以理會。這裡所謂的”說出”,並不是指人類的說話方式,而是把文字訊息送往另外一方的程式,如下列圖示:
其實我們剛剛作的就是制定一個雙方用來溝通的協定,雖然很簡單,但是卻可以滿足我們目前的需要。協定沒有唯一的作法,例如我們可以用數字來取代兩個英文單字,說 “1” 表示開啟 LED 燈泡,而說 “0” 則表示關閉 LED 燈泡。事實上,對一個運作良好的系統來說,制定清楚而且有效率的協定是一件很重要的事情,甚至可以說是最重要的事情。
線路圖
說明如下:
- 此線路包含一個 LED 燈泡與 1K 歐姆的限流電阻。其中 LED 陽極 (長腳) 連結至 Raspberry PI 實體編號的腳位 (BCM 編號 18),而陰極 (短腳) 則經由限流電阻連直接至實體編號 6 之腳位 (接地)。
Raspberry Pi 端程式範例
利用文字編輯器 (如 nano) 新增 Python 程式 led_bt_server.py
1 | nano led_bt_server.py |
內容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import uuid from bluetooth import * import RPi.GPIO as GPIO LED_PIN = 18 GPIO.setmode(GPIO.BCM) GPIO.setup(LED_PIN, GPIO.OUT) server_socket=BluetoothSocket(RFCOMM) server_socket.bind(("", PORT_ANY)) server_socket.listen(1) port = server_socket.getsockname()[1] service_id = str(uuid.uuid4()) advertise_service(server_socket, "LEDServer", service_id = service_id, service_classes = [service_id, SERIAL_PORT_CLASS], profiles = [SERIAL_PORT_PROFILE]) try: print('按下 Ctrl-C 可停止程式') while True: print('等待 RFCOMM 頻道 {} 的連線'.format(port)) client_socket, client_info = server_socket.accept() print('接受來自 {} 的連線'.format(client_info)) try: while True: data = client_socket.recv(1024).decode().lower() if len(data) == 0: break if data == 'on': GPIO.output(LED_PIN, GPIO.HIGH) print('開燈') elif data == 'off': GPIO.output(LED_PIN, GPIO.LOW) print('關燈') else: print('未知的指令: {}'.format(data)) except IOError: pass client_socket.close() print('中斷連線') except KeyboardInterrupt: print('中斷程式') finally: if 'client_socket' in vars(): client_socket.close() server_socket.close() GPIO.cleanup() print('中斷連線') |
說明如下:
- 第 2 行指令引入剛剛安裝的 bluetooth 套件,程式將利用這個套件開發藍芽的相關功能。
- 第 3 行指令引入 RPi.GPIO 套件,程式將使用這個套件來控制 LED 燈泡。
- 第 5 行指令定義 LED 正極 (長腳) 所接腳位之 BCM 編號 18,其實體編號為 12。
- 第 6 行指令指定使用 BCM 編號方式。
- 第 7 行指令將 LED 燈泡控制腳位指定為輸出模式。
- 第 9-11 行指令定義藍芽功能所需的控制物件,之後完成綁定 (bind) 並開始接受其他程式的連線 (listen)。
- 第 12 行指令取得程式所綁定的埠號,後面會用到。
- 第 13 行指令產生一個唯一識別碼 UUID,後面會用到。
- 第 15 行指令宣告藍芽服務,此處會用到第 12、13 行指令所指定的變數內容。
- 第 22~42 行透過無限迴圈不斷地接受手機程式進行連線。僅管如此,一次只有一個手機程式可以連上,也就是同一時間只有一個手機可以控制 LED 燈泡。
- 第 24 行指令會暫停程式的運作,直到有手機程式進行連結。
- 第 27~38 行指令不斷地接受來自手機應用程式的指令,並根據指令作出反應。這個程式僅支援兩個指令,on 表示開啟 LED 燈泡,而 off 則表示關閉 LED 燈泡,也就是分別將 LED 控制腳位設定為高電位或低電位。
- 第 46~49 行指令確保當我們使用 Ctrl-C 中斷程式後,藍芽連線可以正常中斷,而且程式所使用的 GPIO 腳位會回復到預設狀態。
此程式需要管理者的權限才能正常執行,程式完成後,需要輸入下列指令執行程式
1 2 3 4 5 | sudo su source /opt/miniconda3/bin/virtualenvwrapper.sh export WORKON_HOME=/home/pi/.virtualenvs workon rpi python led_bt_server.py |
手機端程式範例
在手機端程式部分,我採用 App Inventor 2 進行開發。
App Inventor 2 為圖形化的開發環境,以下分別是組件設計與邏輯設計的畫面:
你也可選擇下載 App Inventor 2 的專案檔,甚至是直接下載安裝檔。
執行手機端應用程式後,可以看到下列的畫面:
按下 “建立連線”後 ,會出現已經配對過的藍芽設備清單,選取 Raspberry Pi 後應該就可以順利連上我們之前所執行的 Python 程式了:
連上後我們就可以利用”開啟 LED” 跟”關閉 LED” 兩個按鈕來控制 LED 燈泡的發光與熄滅了。
透過藍芽機制,我們可以使用手機上的應用程式遠端控制 Raspberry Pi 的 LED 燈泡。雖然本次範例是以 Android 手機為主,不過只要是支援藍芽的手機、甚至是其他設備,其實也都可以達成相同的應用。在這個範例中,僅使用畫面上的按鈕來控制 LED 燈泡的開啟與關閉,但是如果加上語音辨識的功能,可就是一個酷上加酷的語音控制 LED 燈泡了。
如果對文章的內容有任何疑問或建議,歡迎與我聯絡。


3 comments
您好,我在執行上面的程式時,出現了以下的錯誤訊息:
$ python3 led_bt_server.py
led_bt_server.py:7: RuntimeWarning: This channel is already in use, continuing
anyway. Use GPIO.setwarnings(False) to disable warnings.
GPIO.setup(LED_PIN, GPIO.OUT)
Traceback (most recent call last):
File “/home/pi/.local/lib/python3.5/site-packages/bluetooth/bluez.py”, line 240, in
advertise_service
protocols)
_bluetooth.error: (2, ‘No such file or directory’)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File “led_bt_server.py”, line 18, in
profiles = [SERIAL_PORT_PROFILE])
File “/home/pi/.local/lib/python3.5/site-packages/bluetooth/bluez.py”, line 242, in
advertise_service
raise BluetoothError (str (e))
bluetooth.btcommon.BluetoothError: (2, ‘No such file or directory’)
不知道問題出在哪,能否提示一下問題點,感恩
你是使用 Python 3.6 還是 Python3.5 的虛擬環境呢?
如果依照文章執行,應該是會進入 Python 3.6 的虛擬環境,但是我看你的錯誤訊息好像是 Python 3.5 的環境。
您好,我想請教一個跟文章比較不怎麼相關的問題。
如果我想要反過來將樹莓派上的某個值透過藍芽傳回手機螢幕上,有沒有類似此文章的做法呢。
因為我在網路上都找不到相關的資料,才來此詢問的,感恩