(len = is.read(buffer)) != -1.md

本文深入解析Java中InputStream类的read方法工作原理,探讨read()、read(buffer)及read(byte[], int, int)方法的区别与应用,特别是read(buffer)在文件读取中的高效表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(len = is.read(buffer)) != -1的原理详解

InputStream.read(buffer) 方法

  1. 在java中api文档有read()这几种方法
方法摘要方法作用
abstract intread()从输入流中读取数据的下一个字节
intread(byte[] b)将输入流中读取一定数量 并将其存储在缓冲区数组 b 中。
intread(byte[] b, int off, int len)将输入流中最多 len 个数据字节读入 byte 数组。

​ 2.read()==-1的含义

/** 
	* Reads the next byte of data from the input stream. The value byte is * returned as an <code>int</code> in the range <code>0</code> to * <code>255</code>. If no byte is available because the end of the stream * has been reached, the value <code>-1</code> is returned. This method * blocks until input data is available, the end of the stream is detected, * or an exception is thrown. 
	* * <p> A subclass must provide an implementation of this method.
    * * @return     the next byte of data, or <code>-1</code> if the end of the * stream is reached. 
    * @exception  IOException  if an I/O error occurs. 
*/

​ 这是read()方法中的注释,意识就是read()从输入流中读取下一个字节。如果没有字节可读(也就是read()读到文件最后了)read()返回-1.

  1. 从源码理解
public int read(byte b[]) throws IOException {
                     return read(b, 0, b.length);
                 }
 public int read(byte b[], int off, int len) throws IOException {
                     if (b == null) {
                         throw new NullPointerException();
                     } else if (off < 0 || len < 0 || len > b.length - off) {
                         throw new IndexOutOfBoundsException();
                     } else if (len == 0) {
                         return 0;
                     }
             
                     int c = read();
                     if (c == -1) {
                         return -1;
                     }
                     b[off] = (byte)c;
             
                     int i = 1;
                     try {
                         for (; i < len ; i++) {
                             c = read();
                             if (c == -1) {
                                 break;
                             }
                             b[off + i] = (byte)c;
                         }
                     } catch (IOException ee) {
                     }
                     return i;
                 }

流程:

  1. 如何判断文件读取完成?

    若我们下载一个文件,要在读取中不断的获取read()的返回值,判断何时-1,来表示读取完成。

  2. 为何不使用read(),而是用read(buffer)?

    原因是:read(buffer)效率更高,如果文件有10000byte,使用read()要读10000次然后不停的往存储上写入,而使用read(buffer) 可以读取最大buffer长度的数据(如:buffer长度1000),只需11次(为何11次而不是10,请接着看),然后写入存储中。

  3. read(buffer)如何读入?

    如10000byte的文件下载,我们buffer长度1000,read(buffer)其实可以看做,是将文件分成【(10000除以1000 向上取整)+1 份】11份,其中最后一块其实就是个空的,用来判断文件读取完成。

根据上面的源码,我们知道其实是read(buffer) 里面调用 read(byte b[], int off, int len), 在 read(byte b[], int off, int len) 中,读取文件

  • 首先

,在每个段中,使用

                int c = read();
                     if (c == -1) {
                         return -1;
                     }

判断第一个字节是否是-1. c==-1,说明当前是上面的1所指的第11块。

  • 其次

