Sfoglia il codice sorgente

修改了上位机

codex/plsr-2026-minimal
ywh 3 settimane fa
parent
commit
ee75c7e18d
4 ha cambiato i file con 106 aggiunte e 4 eliminazioni
  1. BIN
      HostComputer/__pycache__/plsr_control_panel.cpython-314.pyc
  2. BIN
      HostComputer/__pycache__/plsr_modbus_product_test.cpython-314.pyc
  3. +101
    -4
      HostComputer/plsr_control_panel.py
  4. +5
    -0
      HostComputer/plsr_modbus_product_test.py

BIN
HostComputer/__pycache__/plsr_control_panel.cpython-314.pyc Vedi File


BIN
HostComputer/__pycache__/plsr_modbus_product_test.cpython-314.pyc Vedi File


+ 101
- 4
HostComputer/plsr_control_panel.py Vedi File

@@ -6,6 +6,7 @@ import argparse
import queue import queue
import threading import threading
import time import time
from datetime import datetime
import tkinter as tk import tkinter as tk
import tkinter.font as tkfont import tkinter.font as tkfont
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
@@ -48,7 +49,9 @@ from plsr_modbus_product_test import (


BAUD_RATE = 9600 BAUD_RATE = 9600
SERIAL_TIMEOUT_SECONDS = 0.8 SERIAL_TIMEOUT_SECONDS = 0.8
STATUS_POLL_SECONDS = 0.25
DEFAULT_STATUS_POLL_MS = 250
MIN_STATUS_POLL_MS = 1
MAX_STATUS_POLL_MS = 3_600_000
SEGMENT_COUNT = 10 SEGMENT_COUNT = 10
SEGMENT_STRIDE = 0x10 SEGMENT_STRIDE = 0x10
MAX_FREQUENCY_HZ = 100_000 MAX_FREQUENCY_HZ = 100_000
@@ -171,10 +174,25 @@ class SerialWorker(threading.Thread):
self.stop_event = threading.Event() self.stop_event = threading.Event()
self.client = None self.client = None
self.next_status_poll = 0.0 self.next_status_poll = 0.0
self.polling_enabled = True
self.poll_interval_seconds = DEFAULT_STATUS_POLL_MS / 1000.0
self.log_callback = None


def submit(self, command, **payload): def submit(self, command, **payload):
self.commands.put((command, payload)) self.commands.put((command, payload))


def configure_polling(self, enabled, interval_ms):
self.polling_enabled = bool(enabled)
self.poll_interval_seconds = interval_ms / 1000.0
self.next_status_poll = time.monotonic() + self.poll_interval_seconds

def set_log_callback(self, callback):
self.log_callback = callback

def _log(self, message):
if self.log_callback is not None:
self.log_callback("%s %s" % (datetime.now().strftime("%H:%M:%S.%f")[:-3], message))

def close(self): def close(self):
self.stop_event.set() self.stop_event.set()
self.commands.put(("shutdown", {})) self.commands.put(("shutdown", {}))
@@ -228,6 +246,7 @@ class SerialWorker(threading.Thread):
SERIAL_TIMEOUT_SECONDS, SERIAL_TIMEOUT_SECONDS,
) )
client.__enter__() client.__enter__()
client.log_callback = self._log
self.client = client self.client = client
self.next_status_poll = 0.0 self.next_status_poll = 0.0
self._emit( self._emit(
@@ -298,14 +317,14 @@ class SerialWorker(threading.Thread):
diagnostic = read_diagnostic(self._require_client()) diagnostic = read_diagnostic(self._require_client())
self._emit("status", status=status) self._emit("status", status=status)
self._emit("diagnostic", diagnostic=diagnostic) self._emit("diagnostic", diagnostic=diagnostic)
self.next_status_poll = time.monotonic() + STATUS_POLL_SECONDS
self.next_status_poll = time.monotonic() + self.poll_interval_seconds
except (OSError, serial.SerialException) as error: except (OSError, serial.SerialException) as error:
self._handle_connection_error("状态轮询", error) self._handle_connection_error("状态轮询", error)
except (TestFailure, ModbusException, RuntimeError) as error: except (TestFailure, ModbusException, RuntimeError) as error:
self._emit( self._emit(
"error", operation="状态轮询", message=str(error), modal=False "error", operation="状态轮询", message=str(error), modal=False
) )
self.next_status_poll = time.monotonic() + 1.0
self.next_status_poll = time.monotonic() + self.poll_interval_seconds


def run(self): def run(self):
try: try:
@@ -322,7 +341,8 @@ class SerialWorker(threading.Thread):
self._handle_command(command, payload) self._handle_command(command, payload)


if ( if (
self.client is not None
self.polling_enabled
and self.client is not None
and time.monotonic() >= self.next_status_poll and time.monotonic() >= self.next_status_poll
): ):
self._poll_status() self._poll_status()
@@ -341,10 +361,15 @@ class PlsrControlPanel:
self.connected = False self.connected = False
self.operation_pending = False self.operation_pending = False
self.worker = SerialWorker() self.worker = SerialWorker()
self.worker.set_log_callback(self._queue_log)
self.worker.start() self.worker.start()
self.log_window = None
self.log_text = None


self.port_var = tk.StringVar() self.port_var = tk.StringVar()
self.slave_var = tk.StringVar(value="1") self.slave_var = tk.StringVar(value="1")
self.polling_enabled_var = tk.BooleanVar(value=True)
self.poll_interval_var = tk.StringVar(value=str(DEFAULT_STATUS_POLL_MS))
self.connection_var = tk.StringVar(value="未连接") self.connection_var = tk.StringVar(value="未连接")
self.footer_var = tk.StringVar(value="请选择串口并连接") self.footer_var = tk.StringVar(value="请选择串口并连接")
self.status_vars = { self.status_vars = {
@@ -410,6 +435,21 @@ class PlsrControlPanel:
connection, text="连接", command=self._toggle_connection connection, text="连接", command=self._toggle_connection
) )
self.connect_button.grid(row=0, column=8, padx=8, pady=8) self.connect_button.grid(row=0, column=8, padx=8, pady=8)

polling = ttk.LabelFrame(connection, text="状态轮询")
polling.grid(row=1, column=0, columnspan=9, padx=8, pady=(0, 6), sticky="ew")
ttk.Checkbutton(
polling, text="启动轮询", variable=self.polling_enabled_var,
command=self._apply_polling_settings,
).grid(row=0, column=0, padx=(8, 4), pady=6)
ttk.Label(polling, text="轮询间隔").grid(row=0, column=1, padx=(12, 4), pady=6)
ttk.Entry(polling, textvariable=self.poll_interval_var, width=9).grid(
row=0, column=2, padx=4, pady=6
)
ttk.Label(polling, text="ms(最小 1 ms)").grid(
row=0, column=3, padx=(4, 8), pady=6
)

self.connection_label = ttk.Label( self.connection_label = ttk.Label(
connection, connection,
textvariable=self.connection_var, textvariable=self.connection_var,
@@ -420,6 +460,10 @@ class PlsrControlPanel:
self.connection_label.grid(row=0, column=7, padx=8, pady=8, sticky="e") self.connection_label.grid(row=0, column=7, padx=8, pady=8, sticky="e")
self.connection_widgets = [self.port_box, refresh_button, slave_entry] self.connection_widgets = [self.port_box, refresh_button, slave_entry]


ttk.Button(connection, text="通信日志", command=self._show_log_window).grid(
row=0, column=9, padx=8, pady=8
)

self._build_runtime_status() self._build_runtime_status()


notebook = ttk.Notebook(self.root) notebook = ttk.Notebook(self.root)
@@ -648,6 +692,44 @@ class PlsrControlPanel:
elif not ports: elif not ports:
self.port_var.set("") self.port_var.set("")


def _queue_log(self, message):
self.worker.results.put(("log", {"message": message}))

def _show_log_window(self):
if self.log_window is not None and self.log_window.winfo_exists():
self.log_window.deiconify()
self.log_window.lift()
return
self.log_window = tk.Toplevel(self.root)
self.log_window.title("Modbus 通信日志")
self.log_window.geometry("760x420")
self.log_text = tk.Text(self.log_window, wrap="none", state="disabled")
self.log_text.pack(fill="both", expand=True, padx=8, pady=8)
ttk.Button(self.log_window, text="清空日志", command=self._clear_log).pack(pady=(0, 8))

def _clear_log(self):
if self.log_text is not None:
self.log_text.configure(state="normal")
self.log_text.delete("1.0", "end")
self.log_text.configure(state="disabled")

def _polling_settings(self):
interval_ms = parse_integer(
self.poll_interval_var.get(), "轮询间隔", MIN_STATUS_POLL_MS, MAX_STATUS_POLL_MS
)
return self.polling_enabled_var.get(), interval_ms

def _apply_polling_settings(self):
try:
enabled, interval_ms = self._polling_settings()
except ValueError as error:
messagebox.showerror("轮询设置", str(error))
return
self.worker.configure_polling(enabled, interval_ms)
self.footer_var.set(
"轮询已启动,间隔 %d ms" % interval_ms if enabled else "轮询已停止"
)

def _set_connected(self, connected, description=""): def _set_connected(self, connected, description=""):
self.connected = connected self.connected = connected
self.connect_button.configure( self.connect_button.configure(
@@ -715,6 +797,15 @@ class PlsrControlPanel:
self._update_action_state() self._update_action_state()
self.connection_var.set("正在连接 %s" % port) self.connection_var.set("正在连接 %s" % port)
self.footer_var.set("正在打开串口并读取参数") self.footer_var.set("正在打开串口并读取参数")
try:
enabled, interval_ms = self._polling_settings()
except ValueError as error:
self.connect_button.configure(state="normal")
self.operation_pending = False
self._update_action_state()
messagebox.showerror("轮询设置", str(error))
return
self.worker.configure_polling(enabled, interval_ms)
self.worker.submit("connect", port=port, slave=slave) self.worker.submit("connect", port=port, slave=slave)


def _common_words(self): def _common_words(self):
@@ -985,6 +1076,12 @@ class PlsrControlPanel:
self._show_status(payload["status"]) self._show_status(payload["status"])
elif event == "diagnostic": elif event == "diagnostic":
self._show_diagnostic(payload["diagnostic"]) self._show_diagnostic(payload["diagnostic"])
elif event == "log":
if self.log_text is not None and self.log_text.winfo_exists():
self.log_text.configure(state="normal")
self.log_text.insert("end", payload["message"] + "\n")
self.log_text.see("end")
self.log_text.configure(state="disabled")
elif event == "operation": elif event == "operation":
self._finish_operation(payload["message"]) self._finish_operation(payload["message"])
elif event == "error": elif event == "error":


+ 5
- 0
HostComputer/plsr_modbus_product_test.py Vedi File

@@ -504,6 +504,7 @@ class RtuClient:
self.frame_gap_seconds = 3.5 * self.character_seconds self.frame_gap_seconds = 3.5 * self.character_seconds
self.serial_port = None self.serial_port = None
self.last_request_finished = 0.0 self.last_request_finished = 0.0
self.log_callback = None


def __enter__(self): def __enter__(self):
self.serial_port = serial.Serial( self.serial_port = serial.Serial(
@@ -534,6 +535,8 @@ class RtuClient:
self.serial_port.reset_input_buffer() self.serial_port.reset_input_buffer()
self.serial_port.write(request) self.serial_port.write(request)
self.serial_port.flush() self.serial_port.flush()
if hasattr(self, "log_callback") and self.log_callback is not None:
self.log_callback("TX %s" % request.hex(" "))


response = bytearray() response = bytearray()
deadline = time.monotonic() + self.timeout deadline = time.monotonic() + self.timeout
@@ -564,6 +567,8 @@ class RtuClient:
expected_length = 8 expected_length = 8


self.last_request_finished = time.monotonic() self.last_request_finished = time.monotonic()
if self.log_callback is not None:
self.log_callback("RX %s" % bytes(response).hex(" "))
if not response: if not response:
raise RtuTimeout("no Modbus response to %s" % request.hex(" ")) raise RtuTimeout("no Modbus response to %s" % request.hex(" "))
if expected_length is None: if expected_length is None:


Caricamento…
Annulla
Salva