使用ch340继电器完成随机断电测试

news2025/7/26 16:36:30

前言

如图所示是市面上常见的OTA压测继电器,通过ch340串口模块完成对继电器的分路控制,这里我编写了一个脚本方便对4路继电器的控制,可以设置开启时间,关闭时间,复位等功能
在这里插入图片描述

软件界面

在这里插入图片描述
在设备管理器查看串口号后,选中串口号,波特率使用9600,选择要控制的继电器,当选中后状态指示灯变为绿色,这时候再设置开启和关闭时间,启动运动即可完成控制了,为了防止状态错乱,我增加了一个复位按钮,一键把所有继电器状态置为初始状态。

运行界面

启动运行后,文本栏会打印相关显示,指示不同继电器状态
在这里插入图片描述

完整代码

代码全部使用python编写,相关依赖的环境较多,我换了电脑后执行经常出问题,就打包成exe运行了。如果有需要exe的可以评论留下邮箱

依赖的库和环境

项目推荐配置
Python 版本Python 3.9 或 3.10(64位)
操作系统Windows 10 / 11
GUI 框架tkinter(Python 标准库中已内置)

pyserial>=3.5

库名用途说明
tkinter图形用户界面(标准库)
serial (pyserial)串口通信控制继电器
threadingtime多线程控制继电器 + 定时
osjsondatetime有效期校验和配置记录处理

代码

import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import time
import os
import json
from datetime import datetime, timedelta

# 有效期检查函数
def check_expiry():
    valid_days = 182  # 6个月
    file_path = "activated.json"

    if os.path.exists(file_path):
        with open(file_path, "r") as f:
            data = json.load(f)
            start_time = datetime.strptime(data["start_time"], "%Y-%m-%d")
    else:
        start_time = datetime.now()
        with open(file_path, "w") as f:
            json.dump({"start_time": start_time.strftime("%Y-%m-%d")}, f)

    now = datetime.now()
    delta = now - start_time
    if delta.days > valid_days:
        messagebox.showerror("软件已过期", "本程序有效期为6个月,已过期,无法继续使用。")
        root.destroy()

# 获取串口列表
def get_serial_ports():
    return [port.device for port in serial.tools.list_ports.comports()]

# 串口发送(线程安全)
serial_lock = threading.Lock()

def send_hex_serial_command(port, baudrate, hex_command):
    try:
        with serial_lock:
            with serial.Serial(port, baudrate, timeout=1) as ser:
                ser.write(bytes.fromhex(hex_command))
                log(f"[发送] {hex_command}")
    except Exception as e:
        log(f"发送失败: {e}")

# 日志输出
def log(msg):
    log_text.config(state='normal')
    log_text.insert(tk.END, msg + '\n')
    log_text.see(tk.END)
    log_text.config(state='disabled')

# 是否处于运行状态
is_running = False

# 控制线程(每路继电器)
def relay_loop(index, port, baudrate, get_on_time, get_off_time):
    on_cmds  = ["A0 01 01 A2", "A0 02 01 A3", "A0 03 01 A4", "A0 04 01 A5"]
    off_cmds = ["A0 01 00 A1", "A0 02 00 A2", "A0 03 00 A3", "A0 04 00 A4"]

    while not is_running:
        time.sleep(0.1)

    try:
        while is_running:
            if relay_enabled[index].get():
                on_time = get_on_time()
                off_time = get_off_time()

                send_hex_serial_command(port, baudrate, on_cmds[index])
                log(f"[继电器{index+1}] 打开,保持 {on_time} 秒")
                time.sleep(on_time)

                send_hex_serial_command(port, baudrate, off_cmds[index])
                log(f"[继电器{index+1}] 关闭,保持 {off_time} 秒")
                time.sleep(off_time)
            else:
                time.sleep(1)
    except Exception as e:
        log(f"[继电器{index+1}] 错误: {e}")

# 启动按钮功能
def start_control_loop():
    global is_running

    if is_running:
        log("控制已在运行")
        return

    port = port_var.get()
    baudrate = int(baudrate_var.get())

    if not port:
        messagebox.showerror("错误", "请选择串口")
        return

    try:
        [int(e.get()) for e in on_entries]
        [int(e.get()) for e in off_entries]
    except ValueError:
        messagebox.showerror("错误", "请输入合法整数")
        return

    is_running = True
    start_button.config(style="BigGreen.TButton")

    for i in range(4):
        t = threading.Thread(
            target=relay_loop,
            args=(
                i,
                port,
                baudrate,
                lambda i=i: int(on_entries[i].get()),
                lambda i=i: int(off_entries[i].get())
            ),
            daemon=True
        )
        t.start()

    log("并行继电器控制已启动")

# 启用/禁用开关
def toggle_relay(index):
    current = relay_enabled[index].get()
    relay_enabled[index].set(not current)
    if relay_enabled[index].get():
        status_labels[index].config(text="🟢", fg="green")
    else:
        status_labels[index].config(text="🔴", fg="red")

# 复位按钮功能
def reset_all():
    global is_running
    is_running = False
    for i in range(4):
        relay_enabled[i].set(False)
        status_labels[i].config(text="🔴", fg="red")
        on_entries[i].delete(0, tk.END)
        on_entries[i].insert(0, "10")
        off_entries[i].delete(0, tk.END)
        off_entries[i].insert(0, "10")
    start_button.config(style="Big.TButton")
    log("已复位:所有继电器禁用,时间重置为10秒")

# 创建主窗口
root = tk.Tk()
root.withdraw()  # 启动时先隐藏窗口
check_expiry()   # 执行有效期检查
root.deiconify() # 检查通过再显示窗口

root.title("继电器并行控制器(启动前选中,启动后运行)")

# 样式定义
style = ttk.Style()
style.configure("Big.TButton", font=("Arial", 13, "bold"), padding=12)
style.configure("Small.TButton", font=("Arial", 10), padding=5)
style.configure("BigGreen.TButton", font=("Arial", 13, "bold"), padding=12, background="green")
style.map("BigGreen.TButton", background=[("active", "green")])

# 串口设置区域
frame_top = ttk.Frame(root)
frame_top.pack(padx=10, pady=5, anchor="w")

ttk.Label(frame_top, text="串口号:").pack(side='left')
port_var = tk.StringVar()
port_menu = ttk.Combobox(frame_top, textvariable=port_var, values=get_serial_ports(), width=10)
port_menu.pack(side='left', padx=5)

ttk.Label(frame_top, text="波特率:").pack(side='left')
baudrate_var = tk.StringVar(value="9600")
baudrate_menu = ttk.Combobox(frame_top, textvariable=baudrate_var, values=["9600", "115200"], width=10)
baudrate_menu.pack(side='left', padx=5)

# 控制区域表格
frame_time = ttk.Frame(root)
frame_time.pack(padx=10, pady=5, anchor="w")

ttk.Label(frame_time, text="继电器").grid(row=0, column=0, sticky="w")
ttk.Label(frame_time, text="名称").grid(row=0, column=1, sticky="w")
ttk.Label(frame_time, text="状态").grid(row=0, column=2, sticky="w")
ttk.Label(frame_time, text="开时间(秒)").grid(row=0, column=3, sticky="w")
ttk.Label(frame_time, text="关时间(秒)").grid(row=0, column=4, sticky="w")

on_entries = []
off_entries = []
relay_enabled = []
status_labels = []
control_buttons = []

def make_toggle_cmd(i):
    return lambda: toggle_relay(i)

for i in range(4):
    ttk.Label(frame_time, text=f"{i+1}").grid(row=i+1, column=0, sticky="w")

    enable_var = tk.BooleanVar(value=False)
    relay_enabled.append(enable_var)

    btn = ttk.Button(frame_time, text=f"继电器{i+1}", command=make_toggle_cmd(i), width=10)
    btn.grid(row=i+1, column=1, sticky="w")
    control_buttons.append(btn)

    status = tk.Label(frame_time, text="🔴", fg="red", font=("Arial", 12))
    status.grid(row=i+1, column=2, sticky="w")
    status_labels.append(status)

    on_entry = ttk.Entry(frame_time, width=8)
    on_entry.insert(0, "10")
    on_entry.grid(row=i+1, column=3, sticky="w")
    on_entries.append(on_entry)

    off_entry = ttk.Entry(frame_time, width=8)
    off_entry.insert(0, "10")
    off_entry.grid(row=i+1, column=4, sticky="w")
    off_entries.append(off_entry)

start_button = ttk.Button(
    frame_time,
    text="启动运行",
    command=start_control_loop,
    style="Big.TButton",
    width=14
)
start_button.grid(
    row=1,
    column=6,
    rowspan=4,
    padx=(40, 0),
    pady=(20, 0),
    sticky="ns"
)

# 日志输出
log_text = tk.Text(root, height=12, state='disabled')
log_text.pack(padx=10, pady=5, fill='both', expand=True)

# 复位按钮
frame_reset = ttk.Frame(root)
frame_reset.pack(padx=10, pady=(0, 5), anchor="e")

reset_btn = ttk.Button(frame_reset, text="复位", command=reset_all, style="Small.TButton")
reset_btn.pack()

# 启动主循环
root.mainloop()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于谷歌ADK的 智能产品推荐系统(2): 模块功能详解

在我的上一篇博客:基于谷歌ADK的 智能产品推荐系统(1): 功能简介-CSDN博客 中我们介绍了个性化购物 Agent 项目,该项目展示了一个强大的框架,旨在模拟和实现在线购物环境中的智能导购。它不仅仅是一个简单的聊天机器人,更是一个集…

VSCode 没有添加Windows右键菜单

关键字:VSCode;Windows右键菜单;注册表。 文章目录 前言一、工程环境二、配置流程1.右键文件打开2.右键文件夹打开3.右键空白处打开文件夹 三、测试总结 前言 安装 VSCode 时没有注意,实际使用的时候发现 VSCode 在 Windows 菜单栏…

vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能

vxe-table vue 表格复选框多选数据&#xff0c;实现快捷键 Shift 批量选择功能 查看官网&#xff1a;https://vxetable.cn 效果 代码 通过 checkbox-config.isShift 启用批量选中,启用后按住快捷键和鼠标批量选取 <template><div><vxe-grid v-bind"gri…

Android Framework预装traceroute执行文件到system/bin下

文章目录 Android SDK中寻找traceroute代码内置traceroute到SDK中traceroute参数说明-I 参数&#xff08;使用 ICMP Echo 请求&#xff09;-T 参数&#xff08;使用 TCP SYN 包&#xff09; 相关文章 Android SDK中寻找traceroute代码 设备使用的是Android 11&#xff0c;在/s…

生信服务器 | 做生信为什么推荐使用Linux服务器?

原文链接&#xff1a;生信服务器 | 做生信为什么推荐使用Linux服务器&#xff1f; 一、 做生信为什么推荐使用服务器&#xff1f; 大家好&#xff0c;我是小杜。在做生信分析的同学&#xff0c;或是将接触学习生信分析的同学&#xff0c;<font style"color:rgb(53, 1…

react-pdf(pdfjs-dist)如何兼容老浏览器(chrome 49)

之前都是使用react-pdf来渲染pdf文件&#xff0c;这次有个需求是要兼容xp环境&#xff0c;xp上chrome最高支持到49&#xff0c;虽然说iframe或者embed都可以实现预览pdf&#xff0c;但为了后续的定制化需求&#xff0c;还是需要使用js库来渲染。 chrome 49测试环境 能用的测试…

RKNN开发环境搭建2-RKNN Model Zoo 环境搭建

目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程.   本…

20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题

20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题 2025/6/9 20:54 缘起&#xff0c;为了跨网段推流&#xff0c;千辛万苦配置好了网络参数。 但是命令iptables -t filter -F tetherctrl_FORWARD可以在调试串口/DEBUG口正确执行。…

