Add TFTP server functionality to the application
- Implement TFTP server tab with IP and port configuration - Create methods to start and stop TFTP server - Add logging functionality for TFTP server events - Integrate TFTPServer class into the main application - Re-enable Firmware directory creation
This commit is contained in:
@@ -37,13 +37,14 @@ import serial
|
|||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
from serial.serialutil import SerialException
|
from serial.serialutil import SerialException
|
||||||
from about_window import AboutWindow
|
from about_window import AboutWindow
|
||||||
|
from TFTPServer import TFTPServer
|
||||||
# from TFTPServer import TFTPServerThread
|
# from TFTPServer import TFTPServerThread
|
||||||
|
|
||||||
# Создаем необходимые папки
|
# Создаем необходимые папки
|
||||||
os.makedirs("Logs", exist_ok=True)
|
os.makedirs("Logs", exist_ok=True)
|
||||||
os.makedirs("Configs", exist_ok=True)
|
os.makedirs("Configs", exist_ok=True)
|
||||||
os.makedirs("Settings", exist_ok=True)
|
os.makedirs("Settings", exist_ok=True)
|
||||||
# os.makedirs("Firmware", exist_ok=True)
|
os.makedirs("Firmware", exist_ok=True)
|
||||||
|
|
||||||
# Файл настроек находится в папке Settings
|
# Файл настроек находится в папке Settings
|
||||||
SETTINGS_FILE = os.path.join("Settings", "settings.json")
|
SETTINGS_FILE = os.path.join("Settings", "settings.json")
|
||||||
@@ -535,6 +536,7 @@ class SerialAppGUI(tk.Tk):
|
|||||||
self.option_add("*Font", default_font)
|
self.option_add("*Font", default_font)
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
self.tftp_server = None
|
||||||
|
|
||||||
# Глобальные биндинги
|
# Глобальные биндинги
|
||||||
self.bind_class("Text", "<Control-c>", lambda event: event.widget.event_generate("<<Copy>>"))
|
self.bind_class("Text", "<Control-c>", lambda event: event.widget.event_generate("<<Copy>>"))
|
||||||
@@ -613,14 +615,17 @@ class SerialAppGUI(tk.Tk):
|
|||||||
interactive_frame = ttk.Frame(self.notebook)
|
interactive_frame = ttk.Frame(self.notebook)
|
||||||
file_exec_frame = ttk.Frame(self.notebook)
|
file_exec_frame = ttk.Frame(self.notebook)
|
||||||
config_editor_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(interactive_frame, text="Интерактивный режим")
|
||||||
self.notebook.add(file_exec_frame, text="Выполнение файла")
|
self.notebook.add(file_exec_frame, text="Выполнение файла")
|
||||||
self.notebook.add(config_editor_frame, text="Редактор конфигурации")
|
self.notebook.add(config_editor_frame, text="Редактор конфигурации")
|
||||||
|
self.notebook.add(tftp_frame, text="TFTP Сервер")
|
||||||
|
|
||||||
self.create_interactive_tab(interactive_frame)
|
self.create_interactive_tab(interactive_frame)
|
||||||
self.create_file_exec_tab(file_exec_frame)
|
self.create_file_exec_tab(file_exec_frame)
|
||||||
self.create_config_editor_tab(config_editor_frame)
|
self.create_config_editor_tab(config_editor_frame)
|
||||||
|
self.create_tftp_tab(tftp_frame)
|
||||||
|
|
||||||
# -------------- Вкладка "Интерактивный режим" --------------
|
# -------------- Вкладка "Интерактивный режим" --------------
|
||||||
def create_interactive_tab(self, frame):
|
def create_interactive_tab(self, frame):
|
||||||
@@ -823,6 +828,88 @@ class SerialAppGUI(tk.Tk):
|
|||||||
about_window.transient(self)
|
about_window.transient(self)
|
||||||
about_window.grab_set()
|
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")
|
||||||
|
|
||||||
# ==========================
|
# ==========================
|
||||||
# Парсер аргументов (не используется)
|
# Парсер аргументов (не используется)
|
||||||
# ==========================
|
# ==========================
|
||||||
|
|||||||
178
TFTPServer.py
Normal file
178
TFTPServer.py
Normal file
@@ -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
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tftpy>=0.8.0
|
||||||
Reference in New Issue
Block a user