You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

树莓派3 LED交通灯与客户端通信代码故障排查求助

解决树莓派交通灯循环与Socket信号响应的冲突问题

嘿,我瞅了你的代码,核心问题其实很明显:socket.accept()是阻塞调用,它会一直卡在那儿等客户端连进来,导致你的红黄绿循环代码根本没机会执行!另外代码里还有几个小坑:比如判断c==0完全不对(accept()返回的c是客户端套接字对象,永远不可能是0),还有重复导入GPIO、重复初始化引脚的问题,这些都得改。

给你整个靠谱的解决方案——用多线程把交通灯循环和Socket监听分开,这样俩任务能同时跑,再用个标志位来触发紧急绿灯模式,完美解决阻塞问题。

修复后的完整代码

import socket
import RPi.GPIO as GPIO
import time
import threading

# GPIO只初始化一次!别重复搞
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# 定义引脚,方便后续维护
RED_PIN = 18
YELLOW_PIN = 23
GREEN_PIN = 24
GPIO.setup(RED_PIN, GPIO.OUT)
GPIO.setup(YELLOW_PIN, GPIO.OUT)
GPIO.setup(GREEN_PIN, GPIO.OUT)

# 全局标志位:标记是否要触发紧急绿灯
emergency_mode = False

def traffic_light_loop():
    """负责红黄绿循环的线程函数"""
    global emergency_mode
    while True:
        if emergency_mode:
            # 紧急模式:立刻亮绿灯,关掉其他灯
            GPIO.output(RED_PIN, GPIO.LOW)
            GPIO.output(YELLOW_PIN, GPIO.LOW)
            GPIO.output(GREEN_PIN, GPIO.HIGH)
            time.sleep(0.5)  # 加个短延迟,避免频繁切换
            continue
        
        # 红灯阶段(原来的2秒拆成20个0.1秒,方便响应紧急信号)
        GPIO.output(RED_PIN, GPIO.HIGH)
        GPIO.output(YELLOW_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        for _ in range(20):
            if emergency_mode:
                break
            time.sleep(0.1)
        
        if emergency_mode:
            continue
        
        # 黄灯阶段(1秒拆成10个0.1秒)
        GPIO.output(RED_PIN, GPIO.LOW)
        GPIO.output(YELLOW_PIN, GPIO.HIGH)
        GPIO.output(GREEN_PIN, GPIO.LOW)
        for _ in range(10):
            if emergency_mode:
                break
            time.sleep(0.1)
        
        if emergency_mode:
            continue
        
        # 绿灯阶段(2秒拆成20个0.1秒)
        GPIO.output(RED_PIN, GPIO.LOW)
        GPIO.output(YELLOW_PIN, GPIO.LOW)
        GPIO.output(GREEN_PIN, GPIO.HIGH)
        for _ in range(20):
            if emergency_mode:
                break
            time.sleep(0.1)

def socket_listener():
    """负责监听Socket连接的线程函数"""
    global emergency_mode
    s = socket.socket()
    host = ''
    port = 12345
    s.bind((host, port))
    s.listen(5)
    print("等着电脑端发信号呢...")
    
    while True:
        c, addr = s.accept()
        print(f'收到来自 {addr} 的连接啦!')
        # 触发紧急模式
        emergency_mode = True
        # 给电脑端回个消息
        c.send('救护车已检测到,绿灯已点亮!'.encode('utf-8'))
        c.close()
        
        # 保持绿灯3秒(你可以根据需求调整这个时间)
        time.sleep(3)
        # 恢复正常循环
        emergency_mode = False

if __name__ == "__main__":
    try:
        # 启动两个线程
        light_thread = threading.Thread(target=traffic_light_loop)
        socket_thread = threading.Thread(target=socket_listener)
        
        # 设置为守护线程,主程序退出时自动结束
        light_thread.daemon = True
        socket_thread.daemon = True
        
        light_thread.start()
        socket_thread.start()
        
        # 主线程保持运行,不然程序会直接退出
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        # 按Ctrl+C时清理GPIO,避免引脚状态异常
        GPIO.cleanup()
        print("程序已终止~")

关键改动说明

  • 多线程分离任务:把交通灯循环和Socket监听拆成两个独立线程,再也不会因为accept()阻塞导致循环跑不起来了。
  • 紧急标志位控制:用emergency_mode这个全局变量来切换模式,交通灯线程会不断检查这个标志,一旦为True就立刻切到绿灯。
  • 拆分长延迟:把原来的time.sleep(2)拆成多个0.1秒的小延迟,这样就算在红灯/黄灯期间,也能在0.1秒内响应紧急信号,不会出现半天没反应的情况。
  • GPIO初始化只做一次:原来的代码在每个分支都重复导入GPIO、初始化引脚,现在只在开头做一次,既高效又避免潜在问题。
  • 优雅退出:捕获Ctrl+C信号,清理GPIO资源,防止树莓派引脚一直处于通电状态。

电脑端测试代码

你可以在电脑上跑这个代码来测试:

import socket

s = socket.socket()
# 把这里改成你的树莓派IP
raspberry_pi_ip = "192.168.x.x"
port = 12345

s.connect((raspberry_pi_ip, port))
print(s.recv(1024).decode('utf-8'))
s.close()

内容的提问来源于stack exchange,提问作者Rahul

火山引擎 最新活动