diff --git a/ComConfigCopy.py b/ComConfigCopy.py index 467c80c..7665c8b 100644 --- a/ComConfigCopy.py +++ b/ComConfigCopy.py @@ -37,13 +37,14 @@ import serial import serial.tools.list_ports from serial.serialutil import SerialException from about_window import AboutWindow +from TFTPServer import TFTPServer # from TFTPServer import TFTPServerThread # Создаем необходимые папки os.makedirs("Logs", exist_ok=True) os.makedirs("Configs", exist_ok=True) os.makedirs("Settings", exist_ok=True) -# os.makedirs("Firmware", exist_ok=True) +os.makedirs("Firmware", exist_ok=True) # Файл настроек находится в папке Settings SETTINGS_FILE = os.path.join("Settings", "settings.json") @@ -535,6 +536,7 @@ class SerialAppGUI(tk.Tk): self.option_add("*Font", default_font) self.settings = settings self.connection = None + self.tftp_server = None # Глобальные биндинги self.bind_class("Text", "", lambda event: event.widget.event_generate("<>")) @@ -613,14 +615,17 @@ class SerialAppGUI(tk.Tk): interactive_frame = ttk.Frame(self.notebook) file_exec_frame = ttk.Frame(self.notebook) config_editor_frame = ttk.Frame(self.notebook) + tftp_frame = ttk.Frame(self.notebook) self.notebook.add(interactive_frame, text="Интерактивный режим") self.notebook.add(file_exec_frame, text="Выполнение файла") self.notebook.add(config_editor_frame, text="Редактор конфигурации") + self.notebook.add(tftp_frame, text="TFTP Сервер") self.create_interactive_tab(interactive_frame) self.create_file_exec_tab(file_exec_frame) self.create_config_editor_tab(config_editor_frame) + self.create_tftp_tab(tftp_frame) # -------------- Вкладка "Интерактивный режим" -------------- def create_interactive_tab(self, frame): @@ -823,6 +828,88 @@ class SerialAppGUI(tk.Tk): about_window.transient(self) about_window.grab_set() + def create_tftp_tab(self, frame): + # Создаем фрейм для управления TFTP сервером + control_frame = ttk.Frame(frame) + control_frame.pack(fill=X, pady=5) + + # IP адрес + ip_frame = ttk.Frame(control_frame) + ip_frame.pack(fill=X, pady=5) + ttk.Label(ip_frame, text="IP адрес:").pack(side=LEFT, padx=5) + self.tftp_ip_var = StringVar(value="0.0.0.0") + ttk.Entry(ip_frame, textvariable=self.tftp_ip_var, width=15).pack(side=LEFT, padx=5) + + # Порт + port_frame = ttk.Frame(control_frame) + port_frame.pack(fill=X, pady=5) + ttk.Label(port_frame, text="Порт:").pack(side=LEFT, padx=5) + self.tftp_port_var = StringVar(value="69") + ttk.Entry(port_frame, textvariable=self.tftp_port_var, width=6).pack(side=LEFT, padx=5) + + # Кнопки управления + button_frame = ttk.Frame(control_frame) + button_frame.pack(fill=X, pady=5) + self.start_tftp_button = ttk.Button(button_frame, text="Запустить сервер", command=self.start_tftp_server) + self.start_tftp_button.pack(side=LEFT, padx=5) + self.stop_tftp_button = ttk.Button(button_frame, text="Остановить сервер", command=self.stop_tftp_server, state="disabled") + self.stop_tftp_button.pack(side=LEFT, padx=5) + + # Лог TFTP сервера + log_frame = ttk.Frame(frame) + log_frame.pack(fill=BOTH, expand=True, pady=5) + ttk.Label(log_frame, text="Лог сервера:").pack(anchor=W, padx=5) + self.tftp_log_text = tk.Text(log_frame, wrap="word", height=15) + self.tftp_log_text.pack(fill=BOTH, expand=True, padx=5, pady=5) + + def start_tftp_server(self): + try: + ip = self.tftp_ip_var.get() + port = int(self.tftp_port_var.get()) + + if not self.tftp_server: + self.tftp_server = TFTPServer("Firmware") + + # Устанавливаем функцию обратного вызова для логирования + def log_callback(message): + self.tftp_log_text.insert(END, f"{message}\n") + self.tftp_log_text.see(END) + + self.tftp_server.set_log_callback(log_callback) + + threading.Thread( + target=self.run_tftp_server, + args=(ip, port), + daemon=True + ).start() + + self.start_tftp_button.config(state="disabled") + self.stop_tftp_button.config(state="normal") + + except ValueError: + messagebox.showerror("Ошибка", "Некорректный порт") + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось запустить TFTP сервер: {str(e)}") + + def run_tftp_server(self, ip, port): + try: + self.tftp_server.start_server(ip, port) + except Exception as e: + self.tftp_log_text.insert(END, f"[ERROR] Ошибка TFTP сервера: {str(e)}\n") + self.stop_tftp_server() + + def stop_tftp_server(self): + if self.tftp_server: + try: + self.tftp_server.stop_server() + self.tftp_server = None + self.tftp_log_text.insert(END, "[INFO] TFTP сервер остановлен\n") + except Exception as e: + self.tftp_log_text.insert(END, f"[ERROR] Ошибка при остановке TFTP сервера: {str(e)}\n") + finally: + self.start_tftp_button.config(state="normal") + self.stop_tftp_button.config(state="disabled") + # ========================== # Парсер аргументов (не используется) # ========================== diff --git a/TFTPServer.py b/TFTPServer.py new file mode 100644 index 0000000..434541d --- /dev/null +++ b/TFTPServer.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import tftpy +import logging +from typing import Optional, Callable, Dict +from dataclasses import dataclass +from datetime import datetime + +@dataclass +class FileTransfer: + filename: str + client_address: tuple + start_time: datetime + total_blocks: int = 0 + current_block: int = 0 + status: str = "в процессе" + +class TFTPServer: + def __init__(self, root_path: str): + """ + Инициализация TFTP сервера + + Args: + root_path (str): Путь к корневой директории для файлов + """ + self.root_path = root_path + self.server: Optional[tftpy.TftpServer] = None + self.logger = logging.getLogger(__name__) + self.log_callback: Optional[Callable[[str], None]] = None + self.progress_callback: Optional[Callable[[str, tuple, int, int, str], None]] = None + self.active_transfers: Dict[str, FileTransfer] = {} + + def set_log_callback(self, callback: Callable[[str], None]): + """Установка функции обратного вызова для логирования""" + self.log_callback = callback + + def set_progress_callback(self, callback: Callable[[str, tuple, int, int, str], None]): + """Установка функции обратного вызова для отображения прогресса""" + self.progress_callback = callback + + def log_message(self, message: str, level: str = "INFO"): + """Логирование сообщения""" + if self.log_callback: + self.log_callback(f"[{level}] {message}") + if level == "INFO": + self.logger.info(message) + elif level == "ERROR": + self.logger.error(message) + elif level == "WARNING": + self.logger.warning(message) + + def update_progress(self, transfer: FileTransfer): + """Обновление прогресса передачи файла""" + if self.progress_callback: + self.progress_callback( + transfer.filename, + transfer.client_address, + transfer.current_block, + transfer.total_blocks, + transfer.status + ) + + def start_server(self, ip: str = "0.0.0.0", port: int = 69): + """ + Запуск TFTP сервера + + Args: + ip (str): IP адрес для прослушивания (по умолчанию "0.0.0.0") + port (int): Порт для прослушивания (по умолчанию 69) + """ + try: + if not os.path.exists(self.root_path): + os.makedirs(self.root_path) + + # Создаем серверный класс с обработчиками событий + server = tftpy.TftpServer(self.root_path) + + # Добавляем обработчики событий + def on_read_request(filename: str, client_address: tuple): + self.log_message(f"Получен запрос на скачивание файла '{filename}' от {client_address[0]}:{client_address[1]}") + file_path = os.path.join(self.root_path, filename) + if not os.path.exists(file_path): + self.log_message(f"Файл '{filename}' не найден", "ERROR") + return False + + # Создаем запись о передаче файла + file_size = os.path.getsize(file_path) + total_blocks = (file_size + 511) // 512 # Размер блока TFTP = 512 байт + transfer = FileTransfer( + filename=filename, + client_address=client_address, + start_time=datetime.now(), + total_blocks=total_blocks + ) + self.active_transfers[f"{filename}_{client_address}"] = transfer + self.update_progress(transfer) + return True + + def on_write_request(filename: str, client_address: tuple): + self.log_message(f"Получен запрос на загрузку файла '{filename}' от {client_address[0]}:{client_address[1]}") + transfer = FileTransfer( + filename=filename, + client_address=client_address, + start_time=datetime.now() + ) + self.active_transfers[f"{filename}_{client_address}"] = transfer + self.update_progress(transfer) + return True + + def on_read_block_sent(filename: str, block_number: int, client_address: tuple): + key = f"{filename}_{client_address}" + if key in self.active_transfers: + transfer = self.active_transfers[key] + transfer.current_block = block_number + if transfer.current_block >= transfer.total_blocks: + transfer.status = "завершено" + del self.active_transfers[key] + self.update_progress(transfer) + self.log_message(f"Отправлен блок {block_number} файла '{filename}' клиенту {client_address[0]}:{client_address[1]}") + + def on_write_block_received(filename: str, block_number: int, client_address: tuple): + key = f"{filename}_{client_address}" + if key in self.active_transfers: + transfer = self.active_transfers[key] + transfer.current_block = block_number + if block_number == 1: # Первый блок + file_path = os.path.join(self.root_path, filename) + if os.path.exists(file_path): + transfer.total_blocks = (os.path.getsize(file_path) + 511) // 512 + if transfer.current_block >= transfer.total_blocks: + transfer.status = "завершено" + del self.active_transfers[key] + self.update_progress(transfer) + self.log_message(f"Получен блок {block_number} файла '{filename}' от клиента {client_address[0]}:{client_address[1]}") + + def on_error(error: Exception, client_address: tuple): + # Помечаем все активные передачи для этого клиента как ошибочные + for key, transfer in list(self.active_transfers.items()): + if transfer.client_address == client_address: + transfer.status = f"ошибка: {str(error)}" + self.update_progress(transfer) + del self.active_transfers[key] + self.log_message(f"Ошибка при обработке запроса от {client_address[0]}:{client_address[1]}: {str(error)}", "ERROR") + + # Устанавливаем обработчики + server.on_read_request = on_read_request + server.on_write_request = on_write_request + server.on_read_block_sent = on_read_block_sent + server.on_write_block_received = on_write_block_received + server.on_error = on_error + + self.server = server + self.log_message(f"Запуск TFTP сервера на {ip}:{port}") + self.server.listen(ip, port) + + except Exception as e: + error_msg = f"Ошибка при запуске TFTP сервера: {str(e)}" + self.log_message(error_msg, "ERROR") + raise + + def stop_server(self): + """Остановка TFTP сервера""" + if self.server: + try: + # Помечаем все активные передачи как прерванные + for transfer in self.active_transfers.values(): + transfer.status = "прервано" + self.update_progress(transfer) + self.active_transfers.clear() + + self.server.stop() + self.log_message("TFTP сервер остановлен") + except Exception as e: + error_msg = f"Ошибка при остановке TFTP сервера: {str(e)}" + self.log_message(error_msg, "ERROR") + raise \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d11b739 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +tftpy>=0.8.0 \ No newline at end of file