From 99e91637603db7218093e3b08d3ccfbcb81cbd22 Mon Sep 17 00:00:00 2001 From: LowaSC Date: Wed, 19 Feb 2025 21:23:47 +0300 Subject: [PATCH] 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 --- ComConfigCopy.py | 297 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 251 insertions(+), 46 deletions(-) diff --git a/ComConfigCopy.py b/ComConfigCopy.py index ea11314..aded353 100644 --- a/ComConfigCopy.py +++ b/ComConfigCopy.py @@ -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 - self.connection = None - self.interactive_text.append_info("[INFO] Соединение закрыто.") - self.update_status_bar() # Обновляем статус бар + finally: + self.connection = None + self.interactive_text.append_info("[INFO] Соединение закрыто.") + 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,55 +1169,144 @@ 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: + break + + if self.execution_paused: + time.sleep(0.1) + continue + + cmd = self.commands[self.current_command_index] + try: + success, response = 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") + + self.current_command_index += 1 + self.after(0, self.update_progress) + time.sleep(1) # Задержка между командами + + except Exception as e: + self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n") + 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.reset_execution_buttons() self.append_file_exec_text("[INFO] Выполнение остановлено.\n") - return - - if self.execution_paused: - self.after(1000, self.execute_next_command) - return - - if self.current_command_index < len(self.commands): - cmd = self.commands[self.current_command_index] - try: - success, response = 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 - ) - - self.current_command_index += 1 - # Планируем выполнение следующей команды - self.after(1000, self.execute_next_command) - - except Exception as e: - self.append_file_exec_text(f"[ERROR] Ошибка при выполнении команды: {str(e)}\n") - self.reset_execution_buttons() else: self.append_file_exec_text("[INFO] Выполнение завершено.\n") - self.reset_execution_buttons() + self.reset_execution_buttons() def start_execution(self): - if not self.execution_thread or not self.execution_thread.is_alive(): - self.start_button.config(state="disabled") - self.pause_button.config(state="normal") - self.stop_button.config(state="normal") + 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") 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: