【Arduino實驗室】NB的玩法,鴻蒙版遠程控制交通信號燈-ESP8266聯(lián)網(wǎng)
Flag:如果本文閱讀量超過10000,或點贊+收藏超過100,我就推出鴻蒙版遠程控制信號燈以及其他智能硬件的文章!!
本文涉及到的源代碼和其他資源見附件!
【Arduino實驗室】中關于智能硬件的實驗在網(wǎng)上絕對是沒有的(有也是我發(fā)的),都由作者單獨設計。敬請期待后期的【鴻蒙實驗室】系列文章和視頻課程。
這個案例是將Python、PyQT6與Arduino結合。通過Arduino開發(fā)板控制3個LED(分別為紅黃綠3個顏色)來模擬交通信號燈??梢酝ㄟ^單擊PC端的三個燈控制Arduino開發(fā)板和3個LED。也可以點擊“自動”按鈕讓信號燈自動變換。本系統(tǒng)完全模擬真實的信號燈的自動切換過程。一開始紅燈亮6秒(為了減少一個完整變化周期的時間,并沒有讓信號燈亮過長時間),然后立刻切換到綠燈,繼續(xù)亮6秒。接下來綠燈閃爍6次(每1秒閃爍一次,一亮一滅),然后切換到黃燈,亮3秒,最后到紅燈(亮6秒),完成一個信號周期。如果點擊“停止”按鈕,信號燈會停止自動切換,但會在完成一個信號周期后停止。視頻演示見附件。
1. 需要準備哪些實驗設備和器材
本實驗需要準備的設備如下:
(1)PC一臺,系統(tǒng)可以是Windows、macOS或Linux,需要安裝Python環(huán)境和PyQt6、以及Arduino IDE;
(2)Arduino開發(fā)板一塊,推薦使用UNO;
(3)3個LED,建議紅、黃、綠各一個;
(4)ESP8266 Wi-Fi模塊一個,用于聯(lián)網(wǎng);
(5)10K電阻一個;
(6)面包板一個,主要用于解決Arduino開發(fā)板接口不足的情況;
(7)杜邦線若干,可以多準備一些(最好有多種顏色),反正很便宜。需要兩類杜邦線:公對公、公對母;
2. ESP8266 Wi-Fi模塊與Arduino開發(fā)板連接
玩物聯(lián)網(wǎng),其實涉及到硬件和軟件兩部分。硬件主要涉及到選擇和連接,一般并不涉及到硬件的設計和制作。本實驗的核心模塊是ESP8266,這是一個Wi-Fi模塊,價格非常便宜,國內價格在15到20元之間,某寶就有賣。
要做的第一步就是將ESP8266與Arduino開發(fā)板用杜邦線連接,程序是上傳到Arduino開發(fā)板上的,然后通過AT命令與ESP8266模塊交互。
ESP8266的樣子如圖1所示。這個模塊相對其他大多數(shù)模塊(如超聲波模塊、LED、按鈕等)要復雜一些,一共有8個管腳,也就是8個針。
ESP8288一般與Arduino開發(fā)板直接相連。Arduino開發(fā)板的樣子如圖2所示。管腳都是眼,所以需要若干公對母的杜邦線。
現(xiàn)在回到圖1的8個管腳,其實在開發(fā)階段,只需要連接其中的5個即可,在刷固件時,一般需要連接6個管腳(關于刷ESP8266固件的問題,我后面會寫文章介紹)。需要連接的5個管腳如下:
(1)3.3v:接到Arduino開發(fā)板的3.3v插孔上,記住,一定是3.3v,不要接在5v上,否則你還需要再買一塊ESP8266,切記、切記、切記;
(2)EN:同樣需要接到3.3v上,但Arduino開發(fā)板只有一個3.3v插孔,所以需要借助面包板;
(3)GND:接在Arduino開發(fā)板的GND插孔(一般有3個,插如任意一個GND插孔即可);
(4)TX:通常接在一個軟串口,本例接到8上;
(5)RX:通常接在一個軟串口,本例接到9上;
3. 模擬連接ESP8266與Arduino開發(fā)板
在正式連接之前,最好先用Fritzing模擬連接下,相當于畫個草圖,以免接錯。連接完成的效果如圖3所示。
這里要說明的是,EN與3.3v都需要接到Arduino開發(fā)板的3.3v管腳上,但Arduino開發(fā)板只有一個3.3v管腳,所以首先將Arduino開發(fā)板的3.3v管腳通過杜邦線(圖3中紅色的線)連接到面包板,然后EN與3.3v再通過杜邦線插入面包板中(兩根橙色的線),要記住,其中一根橙色的線要與紅色線在同一列,另外一根橙色的線在另一列,并讓這兩列用給一個10K的電阻相連,為了不讓ESP8266的EN和3.3v兩個管腳由于直接連通而短路。注意,一定要用電阻相連,推薦是10K歐的電阻。
4. 連接3個LED
LED的連接就簡單的多,LED如圖4所示。
短一些(左側)的管腳接地,長一些(右側)的管腳接某個數(shù)字管腳,本例綠、黃、紅分別接在了7、6、5管腳。由于Arduino開發(fā)板只有3個GND管腳,而ESP8266已經(jīng)占用了一個,所以仍然需要借助面包板擴展GND管腳?;驹硎菍ND通過杜邦線與面包板連接(本例中的黑線),然后將3個LED的GND端都通過杜邦線插入與黑色杜邦線在面包板位置的同一列的其他插孔。最終的效果如圖5所示。
5. 在Arduino開發(fā)板上創(chuàng)建TCP服務器
ESP8266與Arduino交互通常有如下2種方式:
(1)ESP8266作為服務器;
(2)ESP8266作為客戶端;
本實驗采用了第1種方式,第2種方式后面我會寫文章介紹。
不管采用哪一種方式,首先要讓ESP8266上網(wǎng)。通常是連接家中的無線路由器,或用手機做的熱點。設置ESP8266需要通過AT命令,其實就是一組解釋執(zhí)行的命令,與DOS命令類似。
ESP8266在出廠時的波特率是115200,所以執(zhí)行AT命令,必須在這個波特率下。在setup函數(shù)中使用下面的代碼設置波特率,以及執(zhí)行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),為其他設備提供用于上網(wǎng)的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函數(shù)用于執(zhí)行AT命令。要注意,每執(zhí)行一條AT命令,要等待一定的時間,這里是500毫秒。
當?shù)谝槐閳?zhí)行完,除非Arduino開發(fā)板重啟或重新上傳程序,否則setup函數(shù)只會執(zhí)行一次。以后可以將連接路由器的代碼去掉了,因為ESP8266是有記憶功能的,這種配置性質的AT命令,執(zhí)行完,會將執(zhí)行結果記錄在案。所以下一次重啟ESP8266模塊時,不管執(zhí)行不執(zhí)行這條AT命令,ESP8266都會自動連接路由器。
不過其他代碼應該保留,因為這些代碼的執(zhí)行結果是不會記錄在案的,當重啟ESP8266模塊時,需要重新執(zhí)行這些AT命令來建立TCP服務。
經(jīng)過測試,ESP8266在115200波特率的情況下,通過Wi-Fi傳輸數(shù)據(jù)容易出現(xiàn)亂碼,所以需要使用下面的代碼將ESP8266模塊的波特率強行改成9600,這樣數(shù)據(jù)傳輸非常穩(wěn)定。
- wifi.println("AT+CIOBAUD=9600\r\n");
在Arduino開發(fā)板上建立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 = ""; // 接收客戶端發(fā)過來的數(shù)據(jù)
- void setup() {
- //5、6、7三個管腳設置為輸出,以便輸出高電平來點亮LED
- pinMode(LED_RED, OUTPUT);
- pinMode(LED_YELLOW, OUTPUT);
- pinMode(LED_GREEN, OUTPUT);
- // 先將5、6、7三個管腳設置為低電平,默認LED是滅的狀態(tài)
- digitalWrite(LED_RED, LOW);
- digitalWrite(LED_YELLOW, LOW);
- digitalWrite(LED_GREEN, LOW);
- Serial.begin(9600);
- wifi.begin(9600); // 已經(jīng)改成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);
- }
- // 該函數(shù)會不斷循環(huán)調用
- void loop() {
- // 從客戶端(PC端)讀取發(fā)過來的數(shù)據(jù)
- while (wifi.available() > 0) {
- command += char(wifi.read());
- delay(4);
- }
- // 如果數(shù)據(jù)不為空,繼續(xù)處理
- if (command != "") {
- // 將接收到的命令輸出到串口監(jiān)視器
- 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個命令字符串中的一個,就確定客戶端發(fā)出了該命令。
然后使用Arduino IDE上傳程序即可(別忘了選擇開發(fā)板和端口)
PS:如果要改變端口號,可以直接修改5000,然后需要重啟Arduino開發(fā)板(當然,ESP8266也會重啟),這樣就會再次執(zhí)行setup函數(shù)來重新啟動TCP服務。
6. 編寫Python程序
這里編寫客戶端程序,使用PyQt6編寫UI、使用Python編寫全部業(yè)務邏輯,由于代碼比較多,所以只給出了核心代碼,基本原理就是Python通過TCP Socket API連接ESP8255中的TCP Server,然后不斷發(fā)送上一節(jié)給出的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("退出自動運行狀態(tài)")
- # 包含UI事件的代碼
- class Events:
- def __init__(self, ui):
- self.ui = ui
- self.connected = False # 是否已經(jīng)與Arduino建立了連接
- self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 聲明協(xié)議類型,不寫類型使用默認的
- 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參數(shù)用于指定打開哪一個顏色的信號燈)
- 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參數(shù)用于指定關閉哪一個顏色的信號燈)
- 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
- # 點擊紅燈時觸發(fā)
- 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")
- # 點擊黃燈時觸發(fā)
- 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")
- # 點擊綠燈時觸發(fā)
- 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")
- # 點擊“連接”按鈕時觸發(fā),用于鏈接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")
- # 點擊“自動”按鈕觸發(fā),用于開啟信號燈自動切換模式
- 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)
- # 點擊“停止”按鈕觸發(fā),用于關閉信號燈自動切換模式
- 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()
現(xiàn)在整個系統(tǒng)都完事了,好好地享受我們的成果吧!
wifi_led演示(長).mp4.zip