<think>我们注意到用户之前提供的C#代码中,通过控制串口的RTS和DTR信号来控制继电器。在Python版本中,我们也应该使用同样的方式(控制RTS和DTR)来切换电流。但是用户反馈在Python版本中没有反应,可能是因为在Python代码中我们使用了发送字符串命令的方式,而没有直接控制串口的RTS和DTR引脚。
因此,我们需要修改Python代码,使用串口的RTS和DTR信号来控制继电器,而不是发送字符串命令。根据用户C#代码,我们看到:
- 使用RTS信号来控制PC的开关(在timer1中通过eRTS和dRTS方法控制)
- 使用DTR信号来控制AC/DC的开关(在timer2中通过eDTR和dDTR方法控制)
在C#代码中,切换电流实际上是通过交替控制DTR信号来实现的(在timer2_Tick方法中,交替设置DTR的高低电平,从而切换电流通路)。
所以,我们需要在Python程序中:
1. 去掉之前发送字符串命令的方式。
2. 改为直接控制串口的RTS和DTR引脚。
同时,根据用户需求,我们需要实现三种功能:通电(可能是开启一个通路)、断电(关闭所有通路)、切换电流(在两个通路之间切换)。在C#代码中,切换电流是通过定时器周期性地改变DTR信号来实现的,但我们这里需要提供手动切换的功能。
因此,我们重新设计:
- 断电:将RTS和DTR都设置为低电平(或者根据实际硬件,可能需要设置为高电平,这取决于继电器模块的触发方式)
- 通电(通路1):例如,设置RTS为高,DTR为低(或者相反,根据硬件决定)
- 切换电流:改变当前通路,例如从通路1切换到通路2,可以通过翻转DTR信号(或者RTS信号)来实现
但是,由于用户没有提供具体的硬件触发逻辑(即高电平触发还是低电平触发,以及每个信号对应的通路),我们假设如下(这是常见的两种方式之一,具体需要根据硬件调整):
方式1(假设):
- 通路1:RTS=高电平, DTR=低电平
- 通路2:RTS=低电平, DTR=高电平
- 断电:RTS=低电平, DTR=低电平
方式2(另一种常见方式):
- 每个信号控制一个独立的继电器,那么:
通路1:打开RTS对应的继电器,关闭DTR对应的继电器
通路2:关闭RTS对应的继电器,打开DTR对应的继电器
断电:两个继电器都关闭
由于用户C#代码中使用了两个定时器分别控制RTS和DTR,我们可以推断硬件上可能是两个独立的信号控制两个继电器。因此,我们可以这样定义:
- 继电器1(由RTS控制):RTS为高电平则闭合,低电平则断开
- 继电器2(由DTR控制):DTR为高电平则闭合,低电平则断开
那么:
- 断电:两个继电器都断开 -> RTS=False, DTR=False
- 通电(通路1):只闭合继电器1 -> RTS=True, DTR=False
- 通电(通路2):只闭合继电器2 -> RTS=False, DTR=True
切换电流的功能就是在通路1和通路2之间切换。
因此,我们修改Python程序如下:
1. 在连接串口后,初始化状态:RTS=False, DTR=False(断电状态)
2. 通电:设置为通路1(RTS=True, DTR=False)
3. 断电:设置为RTS=False, DTR=False
4. 切换电流:
如果当前是通路1,则切换到通路2(RTS=False, DTR=True)
如果当前是通路2,则切换到通路1(RTS=True, DTR=False)
如果当前是断电状态,则切换到通路1
注意:我们使用两个变量来记录当前状态:relay1_state和relay2_state,但也可以用一个变量(如current_path)来记录。
我们修改之前的代码,去掉发送字符串命令的方式,改为直接控制RTS和DTR。
同时,我们保留日志功能,并显示当前通路状态。
修改后的代码:
</think>
您的问题很关键。在C#代码中,您是通过直接控制串口的**RTS(Request to Send)** 和 **DTR(Data Terminal Ready)** 信号线来控制继电器的,而不是发送特定的数据命令。这是许多继电器模块的常见控制方式。
在之前的Python实现中,我错误地假设了命令协议(如发送"ON1"、"OFF"等字符串),而实际上应该直接控制串口的硬件信号线。以下是修正后的Python代码,完全模拟您C#代码中的RTS/DTR控制逻辑:
```python
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import serial
import serial.tools.list_ports
import threading
import time
class RelayControlApp:
def __init__(self, master):
self.master = master
master.title("RS232继电器控制程序 (RTS/DTR信号控制)")
master.geometry("700x600")
master.resizable(True, True)
# 串口连接
self.serial_conn = None
self.is_connected = False
# 状态变量(与C#代码保持一致)
self.timer1_check = False # PC状态标记
self.timer2_check = False # AC/DC状态标记
self.intPCCont = 0 # PC开关计数
self.intACCont = 0 # AC/DC切换计数
self.test_running = False # 测试运行状态
# 创建UI
self.create_widgets()
# 自动扫描可用串口
self.scan_ports()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.master)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 串口设置区域
port_frame = ttk.LabelFrame(main_frame, text="串口设置")
port_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(port_frame, text="端口:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.port_combo = ttk.Combobox(port_frame, width=15)
self.port_combo.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(port_frame, text="波特率:").grid(row=0, column=2, padx=5, pady=5, sticky="e")
self.baud_combo = ttk.Combobox(port_frame, values=["9600", "19200", "38400", "57600", "115200"], width=10)
self.baud_combo.set("9600")
self.baud_combo.grid(row=0, column=3, padx=5, pady=5)
self.scan_btn = ttk.Button(port_frame, text="扫描端口", command=self.scan_ports)
self.scan_btn.grid(row=0, column=4, padx=5, pady=5)
self.connect_btn = ttk.Button(port_frame, text="连接", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=5, padx=5, pady=5)
# 控制参数区域(新增)
param_frame = ttk.LabelFrame(main_frame, text="控制参数")
param_frame.pack(fill=tk.X, padx=5, pady=5)
params = [
("DC开启时间(秒):", "txt_DCOn"),
("DC关闭时间(秒):", "txt_DCOff"),
("PC开启时间(秒):", "txt_OnTime"),
("PC关闭时间(秒):", "txt_OffTime"),
("计数变化间隔:", "txt_CountChange"),
("设置计数次数:", "txt_SetCount"),
("触发延迟(ms):", "txt_trigger")
]
for i, (label, var_name) in enumerate(params):
ttk.Label(param_frame, text=label).grid(row=i//2, column=(i%2)*2, padx=5, pady=5, sticky="e")
entry = ttk.Entry(param_frame, width=10)
entry.grid(row=i//2, column=(i%2)*2+1, padx=5, pady=5)
setattr(self, var_name, entry)
entry.insert(0, "1") # 设置默认值
# 继电器控制区域
control_frame = ttk.LabelFrame(main_frame, text="继电器控制")
control_frame.pack(fill=tk.X, padx=5, pady=5)
# 状态显示
self.status_var = tk.StringVar()
self.status_var.set("状态: 未连接")
status_label = ttk.Label(control_frame, textvariable=self.status_var, font=("Arial", 10, "bold"))
status_label.grid(row=0, column=0, columnspan=3, padx=5, pady=10, sticky="w")
# 计数器显示
ttk.Label(control_frame, text="PC开关计数:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
self.pc_count_var = tk.StringVar()
self.pc_count_var.set("0")
ttk.Label(control_frame, textvariable=self.pc_count_var).grid(row=1, column=1, padx=5, pady=5, sticky="w")
ttk.Label(control_frame, text="AC/DC切换计数:").grid(row=1, column=2, padx=5, pady=5, sticky="e")
self.acdc_count_var = tk.StringVar()
self.acdc_count_var.set("0")
ttk.Label(control_frame, textvariable=self.acdc_count_var).grid(row=1, column=3, padx=5, pady=5, sticky="w")
# 控制按钮
self.power_on_btn = ttk.Button(control_frame, text="通电", command=self.power_on, state=tk.DISABLED)
self.power_on_btn.grid(row=2, column=0, padx=10, pady=10, ipadx=10)
self.power_off_btn = ttk.Button(control_frame, text="断电", command=self.power_off, state=tk.DISABLED)
self.power_off_btn.grid(row=2, column=1, padx=10, pady=10, ipadx=10)
self.switch_btn = ttk.Button(control_frame, text="切换电流", command=self.switch_current, state=tk.DISABLED)
self.switch_btn.grid(row=2, column=2, padx=10, pady=10, ipadx=10)
# 测试控制按钮
test_frame = ttk.Frame(control_frame)
test_frame.grid(row=3, column=0, columnspan=4, pady=10)
self.start_btn = ttk.Button(test_frame, text="开始测试", command=self.start_test, state=tk.DISABLED)
self.start_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = ttk.Button(test_frame, text="停止测试", command=self.stop_test, state=tk.DISABLED)
self.stop_btn.pack(side=tk.LEFT, padx=5)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="操作日志")
log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.log_text = scrolledtext.ScrolledText(log_frame, height=10)
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.log_text.config(state=tk.DISABLED)
# 底部状态栏
self.status_bar = ttk.Label(self.master, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
def log_message(self, message):
"""在日志区域添加消息"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n")
self.log_text.config(state=tk.DISABLED)
self.log_text.see(tk.END) # 滚动到底部
self.status_bar.config(text=message)
def scan_ports(self):
"""扫描可用串口"""
ports = [port.device for port in serial.tools.list_ports.comports()]
self.port_combo['values'] = ports
if ports:
self.port_combo.set(ports[0])
self.log_message(f"找到 {len(ports)} 个串口: {', '.join(ports)}")
else:
self.log_message("未找到可用串口")
def toggle_connection(self):
"""连接或断开串口"""
if self.is_connected:
self.disconnect()
self.connect_btn.config(text="连接")
self.log_message("已断开串口连接")
else:
port = self.port_combo.get()
baud = self.baud_combo.get()
if not port:
messagebox.showwarning("输入错误", "请选择串口")
return
try:
self.serial_conn = serial.Serial(
port=port,
baudrate=int(baud),
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1
)
self.is_connected = True
self.connect_btn.config(text="断开")
self.status_var.set(f"状态: 已连接 ({port})")
self.log_message(f"已连接到 {port}, 波特率 {baud}")
# 启用控制按钮
self.power_on_btn.config(state=tk.NORMAL)
self.power_off_btn.config(state=tk.NORMAL)
self.switch_btn.config(state=tk.NORMAL)
self.start_btn.config(state=tk.NORMAL)
# 初始状态为断电
self.power_off()
except serial.SerialException as e:
messagebox.showerror("连接错误", f"无法打开串口: {str(e)}")
self.log_message(f"连接失败: {str(e)}")
def disconnect(self):
"""断开串口连接"""
if self.is_connected and self.serial_conn:
try:
self.power_off() # 断开前先断电
self.serial_conn.close()
except:
pass
finally:
self.is_connected = False
self.status_var.set("状态: 未连接")
# 禁用控制按钮
self.power_on_btn.config(state=tk.DISABLED)
self.power_off_btn.config(state=tk.DISABLED)
self.switch_btn.config(state=tk.DISABLED)
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.DISABLED)
# 以下是RTS/DTR控制方法(与C#代码一致)
def eRTS(self):
"""设置RTS信号(高电平)"""
if self.is_connected and self.serial_conn:
self.serial_conn.rts = True
self.log_message("RTS信号: 高电平")
def dRTS(self):
"""清除RTS信号(低电平)"""
if self.is_connected and self.serial_conn:
self.serial_conn.rts = False
self.log_message("RTS信号: 低电平")
def eDTR(self):
"""设置DTR信号(高电平)"""
if self.is_connected and self.serial_conn:
self.serial_conn.dtr = True
self.log_message("DTR信号: 高电平")
def dDTR(self):
"""清除DTR信号(低电平)"""
if self.is_connected and self.serial_conn:
self.serial_conn.dtr = False
self.log_message("DTR信号: 低电平")
def power_on(self):
"""通电 - 使用RTS信号"""
if self.is_connected:
self.eRTS()
self.log_message("已通电 (RTS高电平)")
def power_off(self):
"""断电 - 清除所有信号"""
if self.is_connected:
self.dRTS()
self.dDTR()
self.log_message("已断电 (所有信号低电平)")
def switch_current(self):
"""切换电流通路 - 使用DTR信号"""
if self.is_connected:
if self.serial_conn.dtr:
self.dDTR()
self.log_message("已切换到通路1 (DTR低电平)")
else:
self.eDTR()
self.log_message("已切换到通路2 (DTR高电平)")
def start_test(self):
"""开始自动测试(模拟C# timer功能)"""
if not self.is_connected:
messagebox.showwarning("错误", "请先连接串口")
return
# 验证参数
try:
params = {
"DCOn": int(self.txt_DCOn.get()),
"DCOff": int(self.txt_DCOff.get()),
"OnTime": int(self.txt_OnTime.get()),
"OffTime": int(self.txt_OffTime.get()),
"CountChange": int(self.txt_CountChange.get()),
"SetCount": int(self.txt_SetCount.get()),
"trigger": int(self.txt_trigger.get())
}
if any(v <= 0 for v in params.values()):
raise ValueError("所有参数必须大于0")
except ValueError as e:
messagebox.showerror("参数错误", f"无效的参数: {str(e)}")
return
# 初始化状态
self.timer1_check = False
self.timer2_check = False
self.intPCCont = 0
self.intACCont = 0
self.test_running = True
# 更新显示
self.pc_count_var.set("0")
self.acdc_count_var.set("0")
# 禁用参数输入
for entry in [self.txt_DCOn, self.txt_DCOff, self.txt_OnTime,
self.txt_OffTime, self.txt_CountChange, self.txt_SetCount, self.txt_trigger]:
entry.config(state=tk.DISABLED)
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
# 启动测试线程
self.test_thread = threading.Thread(target=self.run_test, args=(params,), daemon=True)
self.test_thread.start()
self.log_message("测试已启动")
def run_test(self, params):
"""执行测试循环(模拟C# timer功能)"""
while self.test_running and self.intPCCont < params["SetCount"]:
# 模拟C#中的timer1_Tick
self.eRTS()
time.sleep(params["trigger"] / 1000) # 转换为秒
self.dRTS()
self.intPCCont += 1
self.pc_count_var.set(str(self.intPCCont))
if not self.timer1_check:
self.timer1_check = True
self.log_message("PC状态: ON")
time.sleep(params["OnTime"])
else:
self.timer1_check = False
self.log_message("PC状态: OFF")
time.sleep(params["OffTime"])
# 模拟C#中的timer2_Tick
if self.intPCCont % params["CountChange"] == 0:
self.intACCont += 1
self.acdc_count_var.set(str(self.intACCont))
if not self.timer2_check:
self.timer2_check = True
self.eDTR()
self.log_message("AC状态: ON")
time.sleep(params["DCOn"])
else:
self.timer2_check = False
self.dDTR()
self.log_message("DC状态: ON")
time.sleep(params["DCOff"])
if self.intPCCont >= params["SetCount"]:
self.log_message("测试完成: 达到设定计数")
else:
self.log_message("测试已停止")
# 重置状态
self.test_running = False
self.power_off()
self.master.after(0, self.enable_controls)
def enable_controls(self):
"""启用控制"""
for entry in [self.txt_DCOn, self.txt_DCOff, self.txt_OnTime,
self.txt_OffTime, self.txt_CountChange, self.txt_SetCount, self.txt_trigger]:
entry.config(state=tk.NORMAL)
self.start_btn.config(state=tk.NORMAL)
self.stop_btn.config(state=tk.DISABLED)
def stop_test(self):
"""停止测试"""
self.test_running = False
self.log_message("测试停止请求已发送")
def on_closing(self):
"""窗口关闭时的处理"""
self.test_running = False
if self.serial_conn and self.serial_conn.is_open:
self.serial_conn.close()
self.master.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = RelayControlApp(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
```
### 关键修改说明
1. **RTS/DTR信号控制**:
- 完全按照您的C#代码实现了四个核心方法:
```python
def eRTS(self): # 设置RTS高电平
def dRTS(self): # 设置RTS低电平
def eDTR(self): # 设置DTR