You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派水族箱控制器: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)

关键改动说明

  1. 线程分离:新增core_control_loop()函数,把原来的控制逻辑放到这个循环里,用threading.Thread启动后台守护线程,让它持续运行,不再依赖Web请求。
  2. 线程安全:用threading.Lock()创建全局锁,所有读写全局状态变量的操作都放在with data_lock:块里,避免多线程同时读写导致数据混乱。
  3. Web交互支持:新增/toggle/relay/<int:relay_num>路由,允许你通过浏览器手动控制继电器(比如访问http://你的树莓派IP/toggle/relay/1就能切换继电器1的状态)。
  4. 状态同步:后台线程持续更新设备状态,Flask请求时读取最新的状态展示给用户,保证页面显示的是实时数据。

注意事项

  • 如果你用的继电器模块逻辑相反(HIGH是闭合,LOW是断开),记得调整GPIO.output()的参数。
  • time.sleep(1)的间隔可以根据你的需求调整,比如改成0.5秒会更灵敏,但也会增加CPU占用。
  • 调试模式debug=True在生产环境建议关闭,避免安全问题。
  • 可以考虑用try/finally块来保证程序退出时正确清理GPIO资源,避免树莓派引脚状态异常。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 08:57:47