百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

牛批!用Python远程控制交通信号灯

off999 2024-10-27 11:55 36 浏览 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

相关推荐

搜狗输入法2020版下载(搜狗输入法 下载)
  • 搜狗输入法2020版下载(搜狗输入法 下载)
  • 搜狗输入法2020版下载(搜狗输入法 下载)
  • 搜狗输入法2020版下载(搜狗输入法 下载)
  • 搜狗输入法2020版下载(搜狗输入法 下载)
qq好友恢复中心官方网站(qq好友恢复官方网站入口)
  • qq好友恢复中心官方网站(qq好友恢复官方网站入口)
  • qq好友恢复中心官方网站(qq好友恢复官方网站入口)
  • qq好友恢复中心官方网站(qq好友恢复官方网站入口)
  • qq好友恢复中心官方网站(qq好友恢复官方网站入口)
优酷app正版下载(下载优酷官方正版)

可能是网络连接不稳定或者应用商店服务器出现了问题导致无法正常下载。建议您检查网络连接是否正常,并尝试在其他应用商店中搜索并下载该应用。如果仍然无法下载,建议联系手机厂商或者应用商店客服寻求帮助解决。1...

热门小说排行榜(谷歌热门小说排行榜)

1.我吃西红柿:星辰变盘龙九鼎记2.血红:人途逍行纪邪龙道神机鬼藏邪风曲3.唐家三少:斗罗大陆琴帝4.辰东:神墓长生界不死不灭5.流浪的蛤蟆:仙葫6.烽火戏诸侯:极品公子陈二狗的妖孽人生7.跳舞:恶魔...

免费音乐下载网站mp3(免费音乐下载)

有免费下载mp3的网站。除了知名的几个音乐平台外,还有以下三款支持免费MP3无损音乐下载网站,可以将喜欢的歌曲下载到U盘。说明书里有呀91flac音乐网,试试这个,绝对好使,但是不要在酷狗上面说下载最...

印章在线生成(电子印章在线生成)

1、绘制外框先打开我们的Word文档,然后选择【插入】-【形状】-【椭圆】,在空白文档处绘制圆形外框,按住【Shift】就可以绘制正圆,绘制完成后选择【填充】-【无填充】,【轮廓】-【红色】,将线条粗...

一个手机号可以注册几个qq(一个手机号可以注册几个微信号)

可以注册多个QQ号方法如下:1、退出当前QQ,在登录界面点击“新用户注册”。2、输入本机的手机号码,点击下一步。3、输入手机接收到的验证码。4、点击“继续注册”。5、点击“不绑定”。6、输入新QQ昵称...

迷你世界官方版(迷你世界官方版官网入口)

我在官网上下载迷你世界的话,你需要进入浏览器网上下载APP进行下载迷你官网登录时候显示冻结可能是输密码错了好多几次导致的关于您的迷你世界账号被冻结了,可以添加官方联络方式或进行在线投诉建议等进行人...

浩辰cad看图王(浩辰cad看图王电脑版)

多。浩辰cad看图王相比于其他cad看图软件,拥有更多的功能可以使用。它不仅可以用来看图,还拥有图纸测量、添加标注、添加线条、文字等功能。解决方法如下:1、打开浩辰cad,输入一个命令,可以发现光标旁...

打字赚钱一单一结(打字赚钱一单一结app排行榜前十名)

以下是一些可以用于打字接单的App:1.蓝鲸输入法:这是一款功能强大的输入法工具,内置了打字接单功能,可以在输入法中直接接单并进行打字工作。2.有道云笔记:这是一款综合性笔记工具,在其中可以创建任...

ai换脸(ai换脸下载)
  • ai换脸(ai换脸下载)
  • ai换脸(ai换脸下载)
  • ai换脸(ai换脸下载)
  • ai换脸(ai换脸下载)
免费自动连接wifi(最新万能钥匙安全免费自动连接)
免费自动连接wifi(最新万能钥匙安全免费自动连接)

大多数安卓手机都是支持自动连接免费wifi的。同时,为了安全起见,也有对应的选项:检查wifi安全性。这个可以开启的。当然如果只是经常使用常用地点的wifi,这个可以关闭。关闭方法以三星手机为例:进入“设置”,“连接”,“wlan”,“右上...

2026-02-01 02:43 off999

画图工具(画图工具在电脑哪里)
画图工具(画图工具在电脑哪里)

常用的绘图软件有以下这些:1、Coreldraw。Coreldraw是一个绘图和排版软件,主要处理矢量文件。主要用于商标设计、标志制作、模型绘制、插图绘制、排版、分色输出。它在艺术设计和商业设计领域非常流行。2、Photoshop。最强大...

2026-02-01 02:15 off999

qq加速器(Bill bill加速器)

可能原因如下:1、官方服务器正在更新升级,为了给用户更好的体验,系统的维护是必不可少的。2、可以检查是否是网络出现了问题,可以更换网络是否正常。3、很多软件都在维护,包括QQ,微信,微博等,只能是等维...

进入qq安全中心的官网(qq安全中心入口)

1、以电脑版QQ为例,成功登录QQ后,点击界面左下角的“主菜单”图标;2、在弹出框里点击“安全”,再在右边的弹出框里点击“安全中心首页”;3、就会跳转到QQ安全中心的网页了,在里面进行修改密码、我的密...

取消回复欢迎 发表评论: