Add advanced port monitoring and execution progress tracking

- Implement real-time COM port state monitoring with automatic reconnection
- Add connection indicator with dynamic status updates
- Create progress bar and timer for file command execution
- Enhance execution control with detailed progress tracking
- Implement thread-safe port monitoring and execution mechanisms
- Improve error handling and user feedback during command execution
This commit is contained in:
2025-02-19 21:23:47 +03:00
parent b573de9166
commit 99e9163760

View File

@@ -21,6 +21,7 @@ from tkinter import (
END,
BOTH,
LEFT,
RIGHT,
X,
W,
filedialog,
@@ -765,6 +766,15 @@ class SerialAppGUI(tk.Tk):
self.execution_paused = False
self.execution_stop = False
# Добавляем атрибуты для таймера и прогресса
self.start_time = 0
self.elapsed_time = 0
self.timer_running = False
# Добавляем атрибут для отслеживания состояния COM-порта
self.port_monitor_thread = None
self.port_monitoring = False
# Инициализация проверки обновлений
self.update_checker = UpdateChecker(
VERSION,
@@ -966,23 +976,71 @@ class SerialAppGUI(tk.Tk):
self.connection = create_connection(self.settings)
if self.connection:
self.interactive_text.append_info("[INFO] Подключение установлено.")
self.update_status_bar() # Обновляем статус бар
# Запускаем мониторинг состояния порта
self.start_port_monitoring()
self.update_status_bar()
else:
self.interactive_text.append_error("[ERROR] Не удалось установить соединение.")
self.update_status_bar()
# Отключение от устройства
def disconnect_device(self):
if self.connection:
try:
# Останавливаем мониторинг перед закрытием соединения
self.stop_port_monitoring()
self.connection.close()
except Exception:
pass
finally:
self.connection = None
self.interactive_text.append_info("[INFO] Соединение закрыто.")
self.update_status_bar() # Обновляем статус бар
self.update_status_bar()
else:
messagebox.showinfo("Информация", "Соединение не установлено.")
def start_port_monitoring(self):
"""Запуск мониторинга состояния COM-порта"""
if not self.port_monitor_thread or not self.port_monitor_thread.is_alive():
self.port_monitoring = True
self.port_monitor_thread = threading.Thread(target=self.monitor_port_state, daemon=True)
self.port_monitor_thread.start()
def stop_port_monitoring(self):
"""Остановка мониторинга состояния COM-порта"""
self.port_monitoring = False
if self.port_monitor_thread and self.port_monitor_thread.is_alive():
self.port_monitor_thread.join(timeout=1.0)
def monitor_port_state(self):
"""Мониторинг состояния COM-порта"""
while self.port_monitoring and self.connection:
try:
# Проверяем, что порт всё ещё открыт и отвечает
if not self.connection.is_open:
self.connection.open()
# Если порт отвечает, обновляем статус
self.after(0, self.update_connection_indicator, True)
except (SerialException, OSError):
# Если возникла ошибка, помечаем порт как отключенный
self.after(0, self.update_connection_indicator, False)
# Пытаемся переподключиться
try:
if self.connection:
self.connection.close()
except:
pass
self.connection = None
break
time.sleep(1) # Проверяем состояние каждую секунду
def update_connection_indicator(self, is_connected):
"""Обновление индикатора подключения COM-порта"""
if is_connected:
self.connection_indicator.configure(text="", foreground='green')
else:
self.connection_indicator.configure(text="", foreground='red')
# Отправка команды
def send_command(self):
if not self.connection:
@@ -1049,6 +1107,23 @@ class SerialAppGUI(tk.Tk):
self.stop_button = ttk.Button(control_frame, text="⏹ Остановить", command=self.stop_execution, state="disabled")
self.stop_button.pack(side=LEFT, padx=5)
# Создаем фрейм для индикатора прогресса и таймера
progress_frame = ttk.Frame(frame)
progress_frame.pack(fill=X, pady=5, padx=5)
# Добавляем индикатор прогресса
progress_label_frame = ttk.Frame(progress_frame)
progress_label_frame.pack(fill=X)
self.progress_label = ttk.Label(progress_label_frame, text="Прогресс: 0/0 команд")
self.progress_label.pack(side=LEFT, padx=5)
self.timer_label = ttk.Label(progress_label_frame, text="Время: 00:00:00")
self.timer_label.pack(side=RIGHT, padx=5)
self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate')
self.progress_bar.pack(fill=X, pady=5)
# Используем новый TerminalWidget вместо CustomText
self.file_exec_text = TerminalWidget(frame, height=15)
self.file_exec_text.pack(fill=BOTH, expand=True, padx=5, pady=5)
@@ -1079,6 +1154,9 @@ class SerialAppGUI(tk.Tk):
return
if not self.connection:
self.connection = create_connection(self.settings)
if self.connection:
# Запускаем мониторинг при новом подключении
self.start_port_monitoring()
if not self.connection:
self.append_file_exec_text("[ERROR] Не удалось установить соединение.\n")
return
@@ -1091,24 +1169,38 @@ class SerialAppGUI(tk.Tk):
self.execution_paused = False
self.execution_stop = False
# Запускаем выполнение команд
self.execute_next_command()
# Инициализируем прогресс бар
self.progress_bar['value'] = 0
self.update_progress()
# Запускаем таймер
self.start_time = time.time()
self.elapsed_time = 0
self.timer_running = True
self.update_timer()
# Запускаем выполнение команд в отдельном потоке
threading.Thread(target=self.command_execution_thread, daemon=True).start()
except Exception as e:
self.append_file_exec_text(f"[ERROR] Ошибка при чтении файла: {str(e)}\n")
self.reset_execution_buttons()
def execute_next_command(self):
def command_execution_thread(self):
"""Отдельный поток для выполнения команд"""
copy_mode = self.settings.get("copy_mode", "line")
block_size = self.settings.get("block_size", 15)
if copy_mode == "line":
# Построчный режим
while self.current_command_index < len(self.commands):
if self.execution_stop:
self.reset_execution_buttons()
self.append_file_exec_text("[INFO] Выполнение остановлено.\n")
return
break
if self.execution_paused:
self.after(1000, self.execute_next_command)
return
time.sleep(0.1)
continue
if self.current_command_index < len(self.commands):
cmd = self.commands[self.current_command_index]
try:
success, response = send_command_and_process_response(
@@ -1122,24 +1214,99 @@ class SerialAppGUI(tk.Tk):
is_gui=True
)
if not success:
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
self.current_command_index += 1
# Планируем выполнение следующей команды
self.after(1000, self.execute_next_command)
self.after(0, self.update_progress)
time.sleep(1) # Задержка между командами
except Exception as e:
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n")
self.reset_execution_buttons()
break
else:
# Блочный режим
blocks = generate_command_blocks(self.commands, block_size)
total_blocks = len(blocks)
current_block = 0
while current_block < total_blocks:
if self.execution_stop:
break
if self.execution_paused:
time.sleep(0.1)
continue
block = blocks[current_block]
try:
self.append_file_exec_text(f"[CMD] Отправка блока команд:\n{block}\n")
success, response = send_command_and_process_response(
self.connection,
block,
self.settings.get("timeout", 10),
max_attempts=3,
log_callback=self.append_file_exec_text,
login=self.settings.get("login"),
password=self.settings.get("password"),
is_gui=True
)
if not success or (response and '^' in response):
self.append_file_exec_text("[WARNING] Ошибка при выполнении блока команд. Отправляю команды по отдельности...\n")
# Отправляем команды блока по отдельности
for cmd in block.splitlines():
if self.execution_stop:
break
if cmd.strip():
success, resp = send_command_and_process_response(
self.connection,
cmd,
self.settings.get("timeout", 10),
max_attempts=3,
log_callback=self.append_file_exec_text,
login=self.settings.get("login"),
password=self.settings.get("password"),
is_gui=True
)
if not success:
self.append_file_exec_text(f"[ERROR] Не удалось выполнить команду: {cmd}\n")
# Обновляем прогресс на основе количества выполненных блоков
current_block += 1
self.current_command_index = (current_block * 100) // total_blocks
self.after(0, self.update_progress)
time.sleep(1) # Задержка между блоками
except Exception as e:
self.append_file_exec_text(f"[ERROR] Ошибка при выполнении блока команд: {str(e)}\n")
break
# Завершение выполнения
self.after(0, self.execution_completed)
def execution_completed(self):
"""Обработка завершения выполнения в главном потоке"""
if self.execution_stop:
self.append_file_exec_text("[INFO] Выполнение остановлено.\n")
else:
self.append_file_exec_text("[INFO] Выполнение завершено.\n")
self.reset_execution_buttons()
def start_execution(self):
if not self.execution_thread or not self.execution_thread.is_alive():
if not hasattr(self, 'commands') or not self.commands:
self.execution_thread = threading.Thread(target=self.execute_file_commands, daemon=True)
self.execution_thread.start()
else:
# Если команды уже загружены, просто возобновляем выполнение
self.execution_paused = False
self.execution_stop = False
self.timer_running = True
threading.Thread(target=self.command_execution_thread, daemon=True).start()
self.start_button.config(state="disabled")
self.pause_button.config(state="normal")
self.stop_button.config(state="normal")
self.execution_thread = threading.Thread(target=self.execute_file_commands, daemon=True)
self.execution_thread.start()
def pause_execution(self):
if not self.execution_paused:
@@ -1154,7 +1321,24 @@ class SerialAppGUI(tk.Tk):
def stop_execution(self):
self.execution_stop = True
self.execution_paused = False
self.append_file_exec_text("[INFO] Остановка выполнения...\n")
self.timer_running = False
# Отключаемся от COM-порта
if self.connection:
try:
self.stop_port_monitoring()
self.connection.close()
self.connection = None
self.append_file_exec_text("[INFO] Соединение закрыто.\n")
except Exception as e:
self.append_file_exec_text(f"[ERROR] Ошибка при закрытии соединения: {str(e)}\n")
self.current_command_index = 0 # Сбрасываем индекс текущей команды
self.progress_bar['value'] = 0 # Сбрасываем прогресс-бар
self.update_progress() # Обновляем отображение прогресса
self.append_file_exec_text("[INFO] Выполнение остановлено.\n")
self.reset_execution_buttons() # Сбрасываем состояние кнопок
self.update_status_bar() # Обновляем статус бар
def reset_execution_buttons(self):
self.start_button.config(state="normal")
@@ -1162,6 +1346,28 @@ class SerialAppGUI(tk.Tk):
self.stop_button.config(state="disabled")
self.execution_paused = False
self.execution_stop = False
self.timer_running = False
def update_timer(self):
"""Обновление таймера в главном потоке"""
if self.timer_running:
if not self.execution_paused:
self.elapsed_time = time.time() - self.start_time
hours = int(self.elapsed_time // 3600)
minutes = int((self.elapsed_time % 3600) // 60)
seconds = int(self.elapsed_time % 60)
self.timer_label.config(text=f"Время: {hours:02d}:{minutes:02d}:{seconds:02d}")
self.after(1000, self.update_timer) # Обновление каждую секунду
def update_progress(self):
"""Обновление прогресса в главном потоке"""
if hasattr(self, 'commands'):
total_commands = len(self.commands)
current = self.current_command_index
self.progress_label.config(text=f"Прогресс: {current}/{total_commands} команд")
if total_commands > 0:
progress = (current / total_commands) * 100
self.progress_bar['value'] = progress
# Создание вкладки "Редактор конфигурационного файла"
def create_config_editor_tab(self, frame):
@@ -1551,11 +1757,10 @@ class SerialAppGUI(tk.Tk):
# Обновляем метод update_status_bar
def update_status_bar(self):
# Обновляем индикатор подключения COM порта
if self.connection:
self.connection_indicator.configure(text="", foreground='green')
else:
self.connection_indicator.configure(text="", foreground='red')
"""Обновление статус-бара"""
# Проверяем реальное состояние подключения
is_connected = bool(self.connection and self.connection.is_open)
self.update_connection_indicator(is_connected)
# Обновляем индикатор TFTP сервера
if self.tftp_server and self.tftp_server.running: