close

撰寫「具備用戶介面(User Interface)」的應用程式,對於我這種依賴 IDE 開發工具(Integrated Development Environment,整合開發環境)的碼農來說,就像吃飯喝水那樣的直覺,甚至於忘了第一支 Hello world 的用戶介面是怎麼做出來的。這些年來寫過 Windows、macOS、iOS(iPhone/iPad/Apple Watch/Apple TV)、Android 等平台的應用程式,早期的 Windows CE(PDA 裝置)程式開發也接觸過一段時間。然而,有個地方一直都沒碰觸,對我而言也是未知的領域,那就是 Linux 平台的用戶介面應用程式。

大致說來,每個平台都有一套最知名、最強大、最多人使用、教學資源最豐富的 IDE 工具,例如 Windows 平台有微軟官方的 Visual Studio,macOS 有蘋果官方的 Xcode,Android 平台有 Google 官方的 Android Studio。然而在 Linux 上,似乎就少了一套強而有力、專門針對 Linux 視窗環境的 IDE 工具,所以就一直不清楚,如何在 Linux 上寫一支具有用戶介面的應用程式(這裏不談 Linux 系統的瀏覽器運作 HTML5 網頁)。

前陣子工作上接手一份來自古老時代的程式碼,是用 PyQt4 寫的,功能是提供一個使用者操作介面,讓樹莓派主機透過主板 GPIO 介面去控制幾個周邊設備。原作者早已失聯,不過有留著當時的開發筆記重點。順著考古的遺跡,意外讓我補足這一塊技能,原來透過 Python + Qt,也能輕鬆寫出對應 Linux 桌面版的用戶介面應用程式。

然而,使用 PyQt 開發 Linux 版的用戶介面,依然無法像 Visual Studio、Xcode 那麼方便,中間的過程還是有些需要手動生成操作的步驟,決定開一篇文章來記錄。所以了,若是您對「開發 Linux 環境的 UI 應用」感興趣,或是想知道如何寫出「UI 介面控制樹莓派 GPIO 周邊的應用程式」,可以參考這篇文章的內容。

目標:使用 PyQt4 撰寫一支在樹莓派官方作業系統底下運作,使用操作介面控制樹莓派 GPIO 埠上的 LED 二極體燈光強弱。

基本需具備技能:

  • Linux 基本操作:由於以樹莓派的 UI 應用程式為例,所以需具備基本的指令操作基礎。
  • Python 語言基本技能:必備條件,否則以下的內容會跟天書一樣難懂。

準備工作:

  • 一台作為程式開發的電腦:可以是樹莓派(建議 3B / 3B+ / 4B 以上 ),或是安裝了 Python 的個人電腦。本篇文章主要以 Ubuntu 18.04 的個人電腦為例。
  • 一台運作 Raspbian 作業系統(下載)的樹莓派主機:用來測試撰寫的程式
  • 電子零件若干:一顆 LED 發光二極體、一顆 1K 電阻、一張電路麵包板、杜邦線若干:與樹莓派對接,照下圖的接法

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

提到視窗介面,構思的兩個介面與內容:

  1. 一個主視窗,其中有一條可滑動的拖曳棒,控制 LED 燈光的強弱。
  2. 一個對話窗,提示用戶輸入內容。輸入完成,將輸入內容顯示上主視窗。

其實第 2. 項功能與第 1. 項無關,只是因為 UI 程式常有開窗關窗事件,事件也有特定的寫法,所以把這項功能加入。

樹莓派準備好了,GPIO 周邊的電路也接好了,介面的構想也有了,接著就是介面與功能的實作。

介面設計

在 Linux 裝置上(Ubuntu 電腦,或樹莓派主機。以下以 Ubuntu 18.04 為例)安裝以下套件:

$ sudo apt install python-pip
$ sudo apt install python-dev
$ sudo apt install qt4-dev-tools
$ sudo apt install python-qt4
$ sudo apt install pyqt4-dev-tools
$ sudo apt install libcanberra-gtk-module libcanberra-gtk3-module

安裝完畢之後,系統內就會多了「Qt4助理」、「Qt4 設計師」、「Qt4 語言專家」

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

接著執行「Qt4 設計師」,分別將上面提到的主視窗(Main Window)與對話視窗(Dialog)設計出來。這部分很簡單,細節就不提了。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

Qt4 設計師有超多的抹抹角角,例如主視窗在拉動滑棒時,右邊的數字會更著變動 0~100,在這裡只需要使用「編輯信號與信號槽」將兩者的動作連接在一起,不需撰寫程式碼。其他的問題,自行操作嘗試一下,或上 Google 查詢,大多能查得到答案,所以介面設計的細節在此略過不提。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

透過 Qt 設計師,我們得到以下 3 個檔案:主視窗設計檔 mainUI.ui,對話框設計檔 dialogUI.ui 。另外,主視窗的按鈕加入圖示,這部分另外以自建的資源檔 ui.qrc 進行管理。把這些檔案全放在相同的目錄下。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

接著,把這三個檔案透過指令轉換成 .py 檔:

$ pyrcc4 -py3 ui.qrc -o ui_rc.py
$ pyuic4 mainUI.ui -o mainUI.py
$ pyuic4 dialogUI.ui -o dialogUI.py

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

主程式撰寫

使用 Microsoft Visual Studio Code 

接著開始撰寫主程式了,目的是把上面提到 UI 介面以及操作功能(包含調整 LED 的亮度),全都串接起來。使用前請務必注意:專案絕對路徑中不要有中文(例如:文件),否則某些 Visual Studio Code 的插件不起作用。

首先是處理 UI 操作的動作回應,這部分的程式碼不一定得在樹莓派上完成,建議還是在個人電腦上,搭配微軟的 Visual Studio Code 撰寫 Python 會省事很多。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

Visual Studio Code 撰寫 Python 的設定方法,簡單提一下:

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

(1) 開發插件:安裝兩個套件:Python,Qt for Python。

(2) Qt for Python 的設定:安裝完上述兩個插件之後,於設定搜尋「python」,點擊「Qt For Python」,在對應的設定依序填入以下的設定值:

Qt For Python > Path: Designer
/usr/lib/x86_64-linux-gnu/qt4/bin/designer

Qt For Python > Path: Pyrcc
pyrcc4 -o ./"${fileBasenameNoExtension}_rc.py"

Qt For Python > Path: Pyuic
pyuic4 -o ./"${fileBasenameNoExtension}.py"

註:新版的 Qt for Python 已經拆分為路徑(Path)、參數(Args)、副檔名(Execution)三個設定。
以 Pyuic 為例,路徑填入 pyuic4,參數填入 -o ./"${fileBasenameNoExtension}.py" ,副檔名 Auto 打勾即可。

開始撰寫

建立 UI 主程式碼(main.py),內容如下。需注意以下程式碼中 UI 元件的名稱不能照抄,必須與使用 Qt4 設計師設計畫面時,給定的元件名稱相符才行。

# -*- coding: UTF-8 -*-
from PyQt4 import QtCore, QtGui #PyQt4 的部分
import mainUI, dialogUI #介面

class mainWin(QtGui.QMainWindow,mainUI.Ui_MainWindow):
    def __init__(self,parent=None):
        super(mainWin,self).__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.openDialog) #開啟對話框
        self.horizontalSlider.sliderMoved.connect(self.changeLEDPower) #滑動棒移動時的事件
        
    def openDialog(self):
        dlg = dialog(self,self.getName) #parent 為主畫面,getName 為 callback 方法
        dlg.text_name.setText(self.label_name.text()) #主畫面的名字值
        dlg.exec_()
        dlg.destroy()
    def getName(self,name):           
        self.label_name.setText(name) #將對話框內的名字內容填入

    def changeLEDPower(self,pwval):
        pass

    def closeEvent(self, evnt):
        pass
      
