牛批!用Python远程控制交通信号灯
off999 2024-10-27 11:55 31 浏览 0 评论
图片来自 Pexels
Arduino 实验室中关于智能硬件的实验在网上绝对是没有的(有也是我发的),都由作者单独设计。敬请期待后期的【鸿蒙实验室】系列文章和视频课程。
这个案例是将 Python、PyQT6 与 Arduino 结合。通过 Arduino 开发板控制 3 个 LED(分别为红黄绿 3 个颜色)来模拟交通信号灯。
可以通过单击 PC 端的三个灯控制 Arduino 开发板和 3 个 LED。也可以点击“自动”按钮让信号灯自动变换。
本系统完全模拟真实的信号灯的自动切换过程。一开始红灯亮 6 秒(为了减少一个完整变化周期的时间,并没有让信号灯亮过长时间),然后立刻切换到绿灯,继续亮 6 秒。
接下来绿灯闪烁 6 次(每 1 秒闪烁一次,一亮一灭),然后切换到黄灯,亮 3 秒,最后到红灯(亮 6 秒),完成一个信号周期。如果点击“停止”按钮,信号灯会停止自动切换,但会在完成一个信号周期后停止。视频演示请点击“阅读原文”见附件。
01需要准备哪些实验设备和器材
本实验需要准备的设备如下:
- PC 一台,系统可以是 Windows、macOS 或 Linux,需要安装 Python 环境和 PyQt6、以及 Arduino IDE。
- Arduino 开发板一块,推荐使用 UNO。
- 3 个 LED,建议红、黄、绿各一个。
- ESP8266 Wi-Fi 模块一个,用于联网。
- 10K 电阻一个。
- 面包板一个,主要用于解决 Arduino 开发板接口不足的情况。
- 杜邦线若干,可以多准备一些(最好有多种颜色),反正很便宜。需要两类杜邦线:公对公、公对母。
02与 Arduino 开发板连接
玩物联网,其实涉及到硬件和软件两部分。硬件主要涉及到选择和连接,一般并不涉及到硬件的设计和制作。
本实验的核心模块是 ESP8266,这是一个 Wi-Fi 模块,价格非常便宜,国内价格在 15 到 20 元之间,某宝就有卖。
要做的第一步就是将 ESP8266 与 Arduino 开发板用杜邦线连接,程序是上传到 Arduino 开发板上的,然后通过 AT 命令与 ESP8266 模块交互。
ESP8266 的样子如图 1 所示。这个模块相对其他大多数模块(如超声波模块、LED、按钮等)要复杂一些,一共有 8 个管脚,也就是 8 个针。
ESP8288 一般与 Arduino 开发板直接相连。Arduino 开发板的样子如图 2 所示。管脚都是眼,所以需要若干公对母的杜邦线。
现在回到图 1 的 8 个管脚,其实在开发阶段,只需要连接其中的 5 个即可,在刷固件时,一般需要连接 6 个管脚(关于刷 ESP8266 固件的问题,我后面会写文章介绍)。
需要连接的 5 个管脚如下:
- 3.3v:接到 Arduino 开发板的 3.3v 插孔上,记住,一定是 3.3v,不要接在 5v 上,否则你还需要再买一块 ESP8266,切记、切记、切记。
- EN:同样需要接到 3.3v 上,但 Arduino 开发板只有一个 3.3v 插孔,所以需要借助面包板。
- GND:接在 Arduino 开发板的 GND 插孔(一般有 3 个,插如任意一个 GND 插孔即可)。
- TX:通常接在一个软串口,本例接到 8 上。
- RX:通常接在一个软串口,本例接到 9 上。
03连接 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 欧的电阻。
04连接 3 个 LED
LED 的连接就简单的多,LED 如图 4 所示:
短一些(左侧)的管脚接地,长一些(右侧)的管脚接某个数字管脚,本例绿、黄、红分别接在了 7、6、5 管脚。
由于 Arduino 开发板只有 3 个 GND 管脚,而 ESP8266 已经占用了一个,所以仍然需要借助面包板扩展 GND 管脚。
基本原理是将 GND 通过杜邦线与面包板连接(本例中的黑线),然后将 3 个 LED 的 GND 端都通过杜邦线插入与黑色杜邦线在面包板位置的同一列的其他插孔。
最终的效果如图 5 所示:
05创建 TCP 服务器
ESP8266 与 Arduino 交互通常有如下 2 种方式:
- ESP8266 作为服务器
- 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() {
//5、6、7三个管脚设置为输出,以便输出高电平来点亮LED
pinMode(LED_RED, OUTPUT);
pinMode(LED_YELLOW, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
// 先将5、6、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 服务。
06编写 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
# 线程类,用于自动切换信号灯
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
相关推荐
- 纯净无毒的win7下载(有没有纯净的win7系统)
-
下面提供的是微软发布的Windows7各版本光盘ISO镜像下载地址,原始文件均来源自MSDN,和零-售彩盒版本光盘内容完全一致。请放心下载。(如果需要光盘的买家,请无视以下内容)下...
- ie浏览器文件损坏怎么修复(ie浏览器破坏怎么恢复)
-
可以在浏览记录里面查到。重新下载一次就行了如果你在IE浏览器里面下载的文件被你不小心删掉了,而且这个文件对你来说很重要,你可以打开你的IE浏览器选择历史记录,在历史记录里面就可以找到相关的下载的地方,...
- 电脑没音量是什么原因(电脑没音量是什么原因造成的)
-
电脑突然没有声音可能是由于以下原因:1.音量设置被关闭或静音;2.音频驱动程序出现问题或需要更新;3.音频设备连接问题,例如音频插头没有插好或者有问题;4.操作系统的声音设置出现问题。解决方法包括检查...
- 电脑蓝屏u盘装系统教程(电脑蓝屏系统u盘怎么装系统)
-
dell电脑蓝屏用U盘重装系统方法,1在需要重装的电脑上插入启动U盘,开机并不断按下U盘启动快捷键。2在进入系统启动菜单中选择带有USB字样的选项并回车。3进入系统引导选项界面,选择Windows1...
- 系统之家官网纯净版(系统之家首页)
-
我现在用的就是系统之家的win7,以前用的是系统之家纯净版的xp,纯净版的好处就是没有预装的软件,缺点就是有些东西要自己装,包括有些软件必须的东西。装个游戏又要装这个又要装那个的很麻烦,我现在都是安装...
- 声卡正常但是麦克风没有声音
-
1、检查声卡、连接线,以及音箱等设备是否连接正常;运行杀毒软件进行杀毒;2、右击“我的电脑”----“属性”---“硬件”----“设备管理器”,打开“声音、视频和游戏控制器”有无问题,即看前面有没有...
- coreldraw教程自学(coreldraw零基础教学视频)
-
1、CDR是矢量软件,国内印刷业和部分设计用的较多;2、基础入门操作:A、看你用在具体的哪个细分行业,如印刷排版方面、户外室内喷绘方面、图形设计方面、字体设计等等……输出的要求不一样,其他基本一样...
- cad2010序列号和密钥激活码免费
-
回答如下:不可以,CAD2010序列号和密钥是两个不同的概念。序列号是一个唯一的标识符,用于识别软件产品的副本。而密钥是一个加密字符串,用于验证软件副本的合法性。在安装CAD2010时,需要输入正确的...
- 笔记本电脑联想好还是惠普好
-
惠普和联想为全球名列前茅的电脑品牌,其中惠普为美国品牌,联想为中国品牌。联想作为一个后起之秀,当年通过收购thankpad品牌,走向全球。电脑中又分出多种风格的子品牌,有的注重游戏性能,有的注重轻薄商...
- 如何安装路由器步骤(安装路由器操作步骤)
-
路由器安装设置共分两个步骤,依次是”路由器与光猫“串联,“宽带账号写入路由器”即可,方法如下:?步骤一首先将路由器与光猫串联,用网线一端连接至“光猫”的LAN端口,另一端连接至“路由器”的WAN端口“...
- 86位系统是不是32位(86位和32位)
-
众所周知,同众多主打影音娱乐的系统不同,Windows从工业生产到金融消费、从个人应用到企业环境,应用领域极其广泛。以工业生产为例,嵌入式设备只需满足生产中特定的任务,并不追求过剩性能,故而现有绝大多...
- msdn官网下载系统u盘(msdn下载后要怎么做u盘启动盘)
-
1.打开MSDN网站并搜索您需要下载的系统。2.找到并单击“下载”按钮。3.如果系统有多个版本,请选择您需要的版本。4.选择“保存到磁盘”并单击“确定”按钮。5.将U盘插入计算机,并打开文件...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
python入门到脱坑 输入与输出—str()函数
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
失业程序员复习python笔记——条件与循环
-
使用 python-fire 快速构建 CLI_如何搭建python项目架构
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
