7 Commits

Author SHA1 Message Date
f45f1bf556 Improve command logging and terminal display formatting
- Simplify command logging messages with concise `[CMD]` prefix
- Remove redundant command attempt counter from log messages
- Modify TerminalWidget to handle `[CMD]` tagged messages
- Enhance command display with consistent formatting and separators
2025-02-19 20:56:25 +03:00
e6766660c6 Add Enter key support for command input
- Bind Enter key to send command in interactive tab
2025-02-18 20:10:51 +03:00
e0705ad6b5 Add advanced TerminalWidget with enhanced text display and formatting
- Implement TerminalWidget class with color-coded message types
- Add support for error, warning, info, and command message styles
- Include automatic scrollbar and text formatting
- Replace CustomText with TerminalWidget in interactive tabs
- Enhance text appending methods with type-specific display
- Improve terminal readability with command separators
2025-02-18 00:35:14 +03:00
c0d8fd8d89 Improve interactive tab buttons with Unicode icons
- Add Unicode icons to the connect, disconnect and send buttons in the interactive tab
2025-02-18 00:11:42 +03:00
ce81100150 Enhance status bar with TFTP server indicator
- Add TFTP server status indicator to the status bar
- Implement tooltip for TFTP server status
- Update status bar update method to reflect TFTP server state
- Improve status bar layout with separate frames for indicators
2025-02-17 19:39:29 +03:00
48c9bd2d40 Bump version to 1.0.2
- Update application version number in ComConfigCopy.py
- Minor version increment to reflect recent changes
2025-02-17 18:35:58 +03:00
e0f64060f5 Add status bar with connection and settings information
- Implement create_status_bar method to add a new status bar
- Add connection indicator with color-coded status
- Create tooltip for connection status details
- Update status bar with current port, baudrate, and copy mode settings
- Integrate status bar updates in connection and settings change events
2025-02-17 18:31:58 +03:00

View File

@@ -38,7 +38,7 @@ import socket
from update_checker import UpdateChecker from update_checker import UpdateChecker
# Версия программы # Версия программы
VERSION = "1.0.1" VERSION = "1.0.2"
# Создаем необходимые папки # Создаем необходимые папки
os.makedirs("Logs", exist_ok=True) os.makedirs("Logs", exist_ok=True)
@@ -286,7 +286,7 @@ def execute_commands_from_file(
max_attempts = 3 max_attempts = 3
attempt = 0 attempt = 0
while attempt < max_attempts: while attempt < max_attempts:
msg = f"\nОтправка команды: {cmd} (Попытка {attempt+1} из {max_attempts})\n" msg = f"[CMD] {cmd}" # Изменено форматирование для команды
if log_callback: if log_callback:
log_callback(msg) log_callback(msg)
serial_connection.write((cmd + "\n").encode()) serial_connection.write((cmd + "\n").encode())
@@ -327,7 +327,7 @@ def execute_commands_from_file(
elif copy_mode == "block": elif copy_mode == "block":
blocks = generate_command_blocks(lines, block_size) blocks = generate_command_blocks(lines, block_size)
for block in blocks: for block in blocks:
msg = f"\nОтправка блока команд:\n{block}\n" msg = f"[CMD] Отправка блока команд:\n{block}" # Изменено форматирование для блока команд
if log_callback: if log_callback:
log_callback(msg) log_callback(msg)
serial_connection.write((block + "\n").encode()) serial_connection.write((block + "\n").encode())
@@ -350,7 +350,7 @@ def execute_commands_from_file(
max_attempts = 3 max_attempts = 3
attempt = 0 attempt = 0
while attempt < max_attempts: while attempt < max_attempts:
sub_msg = f"\nОтправка команды: {cmd} (Попытка {attempt+1} из {max_attempts})\n" sub_msg = f"[CMD] {cmd}" # Изменено форматирование для команды
if log_callback: if log_callback:
log_callback(sub_msg) log_callback(sub_msg)
serial_connection.write((cmd + "\n").encode()) serial_connection.write((cmd + "\n").encode())
@@ -670,6 +670,86 @@ def send_command_and_process_response(
logging.error(msg) logging.error(msg)
return False, None return False, None
# Класс для терминального виджета с расширенной функциональностью
class TerminalWidget(CustomText):
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# Настройка цветов для разных типов сообщений
self.tag_configure("error", foreground="red")
self.tag_configure("warning", foreground="orange")
self.tag_configure("info", foreground="blue")
self.tag_configure("command", foreground="green")
self.tag_configure("separator", foreground="gray")
# Добавляем скроллбар
self.scrollbar = ttk.Scrollbar(self, command=self.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.config(yscrollcommand=self.scrollbar.set)
# Настройка отступов и переносов
self.config(
wrap=tk.WORD,
padx=5,
pady=5,
spacing1=2, # Отступ перед абзацем
spacing2=2, # Межстрочный интервал
spacing3=2 # Отступ после абзаца
)
# Счетчик команд для разделителей
self.command_counter = 0
def append_text(self, text, message_type=None):
"""
Добавление текста с определенным типом сообщения
message_type может быть: 'error', 'warning', 'info', 'command'
"""
# Добавляем текст
if not text.endswith('\n'):
text += '\n'
start_index = self.index(tk.END)
self.insert(tk.END, text)
end_index = self.index(tk.END)
# Применяем тег в зависимости от типа сообщения
if message_type:
self.tag_add(message_type, start_index, end_index)
# Автоматическая прокрутка к концу
self.see(tk.END)
# Обновляем виджет
self.update_idletasks()
def append_error(self, text):
"""Добавление сообщения об ошибке"""
self.append_text(text, "error")
def append_warning(self, text):
"""Добавление предупреждения"""
self.append_text(text, "warning")
def append_info(self, text):
"""Добавление информационного сообщения"""
self.append_text(text, "info")
def append_command(self, text):
"""Добавление команды с разделителем"""
# Добавляем разделитель между командами
if self.command_counter > 0:
self.insert(tk.END, "\n" + "" * 80 + "\n", "separator")
self.command_counter += 1
# Добавляем команду
self.append_text(text, "command")
def clear(self):
"""Очистка терминала"""
self.delete("1.0", tk.END)
self.command_counter = 0
# Основной класс для графического интерфейса # Основной класс для графического интерфейса
class SerialAppGUI(tk.Tk): class SerialAppGUI(tk.Tk):
def __init__(self, settings): def __init__(self, settings):
@@ -700,6 +780,12 @@ class SerialAppGUI(tk.Tk):
self.create_menu() self.create_menu()
self.create_tabs() self.create_tabs()
# Добавляем статус бар
self.create_status_bar()
# Обновляем информацию в статус баре
self.update_status_bar()
# Проверка первого запуска # Проверка первого запуска
self.check_first_run() self.check_first_run()
@@ -763,6 +849,7 @@ class SerialAppGUI(tk.Tk):
# Обработчик изменения настроек # Обработчик изменения настроек
def on_settings_changed(self): def on_settings_changed(self):
self.settings = settings_load() self.settings = settings_load()
self.update_status_bar() # Добавляем обновление статус бара
# Открытие окна настроек # Открытие окна настроек
def open_settings(self): def open_settings(self):
@@ -825,10 +912,25 @@ class SerialAppGUI(tk.Tk):
def create_interactive_tab(self, frame): def create_interactive_tab(self, frame):
control_frame = ttk.Frame(frame) control_frame = ttk.Frame(frame)
control_frame.pack(fill=X, pady=5) control_frame.pack(fill=X, pady=5)
ttk.Button(control_frame, text="Подключиться", command=self.connect_device).pack(side=LEFT, padx=5)
ttk.Button(control_frame, text="Отключиться", command=self.disconnect_device).pack(side=LEFT, padx=5) # Кнопка подключения с иконкой
connect_btn = ttk.Button(
control_frame,
text="⚡ Подключиться", # Unicode символ для "молнии"
command=self.connect_device
)
connect_btn.pack(side=LEFT, padx=5)
# Кнопка отключения с иконкой
disconnect_btn = ttk.Button(
control_frame,
text="✕ Отключиться", # Unicode символ для "крестика"
command=self.disconnect_device
)
disconnect_btn.pack(side=LEFT, padx=5)
self.interactive_text = CustomText(frame, wrap="word", height=20) # Используем новый TerminalWidget вместо CustomText
self.interactive_text = TerminalWidget(frame, height=20)
self.interactive_text.pack(fill=BOTH, expand=True, padx=5, pady=5) self.interactive_text.pack(fill=BOTH, expand=True, padx=5, pady=5)
input_frame = ttk.Frame(frame) input_frame = ttk.Frame(frame)
@@ -836,7 +938,17 @@ class SerialAppGUI(tk.Tk):
ttk.Label(input_frame, text="Команда:").pack(side=LEFT, padx=5) ttk.Label(input_frame, text="Команда:").pack(side=LEFT, padx=5)
self.command_entry = CustomEntry(input_frame, width=50) self.command_entry = CustomEntry(input_frame, width=50)
self.command_entry.pack(side=LEFT, padx=5) self.command_entry.pack(side=LEFT, padx=5)
ttk.Button(input_frame, text="Отправить", command=self.send_command).pack(side=LEFT, padx=5)
# Привязываем обработчик нажатия Enter к полю ввода
self.command_entry.bind('<Return>', lambda event: self.send_command())
# Кнопка отправки с иконкой
send_btn = ttk.Button(
input_frame,
text="➤ Отправить", # Unicode символ для "стрелки"
command=self.send_command
)
send_btn.pack(side=LEFT, padx=5)
# Подключение к устройству # Подключение к устройству
def connect_device(self): def connect_device(self):
@@ -848,9 +960,10 @@ class SerialAppGUI(tk.Tk):
return return
self.connection = create_connection(self.settings) self.connection = create_connection(self.settings)
if self.connection: if self.connection:
self.append_interactive_text("[INFO] Подключение установлено.\n") self.interactive_text.append_info("[INFO] Подключение установлено.")
self.update_status_bar() # Обновляем статус бар
else: else:
self.append_interactive_text("[ERROR] Не удалось установить соединение.\n") self.interactive_text.append_error("[ERROR] Не удалось установить соединение.")
# Отключение от устройства # Отключение от устройства
def disconnect_device(self): def disconnect_device(self):
@@ -860,7 +973,8 @@ class SerialAppGUI(tk.Tk):
except Exception: except Exception:
pass pass
self.connection = None self.connection = None
self.append_interactive_text("[INFO] Соединение закрыто.\n") self.interactive_text.append_info("[INFO] Соединение закрыто.")
self.update_status_bar() # Обновляем статус бар
else: else:
messagebox.showinfo("Информация", "Соединение не установлено.") messagebox.showinfo("Информация", "Соединение не установлено.")
@@ -872,7 +986,8 @@ class SerialAppGUI(tk.Tk):
cmd = self.command_entry.get().strip() cmd = self.command_entry.get().strip()
if not cmd: if not cmd:
return return
self.append_interactive_text(f"[INFO] Отправка команды: {cmd}\n") self.interactive_text.append_command(f"[CMD] {cmd}")
self.command_entry.delete(0, END) # Очищаем поле ввода
threading.Thread(target=self.process_command, args=(cmd,), daemon=True).start() threading.Thread(target=self.process_command, args=(cmd,), daemon=True).start()
# Обработка команды # Обработка команды
@@ -889,15 +1004,22 @@ class SerialAppGUI(tk.Tk):
is_gui=True is_gui=True
) )
except SerialException as e: except SerialException as e:
self.append_interactive_text(f"[ERROR] Ошибка при отправке команды: {e}\n") self.interactive_text.append_error(f"[ERROR] Ошибка при отправке команды: {e}")
logging.error(f"Ошибка отправки команды: {e}", exc_info=True) logging.error(f"Ошибка отправки команды: {e}", exc_info=True)
except Exception as e: except Exception as e:
self.append_interactive_text(f"[ERROR] Неизвестная ошибка: {e}\n") self.interactive_text.append_error(f"[ERROR] Неизвестная ошибка: {e}")
logging.error(f"Неизвестная ошибка: {e}", exc_info=True) logging.error(f"Неизвестная ошибка: {e}", exc_info=True)
# Добавление текста в текстовое поле # Добавление текста в текстовое поле
def append_interactive_text(self, text): def append_interactive_text(self, text):
append_text_to_widget(self.interactive_text, text) if "[ERROR]" in text:
self.interactive_text.append_error(text)
elif "[WARNING]" in text or "[WARN]" in text:
self.interactive_text.append_warning(text)
elif "[INFO]" in text:
self.interactive_text.append_info(text)
else:
self.interactive_text.append_text(text)
# Создание вкладки "Выполнить команды из файла" # Создание вкладки "Выполнить команды из файла"
def create_file_exec_tab(self, frame): def create_file_exec_tab(self, frame):
@@ -908,9 +1030,23 @@ class SerialAppGUI(tk.Tk):
CustomEntry(file_frame, textvariable=self.file_exec_var, width=40).pack(side=LEFT, padx=5) CustomEntry(file_frame, textvariable=self.file_exec_var, width=40).pack(side=LEFT, padx=5)
ttk.Button(file_frame, text="Выбрать", command=self.select_config_file_fileexec).pack(side=LEFT, padx=5) ttk.Button(file_frame, text="Выбрать", command=self.select_config_file_fileexec).pack(side=LEFT, padx=5)
ttk.Button(frame, text="Выполнить команды", command=self.execute_file_commands).pack(pady=5) ttk.Button(frame, text="Выполнить команды", command=self.execute_file_commands).pack(pady=5)
self.file_exec_text = CustomText(frame, wrap="word", height=15)
# Используем новый TerminalWidget вместо CustomText
self.file_exec_text = TerminalWidget(frame, height=15)
self.file_exec_text.pack(fill=BOTH, expand=True, padx=5, pady=5) self.file_exec_text.pack(fill=BOTH, expand=True, padx=5, pady=5)
def append_file_exec_text(self, text):
if "[ERROR]" in text:
self.file_exec_text.append_error(text)
elif "[WARNING]" in text or "[WARN]" in text:
self.file_exec_text.append_warning(text)
elif "[INFO]" in text:
self.file_exec_text.append_info(text)
elif "[CMD]" in text: # Добавляем обработку команд
self.file_exec_text.append_command(text)
else:
self.file_exec_text.append_text(text)
# Выбор файла конфигурации для выполнения команд # Выбор файла конфигурации для выполнения команд
def select_config_file_fileexec(self): def select_config_file_fileexec(self):
select_config_file(self, self.file_exec_var) select_config_file(self, self.file_exec_var)
@@ -944,9 +1080,6 @@ class SerialAppGUI(tk.Tk):
daemon=True, daemon=True,
).start() ).start()
def append_file_exec_text(self, text):
append_text_to_widget(self.file_exec_text, text)
# Создание вкладки "Редактор конфигурационного файла" # Создание вкладки "Редактор конфигурационного файла"
def create_config_editor_tab(self, frame): def create_config_editor_tab(self, frame):
top_frame = ttk.Frame(frame) top_frame = ttk.Frame(frame)
@@ -1054,13 +1187,9 @@ class SerialAppGUI(tk.Tk):
log_frame = ttk.LabelFrame(tftp_frame, text="Лог сервера") log_frame = ttk.LabelFrame(tftp_frame, text="Лог сервера")
log_frame.pack(fill=BOTH, expand=True, padx=5, pady=5) log_frame.pack(fill=BOTH, expand=True, padx=5, pady=5)
self.tftp_log_text = CustomText(log_frame, wrap=tk.WORD, height=10) # Используем новый TerminalWidget вместо CustomText
self.tftp_log_text = TerminalWidget(log_frame, height=10)
self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5) self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5)
# Добавляем скроллбар для лога
scrollbar = ttk.Scrollbar(self.tftp_log_text, command=self.tftp_log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tftp_log_text.config(yscrollcommand=scrollbar.set)
# Статус передач # Статус передач
transfers_frame = ttk.LabelFrame(tftp_frame, text="Активные передачи") transfers_frame = ttk.LabelFrame(tftp_frame, text="Активные передачи")
@@ -1149,6 +1278,9 @@ class SerialAppGUI(tk.Tk):
# Запускаем периодическое обновление информации о передачах # Запускаем периодическое обновление информации о передачах
self.update_transfers_periodically() self.update_transfers_periodically()
# Обновляем статус бар после запуска сервера
self.update_status_bar()
except Exception as e: except Exception as e:
self.append_tftp_log(f"[ERROR] Ошибка запуска сервера: {str(e)}") self.append_tftp_log(f"[ERROR] Ошибка запуска сервера: {str(e)}")
messagebox.showerror("Ошибка", f"Не удалось запустить TFTP сервер: {str(e)}") messagebox.showerror("Ошибка", f"Не удалось запустить TFTP сервер: {str(e)}")
@@ -1197,6 +1329,9 @@ class SerialAppGUI(tk.Tk):
for item in self.transfers_tree.get_children(): for item in self.transfers_tree.get_children():
self.transfers_tree.delete(item) self.transfers_tree.delete(item)
# Обновляем статус бар после остановки сервера
self.update_status_bar()
except Exception as e: except Exception as e:
self.append_tftp_log(f"[ERROR] Ошибка остановки сервера: {str(e)}") self.append_tftp_log(f"[ERROR] Ошибка остановки сервера: {str(e)}")
messagebox.showerror("Ошибка", f"Не удалось остановить TFTP сервер: {str(e)}") messagebox.showerror("Ошибка", f"Не удалось остановить TFTP сервер: {str(e)}")
@@ -1206,7 +1341,14 @@ class SerialAppGUI(tk.Tk):
self.stop_tftp_button.config(state="normal") self.stop_tftp_button.config(state="normal")
def append_tftp_log(self, text): def append_tftp_log(self, text):
append_text_to_widget(self.tftp_log_text, text) if "[ERROR]" in text:
self.tftp_log_text.append_error(text)
elif "[WARNING]" in text or "[WARN]" in text:
self.tftp_log_text.append_warning(text)
elif "[INFO]" in text:
self.tftp_log_text.append_info(text)
else:
self.tftp_log_text.append_text(text)
# Обновление информации об активных передачах # Обновление информации об активных передачах
def update_transfers_info(self): def update_transfers_info(self):
@@ -1264,6 +1406,89 @@ class SerialAppGUI(tk.Tk):
if not self.tftp_ip_var.get() in adapters: if not self.tftp_ip_var.get() in adapters:
self.tftp_ip_var.set(adapters[0]) self.tftp_ip_var.set(adapters[0])
# Добавляем новый метод для создания статус бара
def create_status_bar(self):
self.status_bar = ttk.Frame(self)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# Создаем фрейм для индикаторов
indicators_frame = ttk.Frame(self.status_bar)
indicators_frame.pack(side=tk.LEFT)
# Индикатор подключения к коммутатору
self.connection_indicator_frame = ttk.Frame(indicators_frame)
self.connection_indicator_frame.pack(side=tk.LEFT)
ttk.Label(self.connection_indicator_frame, text="COM:", padding=(5, 2)).pack(side=tk.LEFT)
self.connection_indicator = ttk.Label(
self.connection_indicator_frame,
text="",
font=("Segoe UI", 10),
width=2,
anchor='center',
padding=(2, 2)
)
self.connection_indicator.pack(side=tk.LEFT)
# Индикатор TFTP сервера
self.tftp_indicator_frame = ttk.Frame(indicators_frame)
self.tftp_indicator_frame.pack(side=tk.LEFT, padx=10)
ttk.Label(self.tftp_indicator_frame, text="TFTP:", padding=(5, 2)).pack(side=tk.LEFT)
self.tftp_indicator = ttk.Label(
self.tftp_indicator_frame,
text="",
font=("Segoe UI", 10),
width=2,
anchor='center',
padding=(2, 2)
)
self.tftp_indicator.pack(side=tk.LEFT)
ttk.Separator(self.status_bar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
# Остальные элементы статус бара...
self.port_label = ttk.Label(self.status_bar, text="", padding=(5, 2))
self.port_label.pack(side=tk.LEFT)
ttk.Separator(self.status_bar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
self.baudrate_label = ttk.Label(self.status_bar, text="", padding=(5, 2))
self.baudrate_label.pack(side=tk.LEFT)
ttk.Separator(self.status_bar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
self.copy_mode_label = ttk.Label(self.status_bar, text="", padding=(5, 2))
self.copy_mode_label.pack(side=tk.LEFT)
ttk.Separator(self.status_bar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5)
self.version_label = ttk.Label(self.status_bar, text=f"Версия: {VERSION}", padding=(5, 2))
self.version_label.pack(side=tk.RIGHT)
# Обновляем метод 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')
# Обновляем индикатор TFTP сервера
if self.tftp_server and self.tftp_server.running:
self.tftp_indicator.configure(text="", foreground='green')
else:
self.tftp_indicator.configure(text="", foreground='red')
# Остальные обновления статус бара
port = self.settings.get("port", "Не выбран")
baudrate = self.settings.get("baudrate", "9600")
copy_mode = "Построчный" if self.settings.get("copy_mode") == "line" else "Блочный"
self.port_label.config(text=f"Порт: {port}")
self.baudrate_label.config(text=f"Скорость: {baudrate}")
self.copy_mode_label.config(text=f"Режим: {copy_mode}")
# ========================== # ==========================
# Основной запуск приложения # Основной запуск приложения
# ========================== # ==========================