class dialog(QtGui.QDialog,dialogUI.Ui_Dialog):
    def __init__(self,parent=None,func=None):
        super(dialog,self).__init__(parent)
        self.setupUi(self)
        self.buttonBox.accepted.connect(self.accepted) #按下確定時
        self.func = func
    def accepted(self):
        self.func(self.text_name.toPlainText()) #執行從主視窗傳來的 callback 方法
        self.close()

    def closeEvent(self, evnt):
        if(str(self.text_name.toPlainText()).strip() == ''): #沒有填入值時,強制取消關閉視窗
            evnt.ignore()
            return None

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    mainWin = mainWin()
    mainWin.show()
    sys.exit(app.exec_())

 

簡單說明一下上面的 UI 程式碼:在 .ui->.py 之後,在自行撰寫的主程式中,建立這些 UI 的子類,子類中再來撰寫用戶操作的動作與邏輯,例如「closeEvent」裡面,要求使用者在對話框內的欄位必須填值,否則按「確定」時不允許關閉對話框。一路做這裡之後,介面(mainUI.py,dialogUI.py) + 資源檔(ui_rc.py)+主程式檔案(main.py),全部放在同一目錄下,整個差不多就是下圖的樣子。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

基本上這個 UI 程式已經可以在開發環境中運作,所以上面才說到,可以將 UI 的部分先在個人電腦的開發環境中,透過強大的微軟 Visual Studio Code 來進行調試與除錯。

在開發環境的 Ubuntu 電腦上測試若沒有問題時,把整個目錄移到樹莓派上,把通過 GPIO 控制 LED 二極體的程式碼,在適當的位置補上就行了。

在樹莓派上補完與執行

樹莓派上除了安裝 pyqt4 之外,也要安裝 GPIO 的 python 模組

$ sudo apt install python-dev
$ sudo apt install python-rpi.gpio
$ sudo apt install python-qt4

補上 GPIO 的部分之後,程式碼的部分如下:

# -*- coding: UTF-8 -*-
from PyQt4 import QtCore, QtGui #PyQt4 的部分
import mainUI, dialogUI #介面

import RPi.GPIO as GPIO #補上 GPIO
GPIO.setmode(GPIO.BCM) #補上 GPIO

LED_PIN = 18 #補上 GPIO
LED_FREQ = 1000 #補上 GPIO

class mainWin(QtGui.QMainWindow,mainUI.Ui_MainWindow):
    def __init__(self,parent=None):
        global PWN_LED #補上 GPIO
        super(mainWin,self).__init__()
        self.setupUi(self)
        self.pushButton.clicked.connect(self.openDialog) #開啟對話框
        self.horizontalSlider.sliderMoved.connect(self.changeLEDPower) #滑動棒移動時的事件
        
        GPIO.setup(LED_PIN, GPIO.OUT)  #補上 GPIO
        self.LED = GPIO.PWM(LED_PIN, LED_FREQ) #補上 GPIO
        self.LED.start(0) #補上 GPIO
    def openDialog(self):
        dlg = dialog(self,self.getName) #parent 為主畫面,getName 為 callback 方法
        dlg.text_name.setText(self.label_name.text()) #主畫面的名字值
        dlg.exec_()
        dlg.destroy()
    def getName(self,name):           
        self.label_name.setText(name) #將對話框內的名字內容填入

    def changeLEDPower(self,pwval):
        self.LED.ChangeDutyCycle(pwval) #補上 GPIO

    def closeEvent(self, evnt):
        GPIO.cleanup()     #補上 GPIO

