树莓派水族箱控制器:Flask与核心控制程序协同方案问询
解决Flask与水族箱核心控制逻辑分离的问题
嘿,我完全懂你的困扰——水族箱的控制逻辑必须全天候持续运行,总不能靠有人一直刷网页来维持吧!咱们可以通过多线程的方式把核心控制逻辑和Flask Web服务分开,让核心代码独立跑起来,同时还能通过Web界面和它交互。下面给你详细说怎么做:
核心思路
把设备控制逻辑放到一个后台线程里,让它持续循环执行;Flask作为独立的Web线程,负责处理网页请求、展示状态,以及接收用户的控制指令(比如开关泵)。两者通过线程安全的共享变量传递状态,用锁来避免并发读写的问题。
修改后的完整代码
from flask import Flask, render_template import datetime import RPi.GPIO as GPIO import time import threading import Adafruit_GPIO.SPI as SPI import Adafruit_MCP3008 # 全局锁,保证线程安全 data_lock = threading.Lock() # GPIO初始化 GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) # MCP3008配置 CLK = 18 MISO = 23 MOSI = 24 CS = 25 mcp = Adafruit_MCP3008.MCP3008(clk=CLK, cs=CS, miso=MISO, mosi=MOSI) # 继电器引脚配置 Relay_Ch1 = 26 Relay_Ch2 = 20 Relay_Ch3 = 21 GPIO.setup(Relay_Ch1, GPIO.OUT) GPIO.setup(Relay_Ch2, GPIO.OUT) GPIO.setup(Relay_Ch3, GPIO.OUT) # 初始化继电器为断开状态(HIGH对应断开,根据你的继电器模块调整) GPIO.output(Relay_Ch1, GPIO.HIGH) GPIO.output(Relay_Ch2, GPIO.HIGH) GPIO.output(Relay_Ch3, GPIO.HIGH) # 光耦开关配置 OptSw1 = 17 OptSw2 = 27 GPIO.setup(OptSw1, GPIO.IN) GPIO.setup(OptSw2, GPIO.IN) # 全局状态变量 levels = 0 Status1 = "Open" Status2 = "Open" Relay1 = "Open" Relay2 = "Open" Relay3 = "Open" def RSVR(): global levels values = float(mcp.read_adc(0)) with data_lock: levels = int(values/1023*100) def TOPOFF(): global Status1, Status2 OptSwSts1 = GPIO.input(OptSw1) OptSwSts2 = GPIO.input(OptSw2) with data_lock: Status1 = "Close" if OptSwSts1 else "Open" Status2 = "Close" if OptSwSts2 else "Open" # 补水逻辑:每4小时的59分0-15秒,若两个开关都打开则启动补水 now = datetime.datetime.now() if now.hour % 4 == 0 and now.minute == 59 and 0 <= now.second <=15: if OptSwSts1 == False and OptSwSts2 == False: GPIO.output(Relay_Ch1, GPIO.LOW) print(f"Topoff Relay_Ch1 engaged {now}") else: GPIO.output(Relay_Ch1, GPIO.HIGH) else: GPIO.output(Relay_Ch1, GPIO.HIGH) def RELAYS(): global Relay1, Relay2, Relay3 Relay1_Sts = GPIO.input(Relay_Ch1) Relay2_Sts = GPIO.input(Relay_Ch2) Relay3_Sts = GPIO.input(Relay_Ch3) with data_lock: Relay1 = "Closed" if not Relay1_Sts else "Open" Relay2 = "Closed" if not Relay2_Sts else "Open" Relay3 = "Closed" if not Relay3_Sts else "Open" # 核心控制循环:后台线程持续运行 def core_control_loop(): while True: RSVR() TOPOFF() RELAYS() # 每隔1秒执行一次,可根据需求调整间隔 time.sleep(1) app = Flask(__name__) @app.route("/") def web(): with data_lock: # 读取当前状态(加锁保证数据一致性) current_levels = levels current_Status1 = Status1 current_Status2 = Status2 current_Relay1 = Relay1 current_Relay2 = Relay2 current_Relay3 = Relay3 now = datetime.datetime.now() timeString = now.strftime("%Y-%m-%d %H:%M:%S") templateData = { 'title' : 'AquaCR', 'time' : timeString, 'levels' : current_levels, 'OptSw1' : current_Status1, 'OptSw2' : current_Status2, 'Relay1' : current_Relay1, 'Relay2' : current_Relay2, 'Relay3' : current_Relay3 } return render_template('main.html', **templateData) # 添加Web控制路由:比如手动切换继电器 @app.route("/toggle/relay/<int:relay_num>") def toggle_relay(relay_num): relay_pins = {1: Relay_Ch1, 2: Relay_Ch2, 3: Relay_Ch3} if relay_num not in relay_pins: return "Invalid Relay Number" pin = relay_pins[relay_num] current_state = GPIO.input(pin) # 切换状态:HIGH是断开,LOW是闭合(根据你的继电器模块调整) GPIO.output(pin, GPIO.LOW if current_state else GPIO.HIGH) # 更新状态变量 RELAYS() return f"Relay {relay_num} toggled" if __name__ == "__main__": # 启动核心控制后台线程,设置为守护线程(主进程退出时自动结束) control_thread = threading.Thread(target=core_control_loop, daemon=True) control_thread.start() # 启动Flask Web服务 app.run(host='0.0.0.0', port=80, debug=True)
关键改动说明
- 线程分离:新增
core_control_loop()函数,把原来的控制逻辑放到这个循环里,用threading.Thread启动后台守护线程,让它持续运行,不再依赖Web请求。 - 线程安全:用
threading.Lock()创建全局锁,所有读写全局状态变量的操作都放在with data_lock:块里,避免多线程同时读写导致数据混乱。 - Web交互支持:新增
/toggle/relay/<int:relay_num>路由,允许你通过浏览器手动控制继电器(比如访问http://你的树莓派IP/toggle/relay/1就能切换继电器1的状态)。 - 状态同步:后台线程持续更新设备状态,Flask请求时读取最新的状态展示给用户,保证页面显示的是实时数据。
注意事项
- 如果你用的继电器模块逻辑相反(HIGH是闭合,LOW是断开),记得调整
GPIO.output()的参数。 time.sleep(1)的间隔可以根据你的需求调整,比如改成0.5秒会更灵敏,但也会增加CPU占用。- 调试模式
debug=True在生产环境建议关闭,避免安全问题。 - 可以考虑用
try/finally块来保证程序退出时正确清理GPIO资源,避免树莓派引脚状态异常。
内容的提问来源于stack exchange,提问作者Paul Thomas
相关产品推荐
相关产品推荐