如果c!=-1,说明文件未读取完成(也就是1-10块),for循环

   for (; i < len ; i++) {
                             c = read();
                             if (c == -1) {
                                 break;
                             }
                             b[off + i] = (byte)c;

不断地读取字节,如果在读取期间c==-1.说明文件读取完成(也就是第10块,第10块写入的byte长度,不一定都填充满buffer的长度1000),此时,break,跳出循环。进行下一个read(buffer) ,此时满足上面的if,返回-1。

import json import os import threading import PySimpleGUI as sg import telnetlib import time import queue import logging from datetime import datetime import re ============== 全局配置 ============== DATA_FILE = ‘pyremote_config.json’ PROJECTS_FILE = ‘projects_config.json’ END_STR = “ROUTER_MPU /home\x1b[m # " LOG_FORMAT = ‘%(asctime)s - %(levelname)s - %(message)s’ ENCODING = ‘utf-8’ BUFFER_SIZE = 4096 CHUNK_SIZE = 10 CHUNK_DELAY = 0.15 COMMAND_DELAY = 0.5 MAX_PORTS = 2 AUTO_OPERATION_SUCCESS_STR = “Close watchdog” BOOT_STRING = r"Press\s*[‘"]?Ctrl+T[’"]?\sto\sskip\s*boot” SUCCESS_PATTERNS = { “watchdog -close”: r"Close watchdog", “ifconfig eth0”: r"Set eth IpAddr:.?Mask:.?Gateway:“, “ftp -get”: r"Ftp get .? success”, “flash -w”: r"Flash write .? ret=0" } ERROR_PATTERNS = [ r"error", r"fail", r"invalid", r"not found", r"timeout", r"unrecognized" ] ============== 日志配置 ============== def setup_logger(): logger = logging.getLogger(‘RemoteControl’) logger.setLevel(logging.DEBUG) file_handler = logging.FileHandler('remote_control.log') file_handler.setFormatter(logging.Formatter(LOG_FORMAT)) console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter(LOG_FORMAT)) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger logger = setup_logger() ============== 项目管理类 ============== class ProjectManager: def init(self): self.last_modified = {} self.projects = self.load_projects() def load_projects(self): default = {"projects": []} if not os.path.exists(PROJECTS_FILE): return default try: with open(PROJECTS_FILE, 'r', encoding='utf-8') as f: projects = json.load(f) if not isinstance(projects, dict) or "projects" not in projects: logger.warning("项目配置文件格式错误,使用默认结构") return default for project in projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return projects except Exception as e: logger.error(f"加载项目配置失败: {e}") return default def save_projects(self): try: with open(PROJECTS_FILE, 'w', encoding='utf-8') as f: json.dump(self.projects, f, indent=4, ensure_ascii=False) for project in self.projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return True except Exception as e: logger.error(f"保存项目配置失败: {e}") return False def add_or_update_project(self, name, commands, auto_operation=False, index=None): project = { "name": name, "commands": commands, "auto_operation": auto_operation, "bios_commands": [], "rbos_commands": [], "initrd_commands": [], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } if index is None: # 添加新项目 if "projects" not in self.projects: self.projects["projects"] = [] self.projects["projects"].append(project) else: # 更新项目 if 0 <= index < len(self.projects["projects"]): # 保留原有升级指令 for key in ["bios_commands", "rbos_commands", "initrd_commands"]: project[key] = self.projects["projects"][index].get(key, []) self.projects["projects"][index] = project self.last_modified[name] = datetime.now() return self.save_projects() def update_upgrade_commands(self, index, upgrade_type, commands): if 0 <= index < len(self.projects["projects"]): project = self.projects["projects"][index] project_name = project["name"] if upgrade_type in ["bios", "rbos", "initrd"]: project[f"{upgrade_type}_commands"] = commands self.last_modified[project_name] = datetime.now() return self.save_projects() return False def get_upgrade_commands(self, index, upgrade_type): if 0 <= index < len(self.projects["projects"]): project = self.projects["projects"][index] if upgrade_type in ["bios", "rbos", "initrd"]: return project.get(f"{upgrade_type}_commands", []) return [] def delete_project(self, index): if 0 <= index < len(self.projects["projects"]): project_name = self.projects["projects"][index]["name"] del self.projects["projects"][index] if project_name in self.last_modified: del self.last_modified[project_name] return self.save_projects() return False def import_export_projects(self, file_path, action='import'): try: if action == 'import': with open(file_path, 'r', encoding='utf-8') as f: imported = json.load(f) if not isinstance(imported, dict) or "projects" not in imported: logger.error("导入的项目文件格式不正确") return False self.projects = imported self.last_modified = {} for project in self.projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return self.save_projects() else: # export with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.projects, f, indent=4, ensure_ascii=False) return True except Exception as e: action_str = "导入" if action == 'import' else "导出" logger.error(f"{action_str}项目失败: {e}") return False def get_project_commands(self, index): if 0 <= index < len(self.projects["projects"]): return self.projects["projects"][index].get("commands", []) return [] def get_project_last_modified(self, name): return self.last_modified.get(name, datetime.min) ============== 指令执行类 ============== class CommandExecutor: def init(self, port_id): self.port_id = port_id self.tn = None self.is_connected = False self.log_queue = queue.Queue() self.lock = threading.Lock() self.stop_event = threading.Event() self.pause_event = threading.Event() self.last_response = “” self.expected_prompt = END_STR self.current_command_index = -1 self.current_command = “” self.total_commands = 0 self.log_file = None self.log_file_path = “” self.auto_mode = False self.boot_string_detected = False self.auto_completed = False self.auto_operation_callback = None self.command_success = False self.retry_count = 0 self.project_index = -1 self.project_name = “” self.project_manager = None self.max_retries = 3 self.retry_delay = 1 self.max_retry_delay = 30 self.upgrade_type = “” self.max_attempts = 10 self.timeout = 30 def set_project_manager(self, manager): self.project_manager = manager def set_log_file(self, file_path): self.log_file_path = file_path try: if self.log_file: self.log_file.close() self.log_file = open(file_path, 'a', encoding=ENCODING) return True except Exception as e: self.log_queue.put(f"打开日志文件失败: {str(e)}") return False def log_to_file(self, message): if self.log_file: try: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] self.log_file.write(f"[{timestamp}] [Port {self.port_id}] {message}\n") self.log_file.flush() except Exception as e: self.log_queue.put(f"日志写入失败: {str(e)}") def connect(self, host, port): try: with self.lock: self.tn = telnetlib.Telnet(host, port, timeout=10) self.is_connected = True self.log_queue.put(f"端口 {self.port_id} 已连接到 {host}:{port}") self.log_to_file(f"Connected to {host}:{port}") self.read_until_prompt(timeout=3) self.boot_string_detected = False self.auto_completed = False return True except Exception as e: self.log_queue.put(f"端口 {self.port_id} 连接失败: {str(e)}") return False def disconnect(self): with self.lock: if self.tn: try: self.tn.close() except: pass self.is_connected = False self.log_queue.put(f"端口 {self.port_id} 连接已断开") self.log_to_file("Disconnected") if self.log_file: try: self.log_file.close() except: pass self.log_file = None def send(self, content): if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False try: with self.lock: self.current_command = content encoded_content = content.encode(ENCODING) for i in range(0, len(encoded_content), CHUNK_SIZE): if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 发送已中止") return False while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False chunk = encoded_content[i:i + CHUNK_SIZE] self.tn.write(chunk) log_msg = f"发送分块: {chunk.decode(ENCODING, errors='replace')}" self.log_queue.put(log_msg) self.log_to_file(log_msg) time.sleep(CHUNK_DELAY) self.tn.write(b"\r\n") self.log_queue.put(f"端口 {self.port_id} 发送回车符") self.log_to_file("Sent ENTER") self.log_queue.put(f"端口 {self.port_id} 完整发送: {content.strip()}") self.log_to_file(f"Sent: {content.strip()}") return True except Exception as e: self.log_queue.put(f"端口 {self.port_id} 发送命令失败: {str(e)}") self.log_to_file(f"Send error: {str(e)}") return False def read_until_prompt(self, timeout=5): if not self.is_connected: return "" try: response = self.tn.read_until(self.expected_prompt.encode(ENCODING), timeout=timeout) decoded_response = response.decode(ENCODING, errors='replace') if decoded_response.endswith(self.expected_prompt): decoded_response = decoded_response[:-len(self.expected_prompt)].rstrip() self.last_response = decoded_response if decoded_response.strip(): self.log_queue.put(f"端口 {self.port_id} 响应: {decoded_response}") self.log_to_file(f"Response: {decoded_response}") return decoded_response except Exception as e: error_msg = f"端口 {self.port_id} 接收响应失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return "" def execute_commands(self, commands, max_retries=None, retry_delay=None): if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False max_retries = max_retries or self.max_retries retry_delay = retry_delay or self.retry_delay self.stop_event.clear() self.pause_event.clear() self.total_commands = len(commands) self.log_queue.put(f"端口 {self.port_id} 开始执行 {self.total_commands} 条命令 (最大重试: {max_retries})") self.log_to_file(f"Starting execution of {self.total_commands} commands (max_retries={max_retries})") try: for idx, cmd in enumerate(commands): if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 命令执行已中止") self.log_to_file("Execution aborted") return False self.current_command_index = idx self.retry_count = 0 self.command_success = False while not self.command_success: if self.stop_event.is_set(): return False while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False if cmd.strip(): if not self.send(cmd): return False response = self.read_until_prompt(timeout=15) self.command_success = self.check_command_success(cmd, response) if not self.command_success: self.retry_count += 1 if self.retry_count < max_retries: current_delay = min(retry_delay * (2 ** self.retry_count), self.max_retry_delay) self.log_queue.put( f"端口 {self.port_id} 命令执行失败,正在重试 ({self.retry_count}/{max_retries}): {cmd}" f" (等待 {current_delay:.1f}秒)" ) self.log_to_file( f"Command failed, retrying ({self.retry_count}/{max_retries}): {cmd}" f" (delay={current_delay:.1f}s)" ) time.sleep(current_delay) continue else: self.log_queue.put( f"端口 {self.port_id} 命令执行失败: {cmd},已达到最大重试次数({max_retries})" ) self.log_to_file(f"Command failed after {max_retries} retries: {cmd}") self.pause_event.set() while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False if self.command_success: delay_remaining = COMMAND_DELAY while delay_remaining > 0: if self.stop_event.is_set(): return False if self.pause_event.is_set(): time.sleep(0.1) continue time.sleep(0.1) delay_remaining -= 0.1 self.log_queue.put(f"端口 {self.port_id} 命令执行完成") self.log_to_file("Execution completed") self.current_command_index = -1 return True except Exception as e: error_msg = f"端口 {self.port_id} 命令执行失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return False def check_command_success(self, command, response): for cmd_key, pattern in SUCCESS_PATTERNS.items(): if cmd_key in command and re.search(pattern, response, re.IGNORECASE): return True for pattern in ERROR_PATTERNS: if re.search(pattern, response, re.IGNORECASE): return False if "ping" in command and "100% packet loss" in response: return False return bool(response.strip()) def stop_execution(self): self.stop_event.set() self.log_queue.put(f"端口 {self.port_id} 正在停止执行...") self.log_to_file("Stopping execution") def pause_execution(self): if not self.pause_event.is_set(): self.pause_event.set() self.log_queue.put(f"端口 {self.port_id} 执行已暂停") self.log_to_file("Execution paused") return True return False def resume_execution(self): if self.pause_event.is_set(): self.pause_event.clear() self.log_queue.put(f"端口 {self.port_id} 执行已继续") self.log_to_file("Execution resumed") return True return False def get_execution_status(self): return { "port_id": self.port_id, "is_connected": self.is_connected, "is_running": not self.stop_event.is_set() and not self.pause_event.is_set(), "is_paused": self.pause_event.is_set(), "is_stopped": self.stop_event.is_set(), "current_command": self.current_command.strip(), "current_index": self.current_command_index, "total_commands": self.total_commands, "log_file_path": self.log_file_path, "boot_string_detected": self.boot_string_detected, "auto_completed": self.auto_completed, "command_success": self.command_success, "retry_count": self.retry_count, "project_index": self.project_index, "project_name": self.project_name, "upgrade_type": self.upgrade_type } def detect_boot_string_and_auto_operation(self): """检测引导字符串并执行自动操作(修复版)""" if not self.is_connected or self.auto_completed: return False try: self.log_queue.put(f"端口 {self.port_id} 开始检测引导字符串...") self.log_to_file("Starting boot string detection") # 重置状态 self.boot_string_detected = False self.auto_completed = False # 第一步:检测引导字符串 start_time = time.time() while time.time() - start_time < self.timeout: if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 检测已中止") return False # 读取设备响应 response = self.read_until_prompt(timeout=1) # 使用正则检测引导字符串 if re.search(BOOT_STRING, response, re.IGNORECASE): self.boot_string_detected = True self.log_queue.put(f"端口 {self.port_id} 检测到引导字符串") self.log_to_file("Detected boot string") break time.sleep(0.5) if not self.boot_string_detected: self.log_queue.put(f"端口 {self.port_id} 未检测到引导字符串") self.log_to_file("Boot string not detected") return False # 第二步:发送Ctrl+T self.log_queue.put(f"端口 {self.port_id} 发送 Ctrl+T") if not self.send_ctrl_t(): self.log_queue.put(f"端口 {self.port_id} Ctrl+T发送失败") self.log_to_file("Failed to send Ctrl+T") return False # 第三步:等待6秒 self.log_queue.put(f"端口 {self.port_id} 等待6秒...") self.log_to_file("Waiting 6 seconds...") time.sleep(6) # 第四步:发送watchdog命令 self.log_queue.put(f"端口 {self.port_id} 发送 watchdog -close") self.log_to_file("Sending watchdog -close") self.send("watchdog -close") # 第五步:验证命令执行结果 watchdog_response = self.read_until_prompt(timeout=10) if re.search(AUTO_OPERATION_SUCCESS_STR, watchdog_response, re.IGNORECASE): self.auto_completed = True self.log_queue.put(f"端口 {self.port_id} watchdog命令执行成功") self.log_to_file("Watchdog command succeeded") return True else: self.log_queue.put(f"端口 {self.port_id} watchdog命令执行失败") self.log_to_file(f"Watchdog command failed, response: {watchdog_response}") return False except Exception as e: self.log_queue.put(f"端口 {self.port_id} 自动操作失败: {str(e)}") self.log_to_file(f"Auto operation failed: {str(e)}") return False def perform_auto_operation(self, callback=None): """执行自动操作(带重试机制)""" self.auto_operation_callback = callback # 尝试自动操作 success = False for attempt in range(1, self.max_attempts + 1): self.log_queue.put( f"端口 {self.port_id} 开始自动操作尝试 ({attempt}/{self.max_attempts})" ) self.log_to_file(f"Starting auto operation attempt {attempt}/{self.max_attempts}") success = self.detect_boot_string_and_auto_operation() if success: break # 计算指数退避延迟 delay = min(2 ** attempt, self.max_retry_delay) self.log_queue.put( f"端口 {self.port_id} 自动操作失败,正在重试 ({attempt}/{self.max_attempts}) " f"等待 {delay}秒..." ) self.log_to_file(f"Retrying in {delay} seconds...") time.sleep(delay) # 执行回调(如果有) if success and self.auto_operation_callback: self.log_queue.put(f"端口 {self.port_id} 触发自动升级回调") self.log_to_file("Triggering auto operation callback") self.auto_operation_callback() return success def send_ctrl_t(self): """发送Ctrl+T组合键(简化版)""" if not self.is_connected: return False try: with self.lock: if not self.tn: return False # 简化Ctrl+T序列 - 直接发送控制字符 # Ctrl+T的ASCII码是20(0x14) self.tn.write(b"\x14") # 添加回车确保命令被执行 self.tn.write(b"\r") self.log_queue.put(f"端口 {self.port_id} 已发送 Ctrl+T") self.log_to_file("Sent Ctrl+T") return True except Exception as e: error_msg = f"端口 {self.port_id} 发送Ctrl+T失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return False ============== GUI 界面类 ============== class RemoteControlApp: def init(self): self.project_manager = ProjectManager() self.default_data = self.load_default_data() self.executors = {} self.window = None for port_id in range(1, MAX_PORTS + 1): executor = CommandExecutor(port_id) executor.set_project_manager(self.project_manager) self.executors[port_id] = executor self.setup_gui() self.running = True def load_default_data(self): default_data = { "IP1": "192.168.1.200", "port1": "1000", "IP2": "71.19.2.130", "port2": "1260", "FTP_IP": "71.19.0.120", "芯片名称": "Hi1260SV100", "发送信息": "", "board_local_ip": "71.19.0.53", "interval": "1", "start_addr": "", "end_addr": "", "文件FTP路径": "", "log_dir": os.getcwd() } if os.path.exists(DATA_FILE): try: with open(DATA_FILE, 'r') as f: data = json.load(f) default_data.update({k: data[k] for k in default_data if k in data}) except Exception as e: logger.error(f"加载默认配置失败: {e}") with open(DATA_FILE, 'w') as f: json.dump(default_data, f) return default_data def save_config(self): try: values = self.window.read()[1] if self.window else {} config = {k: values.get(k, self.default_data[k]) for k in self.default_data} with open(DATA_FILE, 'w') as f: json.dump(config, f) self.default_data = config return True except Exception as e: logger.error(f"保存配置失败: {e}") return False def create_project_buttons(self): layout = [] projects = self.project_manager.projects["projects"] if not projects: layout.append([sg.Text("没有项目,请添加新项目", text_color='red')]) for i, project in enumerate(projects): row = [ sg.Button(project["name"], key=f'-PROJECT-{i}-', size=(20, 1), tooltip=f"创建于: {project['created_at']}\n命令数: {len(project['commands'])}"), sg.Text('○', font=('Arial', 12), text_color='gray', tooltip="自动操作状态: ○未执行 ●已检测到引导字符串 ✓已完成", key=f'-AUTO-INDICATOR-{i}-') ] layout.append(row) layout.append([ sg.Button("添加新项目", key='-ADD-PROJECT-', button_color=('white', 'purple')), sg.Button("导入项目", key='-IMPORT-PROJECTS-'), sg.Button("导出项目", key='-EXPORT-PROJECTS-'), sg.Button("生成模板", key='-CREATE-TEMPLATE-') ]) return layout def create_port_selection_dialog(self, title, port_ids): layout = [ [sg.Text(title)], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in port_ids], [sg.Button("确定"), sg.Button("取消")] ] return sg.Window("选择端口", layout, modal=True) def create_project_function_window(self, index): if index >= len(self.project_manager.projects["projects"]): return None project = self.project_manager.projects["projects"][index] project_name = project["name"] upgrade_types = [ ("定义升级", "base", 'blue'), ("BIOS升级", "bios", 'blue'), ("RBOS升级", "rbos", 'blue'), ("INITRD升级", "initrd", 'blue') ] layout = [ [sg.Text(f"项目: {project_name}", font=("Arial", 12, "bold"))] ] for text, upgrade_type, color in upgrade_types: layout.append([ sg.Button(text, size=(12, 1), key=f'-{upgrade_type.upper()}-UPGRADE-{index}-', button_color=('white', color)), sg.Button("✎", size=(2, 1), key=f'-EDIT-{upgrade_type.upper()}-{index}-', button_color=('white', 'gray'), tooltip=f"编辑{text}指令") ]) layout.extend([ [sg.Button("一键升级", size=(12, 1), key=f'-AUTO-{index}-', button_color=('white', 'purple'), tooltip="执行自动操作进入Shell环境并触发升级")], [sg.Button("编辑项目", key=f'-EDIT-{index}-', button_color=('white', 'orange'))], [sg.Button("删除项目", key=f'-DELETE-{index}-', button_color=('white', 'red'))], [sg.Button("关闭", key='-CLOSE-')] ]) return sg.Window(f"项目功能 - {project_name}", layout, modal=True, finalize=True) def edit_upgrade_commands(self, index, upgrade_type): """编辑指定项目的升级命令""" project = self.project_manager.projects["projects"][index] title = f"{upgrade_type.upper()}升级指令" if upgrade_type != "base" else "基础升级指令" if upgrade_type == "base": commands = project.get("commands", []) else: commands = project.get(f"{upgrade_type}_commands", []) layout = [ [sg.Text(f"{title}:")], [sg.Multiline(key='-UPGRADE-COMMANDS-', size=(60, 10), default_text='\n'.join(commands), tooltip="每行一个命令,命令将按顺序执行")], [sg.Button("保存并执行", key='-SAVE-EXECUTE-'), sg.Button("保存", key='-SAVE-'), sg.Button("取消", key='-CANCEL-')] ] window = sg.Window(title, layout, modal=True, finalize=True) while True: event, values = window.read() if event in (sg.WINDOW_CLOSED, '-CANCEL-'): break commands = [cmd.strip() for cmd in values['-UPGRADE-COMMANDS-'].split('\n') if cmd.strip()] if event in ('-SAVE-', '-SAVE-EXECUTE-'): if upgrade_type == "base": success = self.project_manager.add_or_update_project( project["name"], commands, project.get("auto_operation", False), index) else: success = self.project_manager.update_upgrade_commands(index, upgrade_type, commands) if success: sg.popup(f"{title}保存成功") if event == '-SAVE-EXECUTE-': # 保存后立即执行升级 self.handle_upgrade_event(f'-{upgrade_type.upper()}-UPGRADE-{index}-', index, upgrade_type) break else: sg.popup("操作失败,请查看日志") window.close() return True def handle_auto_event(self, event, index): """处理项目功能窗口中的自动操作按钮""" # 创建端口选择对话框 port_layout = [ [sg.Text("选择执行自动操作的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [ sg.Text("超时时间():", size=(10, 1)), sg.Input(key='-TIMEOUT-', default_text="60", size=(5, 1)), sg.Text("重试次数:", size=(8, 1)), sg.Input(key='-RETRIES-', default_text="5", size=(5, 1)) ], [sg.Button("确定", key='-OK-'), sg.Button("取消", key='-CANCEL-')] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event != '-OK-' or not any(port_values.values()): return selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") return # 获取配置参数 try: timeout = int(port_values.get('-TIMEOUT-', 60)) max_retries = int(port_values.get('-RETRIES-', 10)) except ValueError: sg.popup("无效的超时或重试次数设置,使用默认值") timeout = 60 max_retries = 10 # 设置日志文件 log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") project_name = self.project_manager.projects["projects"][index]["name"] project_name_safe = re.sub(r'[\\/*?:"<>|]', "", project_name) # 创建升级类型选择对话框 upgrade_types = [ ("一键升级", "base"), ("BIOS升级", "bios"), ("RBOS升级", "rbos"), ("INITRD升级", "initrd") ] upgrade_layout = [ [sg.Text("请选择升级类型:")], *[[sg.Radio(text, "UPGRADE_TYPE", key=f'-TYPE-{upgrade_type}-', default=(i == 0))] for i, (text, upgrade_type) in enumerate(upgrade_types)], [sg.Button("确定", key='-UPGRADE-OK-'), sg.Button("取消", key='-UPGRADE-CANCEL-')] ] upgrade_window = sg.Window("选择升级类型", upgrade_layout, modal=True) upgrade_event, upgrade_values = upgrade_window.read() upgrade_window.close() if upgrade_event != '-UPGRADE-OK-': return # 确定选择的升级类型 selected_upgrade_type = None for _, upgrade_type in upgrade_types: if upgrade_values[f'-TYPE-{upgrade_type}-']: selected_upgrade_type = upgrade_type break if not selected_upgrade_type: sg.popup("请选择升级类型") return # 为每个端口设置日志文件 for port_id in selected_ports: executor = self.executors[port_id] log_file = os.path.join( log_dir, f"{project_name_safe}_auto_{selected_upgrade_type}_port{port_id}_{timestamp}.log" ) executor.set_log_file(log_file) executor.timeout = timeout executor.max_attempts = max_retries executor.project_index = index executor.project_name = project_name executor.upgrade_type = selected_upgrade_type # 在新线程中执行自动操作和升级 threading.Thread( target=self._execute_auto_with_upgrade, args=(index, selected_upgrade_type, selected_ports), daemon=True ).start() def _execute_auto_with_upgrade(self, index, upgrade_type, port_ids): """执行自动操作并在成功后触发升级""" for port_id in port_ids: executor = self.executors[port_id] self.window['-STATUS-'].update(f"端口 {port_id} 开始执行自动操作...") # 执行自动操作(进入Shell环境) success = executor.perform_auto_operation() if success: self.window['-STATUS-'].update(f"端口 {port_id} 自动操作成功,开始升级...") # 获取升级命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) # 执行升级命令 upgrade_success = executor.execute_commands(commands, max_retries=3, retry_delay=1) status = "成功" if upgrade_success else "失败" self.window['-STATUS-'].update(f"端口 {port_id} {upgrade_type.upper()}升级{status}") else: self.window['-STATUS-'].update(f"端口 {port_id} 自动操作失败,无法执行升级") def handle_upgrade_event(self, event, index, upgrade_type): project = self.project_manager.projects["projects"][index] project_name = project["name"] # 根据升级类型获取对应的命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) upgrade_type_name = "一键升级" else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) upgrade_type_name = f"{upgrade_type.upper()}升级" if not commands: sg.popup(f"未配置{upgrade_type.upper()}升级指令,请先配置") self.edit_upgrade_commands(index, upgrade_type) return port_window = self.create_port_selection_dialog( f"选择执行 {project_name} {upgrade_type_name} 的端口:", list(self.executors.keys()) ) port_event, port_values = port_window.read() port_window.close() if port_event != "确定" or not any(port_values.values()): sg.popup("请至少选择一个端口") return selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") project_name_safe = re.sub(r'[\\/*?:"<>|]', "", project_name) for port_id in selected_ports: log_file = os.path.join(log_dir, f"{project_name_safe}_{upgrade_type_name}_port{port_id}_{timestamp}.log") executor = self.executors[port_id] executor.set_log_file(log_file) executor.project_index = index executor.project_name = project_name executor.upgrade_type = upgrade_type threading.Thread( target=self.execute_upgrade, args=(index, upgrade_type, selected_ports), daemon=True ).start() def execute_upgrade(self, index, upgrade_type, port_ids): # 根据升级类型获取对应的命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) upgrade_type_name = "一键升级" else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) upgrade_type_name = f"{upgrade_type.upper()}升级" if not commands: for port_id in port_ids: self.executors[port_id].log_queue.put(f"错误:{upgrade_type_name}没有配置命令") return self.window['-CURRENT-COMMAND-'].update("") self.window['-COMMAND-PROGRESS-'].update("0/0") threads = [] for port_id in port_ids: executor = self.executors[port_id] thread = threading.Thread( target=self._execute_upgrade_on_port, args=(executor, commands, port_id, upgrade_type_name), daemon=True ) thread.start() threads.append(thread) for thread in threads: thread.join(timeout=300) self.window['-STATUS-'].update(f"{upgrade_type_name}任务已完成") def _execute_upgrade_on_port(self, executor, commands, port_id, upgrade_type_name): self.window['-STATUS-'].update(f"端口 {port_id} 开始{upgrade_type_name}...") success = executor.execute_commands(commands, max_retries=3, retry_delay=1) status = "成功" if success else "失败" self.window['-STATUS-'].update(f"端口 {port_id} {upgrade_type_name}{status}") def setup_gui(self): sg.theme('LightBlue1') # 输出区域 output = sg.Multiline( size=(80, 20), key='-OUTPUT-', autoscroll=True, background_color='#f0f0f0', text_color='black' ) # 状态信息区域 status_info = [ sg.Text("当前命令: ", size=(10, 1)), sg.Text("无", key='-CURRENT-COMMAND-', size=(40, 1), text_color='blue'), sg.Text("进度: ", size=(5, 1)), sg.Text("0/0", key='-COMMAND-PROGRESS-', size=(10, 1)), sg.Text("状态: ", size=(5, 1)), sg.Text("", key='-COMMAND-STATUS-', size=(10, 1), text_color='green'), sg.Text("项目: ", size=(5, 1)), sg.Text("无", key='-PROJECT-NAME-STATUS-', size=(20, 1), text_color='purple'), sg.Text("升级类型: ", size=(8, 1)), sg.Text("无", key='-UPGRADE-TYPE-STATUS-', size=(15, 1), text_color='red') ] # 项目列表区域 projects_frame = sg.Frame("项目列表", [ [sg.Column( self.create_project_buttons(), scrollable=True, vertical_scroll_only=True, size=(700, 100), key='-PROJECTS-COLUMN-' )] ], key='-PROJECTS-FRAME-') # 执行控制区域 control_buttons = [ sg.Button('暂停执行', key='-PAUSE-EXECUTION-', button_color=('white', 'orange'), size=(10, 1)), sg.Button('继续执行', key='-RESUME-EXECUTION-', button_color=('white', 'green'), size=(10, 1)), sg.Button('停止执行', key='-STOP-EXECUTION-', button_color=('white', 'red'), size=(10, 1)), sg.Button('跳过命令', key='-SKIP-COMMAND-', button_color=('white', 'blue'), size=(10, 1)), sg.Button('Go Shell', key='-GO-SHELL-', button_color=('white', 'purple'), size=(10, 1), tooltip="执行自动操作:发送Ctrl+T,等待6秒后发送watchdog -close") ] # 端口连接区域 port_layouts = [] for port_id in range(1, MAX_PORTS + 1): port_layout = [ sg.Text(f'端口 {port_id} IP:', size=(8, 1)), sg.Input(key=f"IP{port_id}", default_text=self.default_data[f"IP{port_id}"], size=(15, 1)), sg.Text('端口:', size=(5, 1)), sg.Input(key=f"port{port_id}", default_text=self.default_data[f"port{port_id}"], size=(8, 1)), sg.Button(f'连接{port_id}', key=f'-CONNECT-{port_id}-', button_color=('white', 'green')), sg.Button(f'断开{port_id}', key=f'-DISCONNECT-{port_id}-', button_color=('white', 'red')), sg.Text('●', key=f'-STATUS-LIGHT-{port_id}-', text_color='red', font=('Arial', 12)), sg.Text("未连接", key=f'-CONNECTION-STATUS-{port_id}-'), sg.Text('○', key=f'-AUTO-STATUS-{port_id}-', text_color='gray', font=('Arial', 12), tooltip="自动操作状态: ○未执行 ●已检测到引导字符串 ✓已完成") ] port_layouts.append(port_layout) # 日志保存区域 log_layout = [ sg.Text('日志目录:', size=(8, 1)), sg.Input(key='log_dir', default_text=self.default_data["log_dir"], size=(40, 1)), sg.FolderBrowse('浏览', key='-LOG-BROWSE-'), sg.Button('设置日志目录', key='-SET-LOG-DIR-') ] # 主布局 layout = [ *port_layouts, [projects_frame], status_info, [sg.Frame("执行控制", [control_buttons], key='-CONTROL-FRAME-')], [sg.Text("发送信息:", size=(8, 1)), sg.Input(key='发送信息', size=(50, 1), default_text=self.default_data["发送信息"])], [sg.Button('ROUTER_MPU下发送'), sg.Button('Shell下发送'), sg.Text('每隔', size=(3, 1)), sg.Input(key='interval', default_text=self.default_data["interval"], size=(5, 1)), sg.Text("秒", size=(2, 1)), sg.Button('定时发送'), sg.Button("停止定时发送")], [sg.Button('接收1s')], [sg.Button("使用说明", button_color=('white', 'blue'))], [sg.Text('起始地址:', size=(8, 1)), sg.Input(key='start_addr', default_text=self.default_data["start_addr"], size=(12, 1)), sg.Text('结束地址:', size=(8, 1)), sg.Input(key='end_addr', default_text=self.default_data["end_addr"], size=(12, 1)), sg.Button('dump寄存器')], log_layout, [output], [sg.StatusBar("就绪", key='-STATUS-', size=(50, 1))] ] self.window = sg.Window('远程单板连接工具 (多端口)', layout, finalize=True, resizable=True) self.update_status_lights() self.update_auto_status() def handle_go_shell(self): """处理Go Shell按钮点击事件(增强版)""" # 选择端口对话框 port_layout = [ [sg.Text("选择执行 Go Shell 操作的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [ sg.Text("超时时间():", size=(10, 1)), sg.Input(key='-TIMEOUT-', default_text="60", size=(5, 1)), sg.Text("重试次数:", size=(8, 1)), sg.Input(key='-RETRIES-', default_text="5", size=(5, 1)) ], [sg.Button("确定", key='-OK-'), sg.Button("取消", key='-CANCEL-')] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event == '-OK-': selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") return # 获取配置参数 try: timeout = int(port_values.get('-TIMEOUT-', 60)) max_retries = int(port_values.get('-RETRIES-', 10)) except ValueError: sg.popup("无效的超时或重试次数设置,使用默认值") timeout = 60 max_retries = 10 # 设置日志文件 log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") for port_id in selected_ports: executor = self.executors[port_id] log_file = os.path.join(log_dir, f"go_shell_port{port_id}_{timestamp}.log") executor.set_log_file(log_file) # 设置自动操作的参数 executor.timeout = timeout executor.max_attempts = max_retries # 在新线程中执行自动操作 threading.Thread( target=self._execute_go_shell_on_port, args=(executor, port_id), daemon=True ).start() def _execute_go_shell_on_port(self, executor, port_id): """在单个端口上执行Go Shell操作""" self.window['-STATUS-'].update(f"端口 {port_id} 开始执行 Go Shell 操作...") # 执行自动操作(不关联项目) success = executor.perform_auto_operation() if success: self.window['-STATUS-'].update(f"端口 {port_id} Go Shell 操作成功") sg.popup_notify(f"端口 {port_id} Go Shell 操作成功", display_duration_in_ms=3000) else: self.window['-STATUS-'].update(f"端口 {port_id} Go Shell 操作失败") sg.popup_error(f"端口 {port_id} Go Shell 操作失败,请检查日志") def update_status_lights(self): for port_id, executor in self.executors.items(): color = 'green' if executor.is_connected else 'red' status = "已连接" if executor.is_connected else "未连接" self.window[f'-STATUS-LIGHT-{port_id}-'].update(text_color=color) self.window[f'-CONNECTION-STATUS-{port_id}-'].update(status) def update_auto_status(self): for port_id, executor in self.executors.items(): if executor.auto_completed: self.window[f'-AUTO-STATUS-{port_id}-'].update('✓', text_color='green') elif executor.boot_string_detected: self.window[f'-AUTO-STATUS-{port_id}-'].update('●', text_color='red') else: self.window[f'-AUTO-STATUS-{port_id}-'].update('○', text_color='gray') def update_output(self): output_text = self.window['-OUTPUT-'].get() updated = False for port_id, executor in self.executors.items(): while not executor.log_queue.empty(): try: message = executor.log_queue.get_nowait() output_text += message + '\n' updated = True except queue.Empty: break if updated: self.window['-OUTPUT-'].update(output_text) def update_status(self): for port_id, executor in self.executors.items(): status = executor.get_execution_status() if port_id == 1: # 只显示第一个端口的状态 if status["current_command"]: self.window['-CURRENT-COMMAND-'].update(status["current_command"]) if status["current_index"] >= 0 and status["total_commands"] > 0: progress_text = f"{status['current_index'] + 1}/{status['total_commands']}" self.window['-COMMAND-PROGRESS-'].update(progress_text) self.window['-COMMAND-STATUS-'].update( "成功" if status["command_success"] else "失败", text_color='green' if status["command_success"] else 'red' ) self.window['-PROJECT-NAME-STATUS-'].update(status["project_name"] or "无") upgrade_type = status["upgrade_type"] self.window['-UPGRADE-TYPE-STATUS-'].update( upgrade_type.upper() if upgrade_type else "无", text_color='red' if upgrade_type else None ) color = 'green' if status["is_connected"] else 'red' self.window[f'-STATUS-LIGHT-{port_id}-'].update(text_color=color) self.window[f'-CONNECTION-STATUS-{port_id}-'].update( "已连接" if status["is_connected"] else "未连接" ) self.update_auto_status() # 更新按钮状态(基于第一个端口) if self.executors[1].is_connected: status1 = self.executors[1].get_execution_status() if status1["is_running"]: self.window['-PAUSE-EXECUTION-'].update(disabled=False) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].update(disabled=False) self.window['-SKIP-COMMAND-'].update(disabled=True) elif status1["is_paused"]: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=False) self.window['-STOP-EXECUTION-'].update(disabled=False) self.window['-SKIP-COMMAND-'].update(disabled=False) else: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].update(disabled=True) self.window['-SKIP-COMMAND-'].update(disabled=True) def refresh_project_list(self): self.window['-PROJECTS-COLUMN-'].update(visible=False) self.window['-PROJECTS-COLUMN-'].update(self.create_project_buttons()) self.window['-PROJECTS-COLUMN-'].update(visible=True) def run(self): last_status_update = time.time() while True: event, values = self.window.read(timeout=100) if event == sg.WINDOW_CLOSED: break self.update_output() current_time = time.time() if current_time - last_status_update > 0.5: self.update_status() last_status_update = current_time # 事件处理 if event.startswith('-CONNECT-'): port_id = int(event.split('-')[2]) ip_key = f"IP{port_id}" port_key = f"port{port_id}" if values[ip_key] and values[port_key]: if self.executors[port_id].connect(values[ip_key], int(values[port_key])): self.window['-STATUS-'].update(f"端口 {port_id} 已连接") self.update_status_lights() elif event.startswith('-DISCONNECT-'): port_id = int(event.split('-')[2]) self.executors[port_id].disconnect() self.window['-STATUS-'].update(f"端口已断开") self.update_status_lights() elif event.startswith('-EDIT-'): parts = event.split('-') if len(parts) >= 4: try: upgrade_type = parts[2].lower() index = int(parts[3]) self.edit_upgrade_commands(index, upgrade_type) except (ValueError, IndexError): logger.error(f"解析编辑事件失败: {event}") elif event == '-SET-LOG-DIR-': if 'log_dir' in values and values['log_dir']: self.default_data["log_dir"] = values['log_dir'] self.save_config() sg.popup(f"日志目录已设置为: {values['log_dir']}") elif event == '-ADD-PROJECT-': self.edit_project_window() self.refresh_project_list() elif event.startswith('-PROJECT-'): index = int(event.split('-')[2]) project_window = self.create_project_function_window(index) if project_window: while True: win_event, win_values = project_window.read() if win_event in (sg.WINDOW_CLOSED, '-CLOSE-'): break if win_event.startswith('-EDIT-'): parts = win_event.split('-') if len(parts) >= 4: upgrade_type = parts[2].lower() self.edit_upgrade_commands(index, upgrade_type) elif win_event == f'-AUTO-{index}-': self.handle_auto_event(win_event, index) # 处理单独升级按钮事件 elif win_event.startswith('-') and win_event.endswith(f'-UPGRADE-{index}-'): # 解析升级类型 upgrade_type = win_event.split('-')[1].lower() self.handle_upgrade_event(win_event, index, upgrade_type) elif win_event == f'-EDIT-{index}-': self.edit_project_window(index) self.refresh_project_list() # 在事件处理部分添加对自动操作按钮的处理 elif event.startswith('-AUTO-'): parts = event.split('-') if len(parts) >= 3: try: index = int(parts[2]) self.handle_auto_event(event, index) except ValueError: pass elif win_event == f'-DELETE-{index}-': if self.project_manager.delete_project(index): sg.popup("项目删除成功") self.refresh_project_list() break else: sg.popup("项目删除失败") # 处理升级按钮事件(包括一键升级、BIOS升级、RBOS升级、INITRD升级) elif win_event.startswith('-') and win_event.endswith(f'-UPGRADE-{index}-'): parts = win_event.split('-') if len(parts) >= 4: upgrade_type = parts[1].lower() # 事件标识符的格式:-<升级类型>-UPGRADE-<索引>- self.handle_upgrade_event(win_event, index, upgrade_type) project_window.close() elif event == '-IMPORT-PROJECTS-': file_path = sg.popup_get_file("选择项目配置文件", file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_export_projects(file_path, 'import'): sg.popup("项目导入成功") self.refresh_project_list() else: sg.popup("项目导入失败") elif event == '-EXPORT-PROJECTS-': file_path = sg.popup_get_file("保存项目配置文件", save_as=True, file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_export_projects(file_path, 'export'): sg.popup("项目导出成功") else: sg.popup("项目导出失败") elif event == '-CREATE-TEMPLATE-': self.create_template_file() sg.popup("模板文件已生成: projects_template.json") # 执行控制 elif event == '-PAUSE-EXECUTION-': for executor in self.executors.values(): if executor.get_execution_status()["is_running"]: executor.pause_execution() self.window['-STATUS-'].update("执行已暂停") elif event == '-RESUME-EXECUTION-': for executor in self.executors.values(): if executor.get_execution_status()["is_paused"]: executor.resume_execution() self.window['-STATUS-'].update("执行已继续") elif event == '-STOP-EXECUTION-': for executor in self.executors.values(): executor.stop_execution() self.window['-STATUS-'].update("执行已停止") elif event == '-SKIP-COMMAND-': for executor in self.executors.values(): if executor.get_execution_status()["is_paused"]: executor.command_success = True executor.resume_execution() self.window['-STATUS-'].update("已跳过当前命令") elif event == '-GO-SHELL-': self.handle_go_shell() # 其他功能 elif event in ('ROUTER_MPU下发送', 'Shell下发送'): if self.save_config() and values["发送信息"]: for executor in self.executors.values(): if executor.is_connected: executor.send(values["发送信息"]) executor.read_until_prompt() elif event == '定时发送': if self.save_config() and values["发送信息"] and values["interval"]: try: interval = float(values["interval"]) if interval <= 0: raise ValueError("间隔必须大于0") threading.Thread( target=self.periodic_send, args=(values["发送信息"], interval), daemon=True ).start() except Exception as e: sg.popup(f"无效的间隔: {str(e)}") elif event == "停止定时发送": for executor in self.executors.values(): executor.stop_execution() elif event == '接收1s': for executor in self.executors.values(): if executor.is_connected: executor.read_until_prompt(timeout=1) elif event == 'dump寄存器': if values["start_addr"] and values["end_addr"]: for executor in self.executors.values(): if executor.is_connected: executor.send(f"dump {values['start_addr']} {values['end_addr']}") executor.read_until_prompt() elif event == '使用说明': self.show_help() self.running = False for executor in self.executors.values(): executor.disconnect() self.window.close() def periodic_send(self, message, interval): while not self.executors[1].stop_event.is_set(): for executor in self.executors.values(): if executor.is_connected: executor.send(message) executor.read_until_prompt() time.sleep(interval) def create_template_file(self): template = { "projects": [ { "name": "示例项目", "commands": [ "watchdog -close", "ifconfig eth0 71.19.0.53 255.255.255.0 71.19.0.1", "ftp -get ftp://user:pass@71.19.0.120/firmware.bin /home/firmware.bin", "flash -w /home/firmware.bin" ], "auto_operation": True, "bios_commands": ["bios_update_command1", "bios_update_command2"], "rbos_commands": ["rbos_update_command1", "rbos_update_command2"], "initrd_commands": ["initrd_update_command1", "initrd_update_command2"], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } ] } try: with open('projects_template.json', 'w', encoding='utf-8') as f: json.dump(template, f, indent=4, ensure_ascii=False) return True except Exception as e: logger.error(f"生成模板失败: {e}") return False def show_help(self): help_text = f"""=== 远程单板连接工具使用说明 (多端口) === 1. 多端口连接管理: - 支持同时连接 {MAX_PORTS} 个端口 - 每个端口独立显示连接状态(红:未连接, 绿:已连接) - 每个端口独立保存日志文件 2. 项目功能: - 点击项目名称打开功能窗口 - 一键升级:执行项目基础指令 - BIOS/RBOS/INITRD升级:执行特定升级指令 - 自动操作:执行引导字符串检测和自动操作 3. 升级功能: - 每种升级类型有独立的指令配置 - 点击"✎"按钮编辑对应升级类型的指令 - 支持分段发送和响应验证 - 可配置最大重试次数和重试延迟 - 在状态栏显示当前升级类型 4. Go Shell功能: - 自动检测引导字符串(支持多种变体) - 发送Ctrl+T组合键 - 等待6秒后发送watchdog -close命令 - 可配置超时时间和重试次数 5. 执行控制: - 暂停/继续/停止当前执行 - 跳过当前失败的命令 - 实时显示命令执行状态 6. 日志管理: - 每个端口独立日志文件 - 可设置日志保存目录 7. 其他功能: - 定时发送命令 - 寄存器dump - 项目导入导出 """ sg.popup(help_text, title="使用说明", font=("Arial", 10), non_blocking=True) ============== 主程序入口 ============== if name == “main”: try: app = RemoteControlApp() app.run() except Exception as e: logger.exception(“应用程序崩溃”) sg.popup_error(f"应用程序发生致命错误: {str(e)}") 这是我的代码文件,请解析我的代码,并给出各模块代码的解析说明,和功能,以及操作说明,并以文件的形式给我
07-11
import socket import subprocess import websocket import time import os import threading import json import pyaudio import requests import hashlib import base64 from audioplayer import AudioPlayer import numpy as np from runner import set_global_var, get_global_var device_status = {} def listen_devices(): try: # 检测设备连接状态 result = subprocess.check_output("adb devices", shell=True).decode() current_devices = set(line.split('\t')[0] for line in result.splitlines()[1:] if line) # 检测新连接设备 for dev in current_devices - set(device_status.keys()): print(f"[设备已连接] {dev}") device_status[dev] = "connected" # 检测断开设备 for dev in set(device_status.keys()) - current_devices: print(f"[设备已断开连接] {dev}") del device_status[dev] time.sleep(1) except Exception as e: print(f"设备监控错误: {e}") def pcm_to_utf8(pcm_data: bytearray) -> str: """将16位PCM音频数据转为UTF-8字符串""" def validate_pcm(data: bytearray) -> bool: """验证PCM数据有效性""" return len(data) % 2 == 0 # 16位PCM需为偶数长度 if not validate_pcm(pcm_data): raise ValueError("无效的PCM数据长度,16位PCM需为偶数长度") try: # 转为16位有符号整数数组(小端序) samples = np.frombuffer(pcm_data, dtype='<i2') # 标准化到0-255范围 normalized = ((samples - samples.min()) * (255 / (samples.max() - samples.min()))).astype(np.uint8) # 转换为UTF-8字符串 return bytes(normalized).decode('utf-8', errors='replace') except Exception as e: raise RuntimeError(f"转换失败: {str(e)}") # 打印前32字节的十六进制表示 def parse_packets(buffer): """解析接收到的数据包""" # 解析数据包 end_marker = b'\n\n' while buffer.find(end_marker) != -1: packet_bytes = buffer[:buffer.find(end_marker) + len(end_marker)] buffer = buffer[buffer.find(end_marker) + len(end_marker):] try: json_bytes = packet_bytes[:-len(end_marker)] json_str = json_bytes.decode('utf-8') packet = json.loads(json_str) # 处理数据包 packet_type = packet.get("type") if packet_type == "recording": audio_data = base64.b64decode(packet.get("data", "")) print('audio_data ', audio_data) return audio_data elif packet_type in ["startRecorder", "stopRecord"]: pass # command_callback(packet_type) else: print(f"未知数据包类型: {packet_type}") except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") except Exception as e: print(f"数据包处理错误: {e}") def start_server(port=35000): adb_path = "adb.exe" os.system(f"adb forward tcp:{port} tcp:30000") with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('localhost', port)) #s.bind(('0.0.0.0', port)) #s.listen(5) print(f"服务器已启动,正在监听端口 {port}...") while True: threading.Thread(target=listen_devices).start() #client_socket, addr = s.accept() #print(f"接收到来自 {addr} 的连接") buffer = bytearray() try: while True: data = s.recv(4096) if data == b'': pass else: print('data', data) #buffer.extend(data) if not data: print("连接断开") break buffer.extend(data) hex_preview = parse_packets(buffer) handle_audio_chunk(hex_preview) print('hex_preview',hex_preview) '''if data==b'': pass else: if len(data) > 0: hex_preview = ' '.join(f'{b:02x}' for b in data[:32]) print(f"前32字节: {hex_preview}...") #handle_audio_chunk(hex_preview) # 调试用:将PCM转为UTF-8 if len(data) < 1024: try: utf8_data = pcm_to_utf8(data) print(f"UTF-8预览: {utf8_data[:30]}...") #handle_audio_chunk(utf8_data) except: pass''' except Exception as e: print(f"接收音频数据异常: {e}") # 全局配置信息 # 播放时是否停止收音 stop_recording_when_playing = True # 打断播放的语音指令 stop_playing_words = ["别说了", "停止", "停下"] # 说话人id voice_speaker_id = 5199 # 语音活动检测-静音时间长度,超过这个时间视为停止说话 vad_silent_time = 1.5 # 语音合成参数 tts_params = {"lan": "zh", "cuid": "test-1234", "ctp": 1, "pdt":993, "spd":5, "pit": 5,"aue": 3} # 语音识别开始指令参数 asr_params = { "type": "START", "data": { "dev_pid": 1912, "dev_key": "com.baidu.open", "format": "pcm", "sample": 16000, "cuid": "my_test_dev", "type": 1, "asr_type": 1, "need_mid": False, "need_session_finish": True } } # 全局状态变量 ws_running = False ws_object = None recorder_running = False sound_play_list = [] current_audio_player = None chat_running = False current_query = '' last_asr_time = 0 def ws_send_start_command(ws): message = json.dumps(asr_params) ws.send_text(message) def ws_send_stop_command(ws): # 发送数据 msg_data = { "type": "FINISH", } message = json.dumps(msg_data) ws.send_text(message) def on_ws_message(ws, message): global current_query, last_asr_time data = json.loads(message) cmd_type = data.get("type") if cmd_type == 'MID_TEXT': mid_text = data.get("result") set_global_var("voicebot.asr.mid_text", mid_text) last_asr_time = time.time() # print("voicebot.asr.mid_text:", mid_text) elif cmd_type == "FIN_TEXT": query = data.get("result") # print("asr result:", query) set_global_var("voicebot.asr.result", query) last_asr_time = time.time() if query and len(query) > 0: current_query += query set_global_var("voicebot.chat.query", current_query) if ws_running == False: ws.close() def on_ws_close(ws, close_status_code, close_msg): print("websocket closed:", close_status_code, close_msg) def on_ws_error(ws, error): print(f"websocket Error: {error}") ws.close() def on_ws_open(ws): print("websocket connection opened:", ws) ws_send_start_command(ws) def check_chat(query:str): # for word in stop_playing_words: # if word in query: # stop_sound_player() # return False # if query in stop_playing_words: # stop_sound_player() # return False if is_playing_or_chatting(): return False return True def stop_sound_player(): global chat_running if current_audio_player: current_audio_player.stop() if len(sound_play_list) > 0: sound_play_list.clear() chat_running = False def run_chat(query:str): global chat_running chat_running = True set_global_var("voicebot.chat.query", query) params = {"query": query} params['username'] = get_global_var("voicebot.username") params['password'] = get_global_var("voicebot.password") response = requests.post("http://127.0.0.1:8010/chat", json=params, stream=True) total_reply = '' buffer = '' for line in response.iter_lines(): if line and chat_running: text = line.decode('utf-8') data = json.loads(text[5:]) content = data.get("content") buffer += content buffer = extract_play_text(buffer) total_reply += content set_global_var("voicebot.chat.reply", total_reply) # print(content, end='', flush=True) chat_running = False buffer = buffer.strip() if len(buffer) > 0: add_play_text(buffer) time.sleep(1) set_global_var("voicebot.chat.query", None) set_global_var("voicebot.chat.reply", None) #提取播放文本 def extract_play_text(total_text:str): separators = ",;。!?:,.!?\n" last_start_pos = 0 min_sentence_length = 4 for i in range(0, len(total_text)): if total_text[i] in separators and i - last_start_pos >= min_sentence_length: text = total_text[last_start_pos: i + 1] last_start_pos = i + 1 add_play_text(text.strip()) return total_text[last_start_pos:] #添加播放文本 def add_play_text(text:str): # print("add play text:", text) if len(text) > 1: sound_play_list.append({"text": text, "mp3_file": None}) # 语音合成 下载声音文件 def download_sound_file(text:str, speaker:int=None): if speaker is None: speaker = voice_speaker_id # print("tts create:", text) mp3_path = "sounds/" + str(speaker) if not os.path.exists(mp3_path): os.mkdir(mp3_path) mp3_file = mp3_path + "/" + hashlib.md5(text.encode('utf-8')).hexdigest() + ".mp3" if os.path.exists(mp3_file): return mp3_file params = tts_params params['per'] = speaker params['text'] = text url = "http://25.83.75.1:8088/Others/tts/text2audio/json" response = requests.post(url, json=params) data = response.json() if data['success'] == False: binary_array = json.loads(data['message']['message']) binary_data = bytes(binary_array) string_data = binary_data.decode('utf-8', errors='replace') data = json.loads(string_data) return "sounds/tts-failed.mp3" else: b64_string = data['result'].get('data') mp3_data = base64.b64decode(b64_string) with open(mp3_file, 'wb') as file: file.write(mp3_data) return mp3_file #开始聊天 def is_playing_or_chatting(): return len(sound_play_list) > 0 #播放下一个声音 def play_next_sound(): global sound_play_list, current_audio_player item = sound_play_list[0] mp3_file = item.get("mp3_file") if mp3_file: player = AudioPlayer(mp3_file) current_audio_player = player try: player.play(block=True) except Exception as e: print("player exception:" + e) current_audio_player = None # print("remained sound:", len(sound_play_list)) if len(sound_play_list) > 0: sound_play_list.pop(0) #运行websocket def run_websocket(): global ws_running, ws_object ws_running = True uri = "ws://25.83.75.1:8088/Others/asr/realtime_asr?sn=voicebot" ws = websocket.WebSocketApp(uri, on_message=on_ws_message, on_close=on_ws_close, on_error=on_ws_error) ws_object = ws ws.on_open = on_ws_open ws.run_forever() ws_running = False # print("websocket end") #开始记录 def start_recorder(chuck_size:int=2560): audio = pyaudio.PyAudio() try: stream = audio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=chuck_size) return audio, stream except: print("打开麦克风失败") return None, None #获得不发音的时间 def get_silent_chunk(duration:float=0.16): sample_rate = 16000 # 采样率 num_samples = int(sample_rate * duration) # 计算样本数 silent_data = np.zeros(num_samples, dtype=np.int16) silent_bytes = silent_data.tobytes() return silent_bytes #处理音频块 def handle_audio_chunk(chunk_data:bytes): # 接受外部是否收音的要求 recording = get_global_var("voicebot.recording") if ws_object and ws_object.sock and ws_object.sock.connected: if recording == False or (stop_recording_when_playing and is_playing_or_chatting()): # print("ignor audio chunk:", sound_play_list, chat_running) ws_object.send_bytes(get_silent_chunk()) else: ws_object.send_bytes(chunk_data) #运行录音机 def run_recorder(audio=None, stream=None, chuck_size=2560): global recorder_running recorder_running = True set_global_var("voicebot.recording", True) while recorder_running: chunk_data = stream.read(chuck_size) print('chunk_data)',chunk_data) handle_audio_chunk(chunk_data) stream.stop_stream() stream.close() audio.terminate() # print("recorder end") #运行检查 def run_check(): global ws_running, recorder_running, current_query set_global_var("voicebot.running", True) while ws_running and recorder_running: time.sleep(1) if get_global_var("voicebot.running") == False: break if len(current_query) > 0 and last_asr_time > 0 and time.time() - last_asr_time > vad_silent_time: t = threading.Thread(target=run_chat, args=(current_query,)) t.start() current_query = '' ws_running = recorder_running = False set_global_var("voicebot.running", False) # print("语音助手已经停止") #运行播放机 def run_player(): while ws_running and recorder_running: time.sleep(0.1) if len(sound_play_list) > 0: play_next_sound() def run_tts(): while ws_running and recorder_running: time.sleep(0.1) for item in sound_play_list: if item.get("mp3_file") is None: item['mp3_file'] = download_sound_file(item['text']) def run(): active_threads = threading.enumerate() # 打印每个活跃线程的信息 for t in active_threads: if t.name == 'voicebot-runner': return "语音助手已经在运行中了" audio, stream = start_recorder() if audio is None or stream is None: return {"error": "语音助手开启失败,无法访问麦克风"} t = threading.Thread(target=run_websocket) t.daemon = True t.start() t=threading.Thread(target=start_server()) t.daemon = True t.start() t = threading.Thread(target=run_check, name='voicebot-runner') t.daemon = True t.start() t = threading.Thread(target=run_tts) t.daemon = True t.start() t = threading.Thread(target=run_player) t.daemon = True t.start() return "执行成功" if __name__ == "__main__": #run() start_server() 把这个TTS的功能融入到第一个脚本里面生成新脚本,并且修改安卓的代码package com.example.demoapplication; import android.Manifest; import android.content.pm.PackageManager; import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.speech.tts.TextToSpeech; import android.util.Base64; import android.util.Log; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Locale; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { private static final String TAG = "AudioRecorder"; private Button startRecordButton; private Button stopRecordButton; private Button uploadButton; // 音频录制相关 private AudioRecord audioRecord; private static final int SAMPLE_RATE = 44100; // 音频采样率 private static final int BUFFER_SIZE; // 静态代码块用于初始化缓冲区大小 static { int minBufferSize = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); } // 确保缓冲区大小有效 BUFFER_SIZE = Math.max(minBufferSize, 4096); } // 多线程任务调度器 private ScheduledExecutorService scheduler; private AtomicBoolean isRecording = new AtomicBoolean(false); // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 // 线程池服务 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 网络服务器相关 private ServerSocket serverSocket; private volatile boolean isServerRunning = true; // 服务器运行状态 private volatile Socket clientSocket; // 客户端Socket连接 private volatile BufferedWriter socketWriter; // Socket写入流 // 文本转语音(TTS)相关变量 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; // 主线程消息处理器,用于UI更新 private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接成功 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 开始录音 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); break; case 0x13: // 数据发送成功 // 减少Toast频率,避免刷屏 if (Math.random() < 0.1) { // 10%概率显示 Toast.makeText(MainActivity.this, "录音数据已发送", Toast.LENGTH_SHORT).show(); } break; case 0x14: // 停止录音 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); break; case 0x15: // 控制指令 Toast.makeText(MainActivity.this, "收到控制指令:" + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; case 0x16: // 错误消息 Toast.makeText(MainActivity.this, "错误: " + msg.obj.toString(), Toast.LENGTH_LONG).show(); break; case 0x17: // 网络状态 Toast.makeText(MainActivity.this, "网络: " + msg.obj.toString(), Toast.LENGTH_SHORT).show(); break; } } }; /** * Activity创建时调用,进行初始化操作。 * @param savedInstanceState 保存的状态数据 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); // 初始化视图组件 setupClickListeners(); // 设置点击事件监听器 checkPermissions(); // 检查权限 startServer(30000); // 启动服务器,端口30000 } /** * 初始化UI视图组件 */ private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); uploadButton = findViewById(R.id.uploadButton); stopRecordButton.setEnabled(false); uploadButton.setEnabled(false); } /** * 设置按钮点击事件监听器 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); stopRecordButton.setOnClickListener(v -> stopRecording()); uploadButton.setOnClickListener(v -> uploadRecording()); } /** * 检查录音权限并请求必要权限 */ private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } /** * 开始录音操作 */ private void startRecording() { // 检查权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } // 检查是否正在录音 if (isRecording.get() || audioRecord != null) { sendErrorMessage("录音已在进行中"); return; } // 检查网络连接 if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("客户端未连接,无法录音"); return; } try { // 初始化 AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE ); // 检查初始化状态 if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord 初始化失败"); } // 开始录音 audioRecord.startRecording(); isRecording.set(true); // 更新按钮状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); uploadButton.setEnabled(false); // 创建定时任务发送音频数据 scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 100, TimeUnit.MILLISECONDS); // 提高发送频率 handler.sendEmptyMessage(0x12); // 发送开始录音的消息 // 发送开始录音控制指令 sendControlPacket("startRecorder"); // 播放TTS提示音 playTts("开始录音"); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } /** * 停止录音操作 */ private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); // 更新按钮状态 stopRecordButton.setEnabled(false); uploadButton.setEnabled(true); handler.sendEmptyMessage(0x14); // 发送停止录音的消息 // 发送停止录音控制指令 sendControlPacket("stopRecor"); // 播放TTS提示音 playTts("停止录音"); } /** * 使用TTS播放指定文本 * @param text 要播放的文本内容 */ private void playTts(String text) { if (isTtsInitialized) { // 使用系统TTS播放 ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); Log.i(TAG, "播放TTS: " + text); } else { Log.w(TAG, "TTS未初始化,无法播放: " + text); } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); scheduler = null; } } /** * 上传音频数据到服务器 */ private void uploadAudioData() { if (!isRecording.get() || clientSocket == null || clientSocket.isClosed() || socketWriter == null) { Log.w(TAG, "无法发送音频数据: 录音未进行或客户端未连接"); return; } byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 创建JSON数据包 JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", Base64.encodeToString(buffer, 0, bytesRead, Base64.NO_WRAP)); // 使用NO_WRAP避免换行符 // 发送数据 synchronized (this) { if (socketWriter != null) { socketWriter.write(json.toString()); socketWriter.write("\n\n"); // 添加双换行作为结束标识 socketWriter.flush(); } } handler.sendEmptyMessage(0x13); // 发送录音数据的消息 } } catch (Exception e) { Log.e(TAG, "发送音频数据失败", e); sendErrorMessage("发送音频数据失败: " + e.getMessage()); } } /** * TTS初始化回调方法 * @param status 初始化状态 */ @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { // 设置默认语言为中文 int result = ttsEngine.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "TTS语言不支持中文"); } else { isTtsInitialized = true; Log.i(TAG, "TTS初始化成功,语言设置为中文"); } } else { Log.e(TAG, "TTS初始化失败"); } } /** * 发送控制指令包 * @param type 控制指令类型 */ private void sendControlPacket(String type) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("无法发送控制指令: 客户端未连接"); return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); packet.put("data", JSONObject.NULL); synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); // 双换行作为结束标识 socketWriter.flush(); } } Log.i(TAG, "控制指令发送成功: " + type); } catch (Exception e) { Log.e(TAG, "发送控制指令失败", e); sendErrorMessage("发送控制指令失败: " + e.getMessage()); } } /** * 发送错误消息 * @param message 错误信息 */ private void sendErrorMessage(String message) { Message msg = handler.obtainMessage(0x16, message); handler.sendMessage(msg); } /** * 发送网络状态消息 * @param message 网络状态信息 */ private void sendNetworkMessage(String message) { Message msg = handler.obtainMessage(0x17, message); handler.sendMessage(msg); } /** * 上传录音文件(当前模式下无实际作用) */ private void uploadRecording() { Toast.makeText(this, "该模式下无需上传文件,已实时发送", Toast.LENGTH_SHORT).show(); } /** * 启动服务器监听 * @param port 监听端口号 */ private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); Log.i(TAG, "服务器启动,监听端口: " + port); sendNetworkMessage("服务器启动"); while (isServerRunning) { try { Socket socket = serverSocket.accept(); clientSocket = socket; // 创建输出流 synchronized (this) { socketWriter = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } handler.sendEmptyMessage(0x11); // 发送客户端连接成功的消息 Log.i(TAG, "客户端已连接: " + socket.getInetAddress()); sendNetworkMessage("客户端已连接"); // 启动双向通信处理 executorService.execute(() -> startCommunication(socket)); } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "接受连接失败", e); sendErrorMessage("接受连接失败: " + e.getMessage()); } } } } catch (IOException e) { Log.e(TAG, "服务器启动失败", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } finally { closeServerSocket(); } }); } /** * 开始与客户端的通信 * @param socket 客户端Socket连接 */ private void startCommunication(Socket socket) { try (java.io.BufferedReader reader = new java.io.BufferedReader( new java.io.InputStreamReader(socket.getInputStream(), "UTF-8"))) { StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1 && isServerRunning) { char ch = (char) c; packetBuilder.append(ch); // 检测到连续两个换行符,表示一个完整的数据包结束 if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); packetBuilder.setLength(0); // 清空构建器 if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); } catch (JSONException e) { Log.w(TAG, "JSON解析失败: " + packet, e); } } } } } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "通信中断", e); runOnUiThread(() -> Toast.makeText(MainActivity.this, "通信中断: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } } finally { closeSocket(socket); } } /** * 处理接收到的数据包 * @param jsonObject 接收到的JSON数据包 */ private void handleReceivedPacket(JSONObject jsonObject) { try { String type = jsonObject.getString("type"); Object data = jsonObject.opt("data"); // 发送消息到主线程进行显示 Message msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); Log.i(TAG, "收到控制指令: " + type); // 根据不同类型执行不同操作 switch (type) { case "start_recording": runOnUiThread(this::startRecording); break; case "stop_recording": runOnUiThread(this::stopRecording); break; case "ping": sendResponse("pong"); break; } } catch (JSONException e) { Log.e(TAG, "处理数据包失败", e); } } /** * 发送响应给客户端 * @param responseType 响应类型 */ private void sendResponse(String responseType) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) return; try { JSONObject response = new JSONObject(); response.put("type", responseType); response.put("data", ""); synchronized (this) { if (socketWriter != null) { socketWriter.write(response.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } Log.i(TAG, "发送响应: " + responseType); } catch (Exception e) { Log.e(TAG, "发送响应失败", e); } } /** * 关闭指定的Socket连接 * @param socket 要关闭的Socket */ private void closeSocket(Socket socket) { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { Log.w(TAG, "关闭Socket失败", e); } // 如果是当前客户端Socket,重置引用 if (socket == clientSocket) { clientSocket = null; synchronized (this) { socketWriter = null; } sendNetworkMessage("客户端断开连接"); } } /** * 关闭服务器Socket */ private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } /** * Activity销毁时调用,释放所有资源 */ @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; // 关闭TTS引擎 if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } // 关闭所有资源 closeServerSocket(); closeSocket(clientSocket); executorService.shutdownNow(); releaseAudioResources(); Log.i(TAG, "应用已销毁"); sendNetworkMessage("服务已停止"); } /** * 权限请求结果回调 * @param requestCode 请求码 * @param permissions 请求的权限数组 * @param grantResults 权限授予结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "录音权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } } }
07-01
package com.ruoyi.nas.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.hierynomus.msdtyp.AccessMask; import com.hierynomus.mssmb2.SMB2CreateDisposition; import com.hierynomus.mssmb2.SMB2ShareAccess; import com.hierynomus.smbj.SMBClient; import com.hierynomus.smbj.SmbConfig; import com.hierynomus.smbj.auth.AuthenticationContext; import com.hierynomus.smbj.connection.Connection; import com.hierynomus.smbj.session.Session; import com.hierynomus.smbj.share.DiskShare; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.nas.domain.NasDownloadLog; import com.ruoyi.nas.domain.NasFileInfo; import com.ruoyi.nas.mapper.NasDownloadLogMapper; import com.ruoyi.nas.mapper.NasFileInfoMapper; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.util.DigestUtils; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; @Service public class FileUploadService { private static final Logger log = LoggerFactory.getLogger(FileUploadService.class); private final SimpMessagingTemplate messagingTemplate; private final NasFileInfoMapper fileInfoMapper; private final NasDownloadLogMapper nasDownloadLogMapper; private static final String HOSTNAME = "10.32.5.80"; // NAS服务器地址 private static final String SHARE_NAME = "AnyshareBAK"; // 共享文件夹名称 private static final String USERNAME = "anyshare"; // 用户名 private static final String PASSWORD = "anybakup$*5.46"; private static final String NAS_UPLOAD_PATH = "/upload"; // NAS 目标路径,可根据需要修改 private static final String BASE_PATH = "/data/uploads/temp"; // 本地临时目录 private final SMBClient client; private Connection connection; private static Session session; public FileUploadService(SimpMessagingTemplate messagingTemplate, NasFileInfoMapper fileInfoMapper, NasDownloadLogMapper nasDownloadLogMapper) { this.messagingTemplate = messagingTemplate; this.fileInfoMapper = fileInfoMapper; this.nasDownloadLogMapper = nasDownloadLogMapper; this.client = new SMBClient(SmbConfig.builder() .withMultiProtocolNegotiate(true) .build()); } public void init() throws IOException { if (connection == null || !connection.isConnected()) { connection = client.connect(HOSTNAME); session = connection.authenticate(new AuthenticationContext(USERNAME, PASSWORD.toCharArray(), "domain")); } } public void saveChunk(MultipartFile chunk, String fileHash, int chunkIndex, int totalChunks) { File dir = new File(BASE_PATH, fileHash); if (!dir.exists()) dir.mkdirs(); File chunkFile = new File(dir, chunkIndex + ".part"); try (InputStream in = chunk.getInputStream(); OutputStream out = new FileOutputStream(chunkFile)) { in.transferTo(out); // ✅ 上傳完成一個分片後推送進度 notifyProgress(fileHash, getUploadedChunks(fileHash).size(), totalChunks); } catch (IOException e) { throw new RuntimeException("Failed to save chunk", e); } } public List<Integer> getUploadedChunks(String fileHash) { File dir = new File(BASE_PATH, fileHash); if (!dir.exists()) return List.of(); return Arrays.stream(Objects.requireNonNull(dir.listFiles())) .map(file -> Integer.parseInt(file.getName().replace(".part", ""))) .collect(Collectors.toList()); } public void mergeChunks(String fileHash, String fileName) { File dir = new File(BASE_PATH, fileHash); File mergedFile = new File(BASE_PATH + "/complete", fileName); mergedFile.getParentFile().mkdirs(); try (OutputStream out = new FileOutputStream(mergedFile)) { List<File> parts = Arrays.stream(Objects.requireNonNull(dir.listFiles())) .sorted(Comparator.comparingInt(f -> Integer.parseInt(f.getName().replace(".part", "")))) .collect(Collectors.toList()); for (File part : parts) { try (InputStream in = new FileInputStream(part)) { in.transferTo(out); } } log.info("File merged successfully: {}", mergedFile.getAbsolutePath()); // 上传合并后的文件到 NAS try (InputStream inputStream = new FileInputStream(mergedFile)) { uploadToNas(inputStream, NAS_UPLOAD_PATH + "/" + fileName); log.info("Upload to NAS complete: {}", fileName); } // 清理临时文件 for (File part : parts) part.delete(); dir.delete(); mergedFile.delete(); // 可选:也清理合并文件 // 合并和上传成功后保存信息到数据库 NasFileInfo info = new NasFileInfo(); info.setFileHash(fileHash); info.setFileName(fileName); info.setFileSize(mergedFile.length()); info.setUploadTime(LocalDateTime.now()); info.setUsername(SecurityUtils.getUsername()); info.setDeptId(SecurityUtils.getDeptId()); info.setNasPath(NAS_UPLOAD_PATH + "/" + fileName); fileInfoMapper.insert(info); } catch (IOException e) { throw new RuntimeException("Merge or upload failed", e); } } private void uploadToNas(InputStream inputStream, String nasPath) throws IOException { init(); try (DiskShare share = (DiskShare) session.connectShare(SHARE_NAME)) { // 分离目录路径和文件名 int lastSlash = nasPath.lastIndexOf('/'); String directoryPath = (lastSlash > 0) ? nasPath.substring(0, lastSlash) : "/"; String fileName = (lastSlash > 0) ? nasPath.substring(lastSlash + 1) : nasPath; // 创建目录结构(如果不存在) createDirectoryIfNotExists(share, directoryPath); // 创建文件并写入数据 try (com.hierynomus.smbj.share.File nasFile = share.openFile( nasPath, EnumSet.of(AccessMask.GENERIC_ALL), null, SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OVERWRITE_IF, // 存在则覆盖 null )) { try (OutputStream nasOutputStream = nasFile.getOutputStream()) { byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { nasOutputStream.write(buffer, 0, bytesRead); } nasOutputStream.flush(); } } } catch (Exception e) { log.error("上传文件到NAS失败: {}", nasPath, e); throw new IOException("NAS上传失败", e); } } private void createDirectoryIfNotExists(DiskShare share, String path) { String[] folders = path.split("/"); String currentPath = ""; for (String folder : folders) { if (folder.isEmpty()) continue; currentPath += "/" + folder; if (!share.folderExists(currentPath)) { share.mkdir(currentPath); } } } public void download(String fileName, HttpServletRequest req, HttpServletResponse resp) throws IOException { init(); try (DiskShare share = (DiskShare) session.connectShare(SHARE_NAME)) { String nasPath = NAS_UPLOAD_PATH + "/" + fileName; if (!share.fileExists(nasPath)) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } //获取到目标文件夹 com.hierynomus.smbj.share.File remoteFile; try { remoteFile = share.openFile(nasPath, EnumSet.of(AccessMask.GENERIC_WRITE), null, SMB2ShareAccess.ALL, SMB2CreateDisposition.FILE_OVERWRITE_IF, null); } catch (Exception e) { log.error("Error opening remote file for writing: {}", nasPath, e); throw new IOException("Failed to open file for writing: " + nasPath); } long fileLength = remoteFile.getFileInformation().getStandardInformation().getEndOfFile(); String range = req.getHeader("Range"); long start = 0, end = fileLength - 1; if (range != null && range.startsWith("bytes=")) { String[] parts = range.replace("bytes=", "").split("-"); start = Long.parseLong(parts[0]); if (parts.length > 1 && !parts[1].isEmpty()) end = Long.parseLong(parts[1]); resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); } else { resp.setStatus(HttpServletResponse.SC_OK); } long contentLength = end - start + 1; resp.setHeader("Content-Type", "application/octet-stream"); resp.setHeader("Content-Length", String.valueOf(contentLength)); resp.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + fileLength); resp.setHeader("Accept-Ranges", "bytes"); try (InputStream smbIn = remoteFile.getInputStream(); ServletOutputStream out = resp.getOutputStream()) { smbIn.skip(start); // 跳过开始位置 byte[] buffer = new byte[8192]; long remaining = contentLength; int len; while ((len = smbIn.read(buffer, 0, (int) Math.min(buffer.length, remaining))) > 0) { out.write(buffer, 0, len); remaining -= len; } out.flush(); } NasFileInfo fileInfo = fileInfoMapper.selectOne( new LambdaQueryWrapper<NasFileInfo>().eq(NasFileInfo::getFileName, fileName) ); if (fileInfo != null) { NasDownloadLog log = new NasDownloadLog(); log.setFileId(fileInfo.getId()); log.setFileName(fileName); log.setUsername(SecurityUtils.getUsername()); // 自行實作獲取使用者名稱 log.setIpAddress(req.getRemoteAddr()); log.setUserAgent(req.getHeader("User-Agent")); log.setDownloadTime(LocalDateTime.now()); nasDownloadLogMapper.insert(log); } } } // 講進度丟該websocket public void notifyProgress(String fileHash, int currentChunk, int total) { messagingTemplate.convertAndSend("/progress/" + fileHash, Map.of("current", currentChunk, "total", total)); } public void uploadFileToNas(MultipartFile[] files) { for (MultipartFile file : files) { if (file.isEmpty()) continue; String originalFilename = file.getOriginalFilename(); String nasPath = NAS_UPLOAD_PATH + "/" + originalFilename; try { // 生成文件哈希(可选) String fileHash = generateFileHash(file); // 直接上传到NAS uploadToNas(file.getInputStream(), nasPath); // 保存文件信息到数据库 NasFileInfo info = new NasFileInfo(); info.setFileHash(fileHash); info.setFileName(originalFilename); info.setFileSize(file.getSize()); info.setUploadTime(LocalDateTime.now()); info.setUsername(SecurityUtils.getUsername()); info.setDeptId(SecurityUtils.getDeptId()); info.setNasPath(nasPath); fileInfoMapper.insert(info); } catch (IOException e) { log.error("文件上传失败: {}", originalFilename, e); // 可以根据需要添加重试机制或更详细的错误处理 } } } // 生成文件哈希(MD5示例) private String generateFileHash(MultipartFile file) throws IOException { try (InputStream is = file.getInputStream()) { return DigestUtils.md5DigestAsHex(is); } catch (Exception e) { log.warn("MD5算法不可用,使用随机哈希替代"); return UUID.randomUUID().toString(); } } } 使用init来连接nas行不通,还有别的方法可以连接吗
最新发布
07-23
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值