【Arduino實驗室】中關於智能硬件的實驗在網上絕對是沒有的(有也是我發的),都由做者單獨設計。敬請期待後期的【鴻蒙實驗室】系列文章和視頻課程。python
這個案例是將Python、PyQT6與Arduino結合。經過Arduino開發板控制3個LED(分別爲紅黃綠3個顏色)來模擬交通訊號燈。能夠經過單擊PC端的三個燈控制Arduino開發板和3個LED。也能夠點擊「自動」按鈕讓信號燈自動變換。本系統徹底模擬真實的信號燈的自動切換過程。一開始紅燈亮6秒(爲了減小一個完整變化週期的時間,並無讓信號燈亮過長時間),而後馬上切換到綠燈,繼續亮6秒。接下來綠燈閃爍6次(每1秒閃爍一次,一亮一滅),而後切換到黃燈,亮3秒,最後到紅燈(亮6秒),完成一個信號週期。若是點擊「中止」按鈕,信號燈會中止自動切換,但會在完成一個信號週期後中止。視頻演示見附件。git
1. 須要準備哪些實驗設備和器材服務器
本實驗須要準備的設備以下:
(1)PC一臺,系統能夠是Windows、macOS或Linux,須要安裝Python環境和PyQt六、以及Arduino IDE;
(2)Arduino開發板一塊,推薦使用UNO;
(3)3個LED,建議紅、黃、綠各一個;
(4)ESP8266 Wi-Fi模塊一個,用於聯網;
(5)10K電阻一個;
(6)麪包板一個,主要用於解決Arduino開發板接口不足的狀況;
(7)杜邦線若干,能夠多準備一些(最好有多種顏色),反正很便宜。須要兩類杜邦線:公對公、公對母;微信
2. ESP8266 Wi-Fi模塊與Arduino開發板鏈接app
玩物聯網,其實涉及到硬件和軟件兩部分。硬件主要涉及到選擇和鏈接,通常並不涉及到硬件的設計和製做。本實驗的核心模塊是ESP8266,這是一個Wi-Fi模塊,價格很是便宜,國內價格在15到20元之間,某寶就有賣。socket
要作的第一步就是將ESP8266與Arduino開發板用杜邦線鏈接,程序是上傳到Arduino開發板上的,而後經過AT命令與ESP8266模塊交互。ide
ESP8266的樣子如圖1所示。這個模塊相對其餘大多數模塊(如超聲波模塊、LED、按鈕等)要複雜一些,一共有8個管腳,也就是8個針。函數
ESP8288通常與Arduino開發板直接相連。Arduino開發板的樣子如圖2所示。管腳都是眼,因此須要若干公對母的杜邦線。oop
如今回到圖1的8個管腳,其實在開發階段,只須要鏈接其中的5個便可,在刷固件時,通常須要鏈接6個管腳(關於刷ESP8266固件的問題,我後面會寫文章介紹)。須要鏈接的5個管腳以下:post
(1)3.3v:接到Arduino開發板的3.3v插孔上,記住,必定是3.3v,不要接在5v上,不然你還須要再買一塊ESP8266,切記、切記、切記;
(2)EN:一樣須要接到3.3v上,但Arduino開發板只有一個3.3v插孔,因此須要藉助麪包板;
(3)GND:接在Arduino開發板的GND插孔(通常有3個,插如任意一個GND插孔便可);
(4)TX:一般接在一個軟串口,本例接到8上;
(5)RX:一般接在一個軟串口,本例接到9上;
3. 模擬鏈接ESP8266與Arduino開發板
在正式鏈接以前,最好先用Fritzing模擬鏈接下,至關於畫個草圖,以避免接錯。鏈接完成的效果如圖3所示。這裏要說明的是,EN與3.3v都須要接到Arduino開發板的3.3v管腳上,但Arduino開發板只有一個3.3v管腳,因此首先將Arduino開發板的3.3v管腳經過杜邦線(圖3中紅色的線)鏈接到麪包板,而後EN與3.3v再經過杜邦線插入麪包板中(兩根橙色的線),要記住,其中一根橙色的線要與紅色線在同一列,另一根橙色的線在另外一列,並讓這兩列用給一個10K的電阻相連,爲了避免讓ESP8266的EN和3.3v兩個管腳因爲直接連通而短路。注意,必定要用電阻相連,推薦是10K歐的電阻。
4. 鏈接3個LED
LED的鏈接就簡單的多,LED如圖4所示。
短一些(左側)的管腳接地,長一些(右側)的管腳接某個數字管腳,本例綠、黃、紅分別接在了七、六、5管腳。因爲Arduino開發板只有3個GND管腳,而ESP8266已經佔用了一個,因此仍然須要藉助麪包板擴展GND管腳。基本原理是將GND經過杜邦線與麪包板鏈接(本例中的黑線),而後將3個LED的GND端都經過杜邦線插入與黑色杜邦線在麪包板位置的同一列的其餘插孔。最終的效果如圖5所示。
5. 在Arduino開發板上建立TCP服務器
ESP8266與Arduino交互一般有以下2種方式:
(1)ESP8266做爲服務器;
(2)ESP8266做爲客戶端;
本實驗採用了第1種方式,第2種方式後面我會寫文章介紹。
無論採用哪種方式,首先要讓ESP8266上網。一般是鏈接家中的無線路由器,或用手機作的熱點。設置ESP8266須要經過AT命令,其實就是一組解釋執行的命令,與DOS命令相似。
ESP8266在出廠時的波特率是115200,因此執行AT命令,必須在這個波特率下。在setup函數中使用下面的代碼設置波特率,以及執行AT命令鏈接路由器。
#include <SoftwareSerial.h> SoftwareSerial wifi(WIFI_RX, WIFI_TX); //RX, TX void setup() { Serial.begin(9600); wifi.begin(115200); Serial.println("system is ready!"); wifi.println("AT+CWMODE=3\r\n"); // 設置ESP8266的模式,3表示既能夠做爲路由器模式(AP),爲其餘設備提供用於上網的Wi-Fi,也能夠做爲普通的設備創建TCP鏈接 delay(500); wifi.println("AT+CIPMUX=1\r\n"); delay(500); wifi.println("AT+CWJAP=\"路由器名\",\"路由器密碼\"\r\n"); //鏈接路由器,請更換本身的路由器明和路由器密碼 delay(500); wifi.println("AT+CIPSERVER=1,5000\r\n"); // 啓動TCP服務,端口號是5000 delay(500); }
這裏的wifi負責與軟串口通訊(一般硬串口主要用於刷固件),wifi.println函數用於執行AT命令。要注意,每執行一條AT命令,要等待必定的時間,這裏是500毫秒。
當第一遍執行完,除非Arduino開發板重啓或從新上傳程序,不然setup函數只會執行一次。之後能夠將鏈接路由器的代碼去掉了,由於ESP8266是有記憶功能的,這種配置性質的AT命令,執行完,會將執行結果記錄在案。因此下一次重啓ESP8266模塊時,無論執行不執行這條AT命令,ESP8266都會自動鏈接路由器。
不過其餘代碼應該保留,由於這些代碼的執行結果是不會記錄在案的,當重啓ESP8266模塊時,須要從新執行這些AT命令來創建TCP服務。
通過測試,ESP8266在115200波特率的狀況下,經過Wi-Fi傳輸數據容易出現亂碼,因此須要使用下面的代碼將ESP8266模塊的波特率強行改爲9600,這樣數據傳輸很是穩定。
wifi.println("AT+CIOBAUD=9600\r\n");
在Arduino開發板上創建TCP服務器的完整代碼以下:
#include <SoftwareSerial.h> #define WIFI_TX 9 #define WIFI_RX 8 #define LED_RED 7 #define LED_YELLOW 6 #define LED_GREEN 5 SoftwareSerial wifi(WIFI_RX, WIFI_TX); //RX, TX String command = ""; // 接收客戶端發過來的數據 void setup() { //五、六、7三個管腳設置爲輸出,以便輸出高電平來點亮LED pinMode(LED_RED, OUTPUT); pinMode(LED_YELLOW, OUTPUT); pinMode(LED_GREEN, OUTPUT); // 先將五、六、7三個管腳設置爲低電平,默認LED是滅的狀態 digitalWrite(LED_RED, LOW); digitalWrite(LED_YELLOW, LOW); digitalWrite(LED_GREEN, LOW); Serial.begin(9600); wifi.begin(9600); // 已經改爲9600了,因此這裏經過9600波特率與客戶端經過Wi-FI傳輸維護局 Serial.println("system is ready!"); wifi.println("AT+CWMODE=3\r\n"); delay(500); wifi.println("AT+CIPMUX=1\r\n"); delay(500); wifi.println("AT+CIPSERVER=1,4999\r\n"); delay(500); } // 該函數會不斷循環調用 void loop() { // 從客戶端(PC端)讀取發過來的數據 while (wifi.available() > 0) { command += char(wifi.read()); delay(4); } // 若是數據不爲空,繼續處理 if (command != "") { // 將接收到的命令輸出到串口監視器 Serial.println(command); // 命令會自動加上一個前綴+IPD,若是包含這個前綴,纔是傳過來的命令 if (command.indexOf("+IPD") > -1) { if (command.indexOf("close_red") > -1) { digitalWrite(LED_RED, LOW); //0 燈滅 Serial.println("close_red"); } else if (command.indexOf("close_yellow") > -1) { digitalWrite(LED_YELLOW, LOW); //0 燈滅 Serial.println("close_yellow"); } else if (command.indexOf("close_green") > -1) { digitalWrite(LED_GREEN, LOW); //0 燈滅 Serial.println("close_green"); } else if (command.indexOf("open_red") > -1) { digitalWrite(LED_RED, HIGH); //1 燈亮 Serial.println("open_red"); } else if (command.indexOf("open_yellow") > -1) { digitalWrite(LED_YELLOW, HIGH); //1 燈亮 Serial.println("open_yellow"); } else if (command.indexOf("open_green") > -1) { digitalWrite(LED_GREEN, HIGH); //1 燈亮 Serial.println("open"); } } command = ""; } }
閱讀這段代碼要注意,這裏提供了6個命令:close_red(紅色LED滅燈)、open_red(紅色LED亮燈)、close_yellow(黃色LED滅燈)、open_yellow(黃色LED亮燈)、close_green(綠色LED滅燈)、open_green(綠色LED亮燈)。只要command中包含着6個命令字符串中的一個,就肯定客戶端發出了該命令。
而後使用Arduino IDE上傳程序便可(別忘了選擇開發板和端口)
PS:若是要改變端口號,能夠直接修改5000,而後須要重啓Arduino開發板(固然,ESP8266也會重啓),這樣就會再次執行setup函數來從新啓動TCP服務。
6. 編寫Python程序
這裏編寫客戶端程序,使用PyQt6編寫UI、使用Python編寫所有業務邏輯,因爲代碼比較多,因此只給出了核心代碼,基本原理就是Python經過TCP Socket API鏈接ESP8255中的TCP Server,而後不斷髮送上一節給出的6個命令。
import TrafficLight1 import sys import socket from PyQt6.QtWidgets import QApplication,QMainWindow,QMessageBox from PyQt6 import QtGui from PyQt6.QtCore import QThread """ 講師:李寧(蒙娜麗寧) 微信:unitymarvel 微信公衆號:極客起源 B站:https://space.bilibili.com/477001733 """ # 線程類,用於自動切換信號燈 class WorkThread(QThread): def __init__(self, events): super(WorkThread, self).__init__() self.events = events self.running = False def run(self): # 先關閉全部的信號燈 self.events.close_all_light() # 開始自動切換信號燈 while self.running: # 紅色信號燈打開6秒 self.events.open_light("red") QThread.msleep(6000) self.events.close_light("red") QThread.msleep(200) # 綠色信號燈打開5秒 self.events.open_light("green") QThread.msleep(6000) # 綠色信號燈閃爍5次 count = 0 while count < 5: self.events.close_light("green") QThread.msleep(500) self.events.open_light("green") QThread.msleep(500) count += 1 self.events.close_light("green") QThread.msleep(200) # 黃色信號燈顯示2秒 self.events.open_light("yellow") QThread.msleep(3000) self.events.close_light("yellow") QThread.msleep(200) self.events.close_all_light() print("退出自動運行狀態") # 包含UI事件的代碼 class Events: def __init__(self, ui): self.ui = ui self.connected = False # 是否已經與Arduino創建了鏈接 self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 聲明協議類型,不寫類型使用默認的 self.workThread = WorkThread(self) def close_all_light(self): self.close_light("red") QThread.msleep(200) self.close_light("yellow") QThread.msleep(200) self.close_light("green") QThread.msleep(200) # 打開LED(color參數用於指定打開哪個顏色的信號燈) def open_light(self, color): if self.connected: self.client.sendall(("open_" + color).encode()) if color == 'red': self.ui.labelRedLight.setPixmap(QtGui.QPixmap("")) self.ui.labelRedLight.state = "open" elif color == 'yellow': self.ui.labelYellowLight.setPixmap(QtGui.QPixmap("")) self.ui.labelYellowLight.state = "open" elif color == 'green': self.ui.labelGreenLight.setPixmap(QtGui.QPixmap("")) self.ui.labelGreenLight.state = "open" return True else: QMessageBox.warning(self.ui.centralwidget, "警告", "請先鏈接Arduino,再打開燈") return False # 關閉LED(color參數用於指定關閉哪個顏色的信號燈) def close_light(self, color): if self.connected: self.client.sendall(("close_" + color).encode()) if color == 'red': self.ui.labelRedLight.setPixmap(QtGui.QPixmap("close_light.png")) self.ui.labelRedLight.state = "close" elif color == 'yellow': self.ui.labelYellowLight.setPixmap(QtGui.QPixmap("close_light.png")) self.ui.labelYellowLight.state = "close" elif color == 'green': self.ui.labelGreenLight.setPixmap(QtGui.QPixmap("close_light.png")) self.ui.labelGreenLight.state = "close" return True else: QMessageBox.warning(self.ui.centralwidget, "警告", "請先鏈接Arduino,再關閉燈") return False # 點擊紅燈時觸發 def red_light_mouse_press_event(self, event): if ui.labelRedLight.state == "open": self.close_light("red") elif ui.labelRedLight.state == "close": self.open_light("red") # 點擊黃燈時觸發 def yellow_light_mouse_press_event(self, event): if ui.labelYellowLight.state == "open": self.close_light("yellow") elif ui.labelYellowLight.state == "close": self.open_light("yellow") # 點擊綠燈時觸發 def green_light_mouse_press_event(self, event): if ui.labelGreenLight.state == "open": self.close_light("green") elif ui.labelGreenLight.state == "close": self.open_light("green") # 點擊「鏈接」按鈕時觸發,用於連接TCP Server def pushButton_connect_mouse_press_event(self, event): self.client.connect(('192.168.31.164', 4999)) self.connected = True self.ui.pushButtonConnect.setEnabled(False) QMessageBox.information(self.ui.centralwidget, "消息", "成功鏈接Arduino") # 點擊「自動」按鈕觸發,用於開啓信號燈自動切換模式 def pushButton_auto_mouse_press_event(self, event): self.workThread.running = True self.workThread.start() self.ui.pushButtonAuto.setEnabled(False) self.ui.pushButtonStop.setEnabled(True) # 點擊「中止」按鈕觸發,用於關閉信號燈自動切換模式 def pushButton_stop_mouse_press_event(self, event): self.workThread.running = False self.ui.pushButtonAuto.setEnabled(True) self.ui.pushButtonStop.setEnabled(False) # 用於初始化代碼 if __name__ == '__main__': app = QApplication(sys.argv) ui = TrafficLight1.Ui_MainWindow() mainWindow = QMainWindow() ui.setupUi(mainWindow) events = Events(ui) ui.labelRedLight.state = "close" ui.labelYellowLight.state = "close" ui.labelGreenLight.state = "close" ui.labelRedLight.mousePressEvent = events.red_light_mouse_press_event ui.labelYellowLight.mousePressEvent = events.yellow_light_mouse_press_event ui.labelGreenLight.mousePressEvent = events.green_light_mouse_press_event ui.pushButtonConnect.mousePressEvent = events.pushButton_connect_mouse_press_event ui.pushButtonAuto.mousePressEvent = events.pushButton_auto_mouse_press_event ui.pushButtonStop.mousePressEvent = events.pushButton_stop_mouse_press_event ui.pushButtonStop.setEnabled(False) # 將全部組件的文本尺寸都設置爲30px mainWindow.setStyleSheet("QWidget{font-size:30px}"); mainWindow.show() app.exec()
如今整個系統都完事了,好好地享受咱們的成果吧!
文章相關附件能夠點擊下面的原文連接前往學習
原文連接:https://harmonyos.51cto.com/posts/3480#bkwz