21-Oracle 23 ai-Automatic SQL Plan Management(SPM)

小伙伴们&#xff0c;有没有迁移数据库完毕后或是突然某一天在同一个实例上同样的SQL&#xff0c; 性能不一样了、业务反馈卡顿、业务超时等各种匪夷所思的现状。 于是SPM定位开始&#xff0c;OCM考试中SPM必考。 其他的AWR、ASH、SQLHC、SQLT、SQL profile等换作下一个话题…

性能优化中,多面体模型基本原理

1&#xff09;多面体编译技术是一种基于多面体模型的程序分析和优化技术&#xff0c;它将程序 中的语句实例、访问关系、依赖关系和调度等信息映射到多维空间中的几何对 象&#xff0c;通过对这些几何对象进行几何操作和线性代数计算来进行程序的分析和优 化。 其中&#xff0…

【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战

🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…

二维数组 行列混淆区分 js

二维数组定义 行 row&#xff1a;是“横着的一整行” 列 column&#xff1a;是“竖着的一整列” 在 JavaScript 里访问二维数组 grid[i][j] 表示 第i行第j列的元素 let grid [[1, 2, 3], // 第0行[4, 5, 6], // 第1行[7, 8, 9] // 第2行 ];// grid[i][j] 表示 第i行第j列的…

HTML版英语学习系统

HTML版英语学习系统 这是一个完全免费、无需安装、功能完整的英语学习工具&#xff0c;使用HTML CSS JavaScript实现。 功能 文本朗读练习 - 输入英文文章&#xff0c;系统朗读帮助练习听力和发音&#xff0c;适合跟读练习&#xff0c;模仿学习&#xff1b;实时词典查询 - 双…

【threejs】每天一个小案例讲解:创建基本的3D场景

代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone&#xff0c;无需安装依赖&#xff0c;直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景&#xff08;Scene&#xff09; 使用 THREE.Scene(…

C#中用于控制自定义特性(Attribute)

我们来详细解释一下 [AttributeUsage(AttributeTargets.Class, AllowMultiple false, Inherited false)] 这个 C# 属性。 在 C# 中&#xff0c;Attribute&#xff08;特性&#xff09;是一种用于向程序元素&#xff08;如类、方法、属性等&#xff09;添加元数据的机制。Attr…

2025 后端自学UNIAPP【项目实战:旅游项目】7、景点详情页面【完结】

1、获取景点详情的请求【my_api.js】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http(/login/getWXSessionKey, {code,avatar}); };//…

项目进度管理软件是什么?项目进度管理软件有哪些核心功能?

无论是建筑施工、软件开发&#xff0c;还是市场营销活动&#xff0c;项目往往涉及多个团队、大量资源和严格的时间表。如果没有一个系统化的工具来跟踪和管理这些元素&#xff0c;项目很容易陷入混乱&#xff0c;导致进度延误、成本超支&#xff0c;甚至失败。 项目进度管理软…

JUC并发编程(二)Monitor/自旋/轻量级/锁膨胀/wait/notify/锁消除

目录 一 基础 1 概念 2 卖票问题 3 转账问题 二 锁机制与优化策略 0 Monitor 1 轻量级锁 2 锁膨胀 3 自旋 4 偏向锁 5 锁消除 6 wait /notify 7 sleep与wait的对比 8 join原理 一 基础 1 概念 临界区 一段代码块内如果存在对共享资源的多线程读写操作&#xf…

深入理解 C++ 左值右值、std::move 与函数重载中的参数传递

在 C 编程中&#xff0c;左值和右值的概念以及std::move的使用&#xff0c;常常让开发者感到困惑。特别是在函数重载场景下&#xff0c;如何合理利用这些特性来优化代码性能、确保语义正确&#xff0c;更是一个值得深入探讨的话题。 在开始之前&#xff0c;先提出几个问题&…

java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟

众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了&#xff0c;延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp &#xff0c;边缘服务器拉流推送到云服务器 …