class dialog(QtGui.QDialog,dialogUI.Ui_Dialog):
    def __init__(self,parent=None,func=None):
        super(dialog,self).__init__(parent)
        self.setupUi(self)
        self.buttonBox.accepted.connect(self.accepted) #按下確定時
        self.func = func
    def accepted(self):
        self.func(self.text_name.toPlainText()) #執行從主視窗傳來的 callback 方法
        self.close()

    def closeEvent(self, evnt):
        if(str(self.text_name.toPlainText()).strip() == ''): #沒有填入值時,強制取消關閉視窗
            evnt.ignore()
            return

if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    mainWin = mainWin()
    mainWin.show()
    sys.exit(app.exec_())

程式碼補完之後,執行

$ python main.py

運作情形如下:

補充:

1. 從 PyQt4 轉換到 PyQt5

PyQt4 已不再獲得 Qt 官方技術支援,所以若想著手 PyQt 的開發,最好是直接從 QyQt5 下手。必須注意的是,PyQt5 也不再支援 python2 以前的版本。以本文章的範例來說,轉換到 PyQt5 很容易。

開發 Qt 介面的 Python3 程式需要以下項目:
 - Python3 核心
 - (X) Qt5 核心
 - python3-pyqt5:Qt5 for Python3 核心(PyQt5)
 - designer : 介面設計工具,包含在 qttools5-dev-tools 內
 - pyuic5 / pyrcc5:將介面轉為 Python3 語法的工具,包含在 pyqt5-dev-tools 內

而 qtcreator 雖然包含 qt5-default 與 qttools5-dev-tools,但也包含太多其他東西而肥大,所以不採用。 pyqt5 包含 python3-pyqt5 + pyqt5-dev-tools,但 pyqt5 需要用 pip3 安裝,適合非 Linux 環境。

所以 Linux 的 Qt5 for Python3 開發環境最小安裝如下

$ sudo apt install python3-dev python3-pyqt5 qttools5-dev-tools pyqt5-dev-tools #qt5-default 

下載需 67MB ,安裝需 314MB 空間

樹莓派執行端:
可安裝與開發端相同的套件,或是以下三者即可(無 Designer )

$ sudo apt install python3-dev python3-pyqt5 pyqt5-dev-tools

.ui 與 .qrc 轉換:

$ pyrcc5 ui.qrc -o ui_rc.py
$ pyuic5 mainUI.ui -o mainUI.py
$ pyuic5 dialogUI.ui -o dialogUI.py

程式碼轉換:EX 主程式 main.py:

Python2->Python3: 語法與函式庫的改變,如
  thread 改 _thread
Qt4->Qt5:
  所有的 QtGui 改為 QtWidgets

執行:

python3 main.py

2. 產生獨立執行檔

由於 python 以 runtime 的方式運作,程式碼全寫在 .py 檔,用戶端得安裝 python 與對應的模組之外,還有程式碼被看光光的隱憂。可以用 pyinstaller 將 .py 打包成獨立執行檔,讓程式可以隱藏原始碼,更有利於發行。

先安裝 pyinstaller:

適用於 python2
$ sudo pip2 install pyinstaller

適用於 python3
$ sudo pip3 install pyinstaller

pyinstaller 基本指令

範例:pyinstaller --add-data="xxxxxxx.xxx:." -F --clean xxxx.py

參數說明:

  • -add-data : 加入程式運作時會使用的檔案(例如 SQLite 檔、文字檔等等,也可以是目錄名稱),格式是 "[來源][分隔號][目的]",目的用 " . " 表示該檔案在封裝時不改名稱。要注意的是分隔號有平台之分,macOS/Linux 平台分隔號是「 : 」,Windows 平台分隔號是「;」。若有多個檔案或目錄,可以多加幾次,例如 --add-data="a.txt:." --add-data="dirB:dirBB" 表示封裝時加入來源 a.txt 檔案與 dirB/*.* 並且 dirB 在目的端改為 dirBB/*.*。 
  • -F:合併為單一 buldle 執行檔。其實也是一個壓縮檔,運作時會自行解開。
  • --clean:清除暫存資料
  • --windowed(或 -w):產生符合視窗環境的獨立執行檔,僅適用於 macOS / Windows 平台。

補充:打包出來的獨立執行檔,運作時有可能會發生找不到模組 xxxx 的問題。經測試後發現,樹莓派的 gpiozero 模組很難被包進執行檔,我個人還找不到解法,所以能不用 gpiozero 就儘量不要用。以及 pandas 套件被封裝後有極高機率造成打包後的程式執行閃退。

pyinstaller 的參考資料:
主頁說明文檔有關 macOS 環境打包成執行檔後,運作發生 codesign 的問題的解法、macOS 平台可參考這個網址


macOS 開發 PyQt5

若不想生一個 Linux 來做 PyQt5 開發,想使用現成的 macOS + VSCode 進行開發,以下是簡單的做法。

(一)安裝 Qt Design

下載位址:https://build-system.fman.io/qt-designer-download

在 macOS 10.15 第一次啟動時,會出現「無法打開「Qt Designer」,因為無法確認開發者的識別身分。」錯誤。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

解決方式:按住 Ctrl 按鈕,再執行 Qt Design。出現警告之後,點擊「打開」即可。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

以後再次執行的時候,就不會出現警告了。

(二)安裝 Python 3

macOS 雖然內建 Python ,但只適用於 macOS 系統本身自用,所以要自行另外安裝。先前在「教你的 iPhone 認識 Gogoro 換電站(Part 2)」文章中已經提過官網下載安裝的方式,這邊再簡單提一次。

1. 官網下載位址:https://www.python.org/downloads/ ,依照指示選擇 Python 3.x.x 的 macOS pkg 下載。下載後安裝即可。

2. 安裝完之後,應用程式底下會出現 Python 3.x.x 目錄夾,記得執行底下兩個 command (滑鼠直接點擊即可)。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

3. 編輯 ~/.bash_profile 檔案,確認自行安裝的 Python 的 PATH 有效。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

到這裡 Python 3 已經安裝完成。

4. 驗證:開啟一終端機,輸入 which python3。若出現 「/Library/Frameworks/Python.framework/Versions/3.7/bin/python3 」表示安裝完成。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

(三)安裝 PyQt5

很簡單,開啟一終端機視窗,輸入 pip3 install pyqt5 即可。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

驗證:在終端機視窗中輸入「which pyrcc5」和「which pyuic5」,看看回應的路徑是否有值

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

到這邊,已經完成 macOS 環境安裝 Python3 與 PyQt5 的全部工作。 最後一步是安裝與設定 Microsoft Visual Studio Code 的 Python + PyQt5 的開發環境設定。

(四)Microsoft Visual Studio Code for macOS 安裝與 Python3 + PyQt5 設定

和上面 Linux 的做法幾乎相同,不過以下介紹 PyQt 的另一套插件。

1. 安裝 Microsoft Visual Studio Code

下載位置:https://code.visualstudio.com/download 下載 Mac 的版本。

2. 以下看圖說故事,安裝 Python / PYQT Integration 插件,以及設定。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

做到這裡,全部的開發設定工作就完成了。

3. 驗證

用 Microsoft Visual Studio Code 開啟 PyQt 專案目錄夾,點擊 .ui 檔按右鍵

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

  • 選擇「PYQT: Edit in Designer」則會呼叫 Qt Designer。選擇「PYQT: Compile」則會自動運行 pyuic5 編譯 .ui 檔並產生 .py 檔。
  • 點擊 .qrc 檔按右鍵,出現「PYQT: Compile Resource」,選擇後會自動運行 pyrcc5 編譯 .qrc 檔並產生 *_rc.py 檔案。

2021.07.18 :以上的內容實在又臭又長。直接寫重點如下:

一、安裝 Qt5 for python3 開發環境(最少安裝):

Ubuntu / Debian / Raspbian

$ sudo apt install python3-dev python3-pyqt5 pyqt5-dev-tools qttools5-dev-tools

(2023.11.15) 或是

$ sudo apt install python3-pip qttools5-dev-tools
$ sudo pip3 install pyqt5

樹莓派不建置開發環境,僅運行環境如下(最少安裝,意即少了 qttools5-dev-tools ,也就是少安裝 Qt5 Design ):

$ sudo apt install python3-dev python3-pyqt5 pyqt5-dev-tools

Windows / macOS :

官網安裝 python3 + pip 之後,使用 pip3 install pyqt5,再到 https://build-system.fman.io/qt-designer-download 下載安裝 designer 。

二、Ubuntu 環境下,OpenCV 在 Visual Studio Code 無法 Intellisense (Autocomplete) 問題的處理方式:

在Ubuntu 環境下,即使安裝 OpenCV for Python3 ,但是在 Visual Studio Code 開發環境中,import cv2 之後,cv2 的指令依舊無法支援自動完成(Intellisense / Autocomplete) ,於是每次要撰寫 cv2 指令時還要上網找資料。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

問題的根本原因,是因為即便安裝了 OpenCV for Python3,系統內仍然少了 cv2.pyi 檔案,導致 Visual Studio Code 找不到 Intellisense / Autocomplete 的對應資料。

解決方式:以 pip 的方式安裝 OpenCV for Python3,並生成 cv2.pyi。操作步驟如下:

1. 安裝套件
- pip3:
$ sudo apt install python3-pip

- pylint
$ sudo pip3 install pylint

- OpenCV
$ sudo pip3 install opencv-python

裝完以後,會發現 cv2 的套件路徑會指向

/usr/local/lib/python3.8/dist-packages/cv2

但因為少了 cv2.pyi 定義檔,自動完成(Intellisense / Autocomplete)的功能還是不夠完整,需生成 cv2.pyi 來解決。

2. 生成 cv2.pyi
- 安裝 mypy
$ sudo pip3 install mypy

- 生成 cv2.pyi
$ sudo stubgen -m cv2 -o /usr/local/lib/python3.8/dist-packages/cv2/

3. 將 cv2 加入 VSCode 的 settings.json
VSCode 左下角齒輪 - 設定[Ctrl+,]

貼上的影像_2021_7_18_上午2_44.png

輸入「extraPaths」,出現
Python > Auto CompleteL Extra Paths,點擊「在Settings.json」內編輯,

貼上的影像_2021_7_18_上午2_47.png
在 python.autoComplete.extraPaths 加入「/usr/local/lib/python3.8/dist-packages」,如下:

    "python.autoComplete.extraPaths": [
    "/usr/local/lib/python3.8/dist-packages"
    ]

貼上的影像_2021_7_18_上午2_50.png

做到這裡,問題應該就解決了。

【碼農】使用 PyQt 開發樹莓派的 UI 介面程式

附帶提一下上圖 opencv 的臉部偵測範例原始碼

import cv2
# 讀取圖片
img = cv2.imread('FaceDetector.jpg')
# 取得圖片資訊
height, width, channels = img.shape
# 轉灰階
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 宣告臉部偵測物件
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_default.xml')
# 偵測圖片中的人臉
faces = face_cascade.detectMultiScale(gray,
                                    scaleFactor=1.1,
                                    minNeighbors=16,
                                    minSize=(30,30),
                                    maxSize=(max(width,height) ,max(width,height)))

# 綠線標示人臉位置(座標x,座標y,寬w,高h),cv2.rectangle 後面兩個參數是 RGB 顏色與繪圖線寬
for (x,y,w,h) in faces:
    img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,200,0),2)
# 顯示圖片
cv2.imshow('FaceDector',img)
# 程式暫停,等待用戶按下任何一鍵
cv2.waitKey(0)
# 離開
cv2.destroyAllWindows()

執行結果

貼上的影像_2021_7_18_上午11_45.png

arrow
arrow

    benjenq 發表在 痞客邦 留言(0) 人氣()