diff --git a/custom_components/fn_nas/coordinator.py b/custom_components/fn_nas/coordinator.py index 9426cc2..0a41231 100644 --- a/custom_components/fn_nas/coordinator.py +++ b/custom_components/fn_nas/coordinator.py @@ -1,4 +1,3 @@ -# coordinator.py (文档9) import logging import asyncio import asyncssh @@ -36,6 +35,10 @@ class FlynasCoordinator(DataUpdateCoordinator): self.docker_manager = DockerManager(self) if self.enable_docker else None self.ssh = None self.ssh_closed = True + # SSH连接池管理 + self.ssh_pool = [] + self.ssh_pool_size = 3 # 连接池大小 + self.ssh_pool_lock = asyncio.Lock() self.ups_manager = UPSManager(self) self.vm_manager = VMManager(self) self.use_sudo = False @@ -61,6 +64,9 @@ class FlynasCoordinator(DataUpdateCoordinator): self._last_command_time = 0 self._command_count = 0 + # 添加日志方法 + self.debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) + def get_default_data(self): """返回默认的数据结构""" return { @@ -76,148 +82,245 @@ class FlynasCoordinator(DataUpdateCoordinator): "docker_containers": [] } - async def async_connect(self): - """建立并保持持久SSH连接""" - if self.ssh is not None and not self.ssh_closed: - try: - # 测试连接是否仍然活跃 - await self.ssh.run("echo 'connection_test'", timeout=1) - return True - except (asyncssh.Error, TimeoutError): - _LOGGER.debug("现有连接失效,准备重建") - await self.async_disconnect() - + def _debug_log(self, message: str): + """只在调试模式下输出详细日志""" + if self.debug_enabled: + _LOGGER.debug(message) + + def _info_log(self, message: str): + """重要信息日志""" + _LOGGER.info(message) + + def _warning_log(self, message: str): + """警告日志""" + _LOGGER.warning(message) + + def _error_log(self, message: str): + """错误日志""" + _LOGGER.error(message) + + async def get_ssh_connection(self): + """从连接池获取可用的SSH连接""" + async with self.ssh_pool_lock: + # 检查现有连接 + for i, (ssh, in_use) in enumerate(self.ssh_pool): + if not in_use: + try: + # 测试连接是否活跃 + await asyncio.wait_for(ssh.run("echo 'test'", timeout=1), timeout=2) + self.ssh_pool[i] = (ssh, True) # 标记为使用中 + self._debug_log(f"复用连接池中的连接 {i}") + return ssh, i + except Exception: + # 连接失效,移除 + try: + ssh.close() + except: + pass + self.ssh_pool.pop(i) + break + + # 如果连接池未满,创建新连接 + if len(self.ssh_pool) < self.ssh_pool_size: + try: + ssh = await asyncssh.connect( + self.host, + port=self.port, + username=self.username, + password=self.password, + known_hosts=None, + connect_timeout=5 + ) + + # 检查并设置权限状态 + await self._setup_connection_permissions(ssh) + + connection_id = len(self.ssh_pool) + self.ssh_pool.append((ssh, True)) + self._debug_log(f"创建新的SSH连接 {connection_id}") + return ssh, connection_id + except Exception as e: + self._debug_log(f"创建SSH连接失败: {e}") + raise + + # 连接池满且所有连接都在使用中,等待可用连接 + self._debug_log("所有连接都在使用中,等待可用连接...") + for _ in range(50): # 最多等待5秒 + await asyncio.sleep(0.1) + for i, (ssh, in_use) in enumerate(self.ssh_pool): + if not in_use: + try: + await asyncio.wait_for(ssh.run("echo 'test'", timeout=1), timeout=2) + self.ssh_pool[i] = (ssh, True) + self._debug_log(f"等待后获得连接 {i}") + return ssh, i + except Exception: + try: + ssh.close() + except: + pass + self.ssh_pool.pop(i) + break + + raise Exception("无法获取SSH连接") + + async def _setup_connection_permissions(self, ssh): + """为新连接设置权限状态""" try: - self.ssh = await asyncssh.connect( - self.host, - port=self.port, - username=self.username, - password=self.password, - known_hosts=None, - connect_timeout=5 - ) - - self.ssh_closed = False - _LOGGER.info("已建立持久SSH连接到 %s", self.host) - - # 检查权限状态 - if await self.is_root_user(): - _LOGGER.debug("当前用户是 root") + # 检查是否为root用户 + result = await ssh.run("id -u", timeout=3) + if result.stdout.strip() == "0": + self._debug_log("当前用户是 root") self.use_sudo = False - else: - # 尝试切换到root会话 - if await self.try_switch_to_root(): - self.use_sudo = False + return - return True - - except Exception as e: - self.ssh = None - self.ssh_closed = True - _LOGGER.debug("连接失败: %s", str(e)) - return False - - async def try_switch_to_root(self): - """尝试切换到root会话""" - try: + # 尝试切换到root会话 if self.root_password: - result = await self.ssh.run( - f"echo '{self.root_password}' | sudo -S -i", - input=self.root_password + "\n", + try: + await ssh.run( + f"echo '{self.root_password}' | sudo -S -i", + input=self.root_password + "\n", + timeout=5 + ) + whoami = await ssh.run("whoami") + if "root" in whoami.stdout: + self._info_log("成功切换到 root 会话(使用 root 密码)") + self.use_sudo = False + return + except Exception: + pass + + # 尝试使用登录密码sudo + try: + await ssh.run( + f"echo '{self.password}' | sudo -S -i", + input=self.password + "\n", timeout=5 ) - whoami = await self.ssh.run("whoami") + whoami = await ssh.run("whoami") if "root" in whoami.stdout: - _LOGGER.info("成功切换到 root 会话(使用 root 密码)") - return True - - result = await self.ssh.run( - f"echo '{self.password}' | sudo -S -i", - input=self.password + "\n", - timeout=5 - ) - whoami = await self.ssh.run("whoami") - if "root" in whoami.stdout: - _LOGGER.info("成功切换到 root 会话(使用登录密码)") - return True + self._info_log("成功切换到 root 会话(使用登录密码)") + self.use_sudo = False + return + except Exception: + pass + # 设置为使用sudo模式 self.use_sudo = True - return False - except Exception: + self._debug_log("设置为使用sudo模式") + + except Exception as e: + self._debug_log(f"设置连接权限失败: {e}") self.use_sudo = True - return False + + async def release_ssh_connection(self, connection_id): + """释放SSH连接回连接池""" + async with self.ssh_pool_lock: + if 0 <= connection_id < len(self.ssh_pool): + ssh, _ = self.ssh_pool[connection_id] + self.ssh_pool[connection_id] = (ssh, False) # 标记为可用 + self._debug_log(f"释放SSH连接 {connection_id}") - async def is_root_user(self): + async def close_all_ssh_connections(self): + """关闭所有SSH连接""" + async with self.ssh_pool_lock: + for ssh, _ in self.ssh_pool: + try: + ssh.close() + except: + pass + self.ssh_pool.clear() + self._debug_log("已关闭所有SSH连接") + + async def async_connect(self): + """建立并保持持久SSH连接 - 兼容旧代码""" try: - result = await self.ssh.run("id -u", timeout=3) - return result.stdout.strip() == "0" + ssh, connection_id = await self.get_ssh_connection() + await self.release_ssh_connection(connection_id) + return True except Exception: return False async def async_disconnect(self): - """断开SSH连接""" - if self.ssh is not None and not self.ssh_closed: - try: - self.ssh.close() - self.ssh_closed = True - _LOGGER.debug("已关闭SSH连接") - except Exception as e: - _LOGGER.debug("关闭SSH连接时出错: %s", str(e)) - finally: - self.ssh = None + """断开SSH连接 - 兼容旧代码""" + await self.close_all_ssh_connections() async def run_command(self, command: str, retries=2) -> str: - """执行SSH命令,使用持久连接""" - current_time = asyncio.get_event_loop().time() - - # 连接冷却机制:避免短时间内频繁创建新连接 - if current_time - self._last_command_time < 1.0 and self._command_count > 5: - await asyncio.sleep(0.5) - - self._last_command_time = current_time - self._command_count += 1 - + """执行SSH命令,使用连接池""" # 系统离线时直接返回空字符串 if not self._system_online: return "" - + + ssh = None + connection_id = None + try: - # 确保连接有效 - if not await self.async_connect(): - return "" - - # 使用sudo执行命令 + # 从连接池获取连接 + ssh, connection_id = await self.get_ssh_connection() + + # 构建完整命令 if self.use_sudo: if self.root_password or self.password: password = self.root_password if self.root_password else self.password full_command = f"sudo -S {command}" - result = await self.ssh.run(full_command, input=password + "\n", timeout=10) + result = await ssh.run(full_command, input=password + "\n", timeout=10) else: full_command = f"sudo {command}" - result = await self.ssh.run(full_command, timeout=10) + result = await ssh.run(full_command, timeout=10) else: - result = await self.ssh.run(command, timeout=10) - + result = await ssh.run(command, timeout=10) + return result.stdout.strip() - except (asyncssh.Error, TimeoutError) as e: - _LOGGER.debug("命令执行失败: %s, 错误: %s", command, str(e)) - # 标记连接失效 - self.ssh_closed = True - return "" except Exception as e: - _LOGGER.debug("执行命令时出现意外错误: %s", str(e)) - self.ssh_closed = True + self._debug_log(f"命令执行失败: {command}, 错误: {str(e)}") return "" + + finally: + # 释放连接回连接池 + if connection_id is not None: + await self.release_ssh_connection(connection_id) + + async def run_command_direct(self, command: str) -> str: + """直接执行命令,获取独立连接 - 用于并发任务""" + if not self._system_online: + return "" + + ssh = None + connection_id = None + + try: + ssh, connection_id = await self.get_ssh_connection() + + if self.use_sudo: + if self.root_password or self.password: + password = self.root_password if self.root_password else self.password + full_command = f"sudo -S {command}" + result = await ssh.run(full_command, input=password + "\n", timeout=10) + else: + full_command = f"sudo {command}" + result = await ssh.run(full_command, timeout=10) + else: + result = await ssh.run(command, timeout=10) + + return result.stdout.strip() + + except Exception as e: + self._debug_log(f"直接命令执行失败: {command}, 错误: {str(e)}") + return "" + + finally: + if connection_id is not None: + await self.release_ssh_connection(connection_id) async def _monitor_system_status(self): """系统离线时轮询检测状态""" - self.logger.debug("启动系统状态监控,每%d秒检测一次", self._retry_interval) + self._debug_log(f"启动系统状态监控,每{self._retry_interval}秒检测一次") while True: await asyncio.sleep(self._retry_interval) if await self.ping_system(): - self.logger.info("检测到系统已开机,触发重新加载") + self._info_log("检测到系统已开机,触发重新加载") # 触发集成重新加载 self.hass.async_create_task( self.hass.config_entries.async_reload(self.config_entry.entry_id) @@ -244,45 +347,60 @@ class FlynasCoordinator(DataUpdateCoordinator): async def _async_update_data(self): """数据更新入口,优化命令执行频率""" - _LOGGER.debug("开始数据更新...") + self._debug_log("开始数据更新...") is_online = await self.ping_system() self._system_online = is_online if not is_online: - _LOGGER.debug("系统离线,跳过数据更新") + self._debug_log("系统离线,跳过数据更新") # 启动后台监控任务 if not self._ping_task or self._ping_task.done(): self._ping_task = asyncio.create_task(self._monitor_system_status()) - await self.async_disconnect() + await self.close_all_ssh_connections() return self.get_default_data() # 系统在线处理 try: - # 确保连接有效 - if not await self.async_connect(): - return self.get_default_data() - + # 预热连接池并确保权限设置正确 + await self.async_connect() + # 获取系统状态信息 status = "on" - # 并行获取磁盘、UPS和系统信息 - system_task = asyncio.create_task(self.system_manager.get_system_info()) - disks_task = asyncio.create_task(self.disk_manager.get_disks_info()) - ups_task = asyncio.create_task(self.ups_manager.get_ups_info()) - vms_task = asyncio.create_task(self.vm_manager.get_vm_list()) - # 等待并行任务完成 - system, disks, ups_info, vms = await asyncio.gather( - system_task, disks_task, ups_task, vms_task - ) + # 串行获取信息以确保稳定性 + self._debug_log("开始获取系统信息...") + system = await self.system_manager.get_system_info() + self._debug_log("系统信息获取完成") + + self._debug_log("开始获取磁盘信息...") + disks = await self.disk_manager.get_disks_info() + self._debug_log(f"磁盘信息获取完成,数量: {len(disks)}") + + self._debug_log("开始获取UPS信息...") + ups_info = await self.ups_manager.get_ups_info() + self._debug_log("UPS信息获取完成") + + self._debug_log("开始获取虚拟机信息...") + vms = await self.vm_manager.get_vm_list() + self._debug_log(f"虚拟机信息获取完成,数量: {len(vms)}") # 为每个虚拟机获取标题 for vm in vms: - vm["title"] = await self.vm_manager.get_vm_title(vm["name"]) + try: + vm["title"] = await self.vm_manager.get_vm_title(vm["name"]) + except Exception as e: + self._debug_log(f"获取VM标题失败 {vm['name']}: {e}") + vm["title"] = vm["name"] # 获取Docker容器信息 docker_containers = [] - if self.enable_docker: - docker_containers = await self.docker_manager.get_containers() + if self.enable_docker and self.docker_manager: + self._debug_log("开始获取Docker信息...") + try: + docker_containers = await self.docker_manager.get_containers() + self._debug_log(f"Docker信息获取完成,数量: {len(docker_containers)}") + except Exception as e: + self._debug_log(f"Docker信息获取失败: {e}") data = { "disks": disks, @@ -292,40 +410,16 @@ class FlynasCoordinator(DataUpdateCoordinator): "docker_containers": docker_containers } + self._debug_log(f"数据更新完成: disks={len(disks)}, vms={len(vms)}, containers={len(docker_containers)}") return data except Exception as e: - _LOGGER.debug("数据更新失败: %s", str(e)) + self._error_log(f"数据更新失败: {str(e)}") self._system_online = False if not self._ping_task or self._ping_task.done(): self._ping_task = asyncio.create_task(self._monitor_system_status()) return self.get_default_data() - - def get_default_data(self): - """获取默认数据(离线状态)""" - return { - "disks": [], - "system": { - "uptime": "未知", - "cpu_temperature": "未知", - "motherboard_temperature": "未知", - "status": "off" - }, - "ups": {}, - "vms": [], - "docker_containers": [] - } - - async def reboot_system(self): - await self.system_manager.reboot_system() - - async def shutdown_system(self): - await self.system_manager.shutdown_system() - # 更新状态,但使用安全的方式 - if self.data and isinstance(self.data, dict) and "system" in self.data: - self.data["system"]["status"] = "off" - self.async_update_listeners() class UPSDataUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass: HomeAssistant, config, main_coordinator): @@ -357,10 +451,7 @@ class UPSDataUpdateCoordinator(DataUpdateCoordinator): async def control_vm(self, vm_name, action): try: - if not hasattr(self, 'vm_manager'): - self.vm_manager = VMManager(self) - - result = await self.vm_manager.control_vm(vm_name, action) + result = await self.main_coordinator.vm_manager.control_vm(vm_name, action) return result except Exception as e: _LOGGER.debug("虚拟机控制失败: %s", str(e)) diff --git a/custom_components/fn_nas/manifest.json b/custom_components/fn_nas/manifest.json index 5e79f57..3176802 100644 --- a/custom_components/fn_nas/manifest.json +++ b/custom_components/fn_nas/manifest.json @@ -1,7 +1,7 @@ { "domain": "fn_nas", "name": "飞牛NAS", - "version": "1.3.5", + "version": "1.3.6", "documentation": "https://github.com/anxms/fn_nas", "dependencies": [], "codeowners": ["@anxms"], diff --git a/custom_components/fn_nas/system_manager.py b/custom_components/fn_nas/system_manager.py index 86f27c8..942a4c5 100644 --- a/custom_components/fn_nas/system_manager.py +++ b/custom_components/fn_nas/system_manager.py @@ -9,10 +9,41 @@ class SystemManager: def __init__(self, coordinator): self.coordinator = coordinator self.logger = _LOGGER.getChild("system_manager") - self.logger.setLevel(logging.DEBUG) - self.debug_enabled = False # 调试模式开关 - self.sensors_debug_path = "/config/fn_nas_debug" # 调试文件保存路径 - + # 根据Home Assistant的日志级别动态设置 + self.logger.setLevel(logging.DEBUG if _LOGGER.isEnabledFor(logging.DEBUG) else logging.INFO) + self.debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) # 基于HA调试模式 + self.sensors_debug_path = "/config/fn_nas_debug" + + # 温度传感器缓存 + self.cpu_temp_cache = { + "hwmon_id": None, + "temp_id": None, + "driver_type": None, + "label": None + } + self.mobo_temp_cache = { + "hwmon_id": None, + "temp_id": None, + "label": None + } + + def _debug_log(self, message: str): + """只在调试模式下输出详细日志""" + if self.debug_enabled: + self.logger.debug(message) + + def _info_log(self, message: str): + """重要信息日志""" + self.logger.info(message) + + def _warning_log(self, message: str): + """警告日志""" + self.logger.warning(message) + + def _error_log(self, message: str): + """错误日志""" + self.logger.error(message) + async def get_system_info(self) -> dict: """获取系统信息""" system_info = {} @@ -31,13 +62,10 @@ class SystemManager: system_info["uptime_seconds"] = 0 system_info["uptime"] = "未知" - # 只通过内核方式获取温度 - cpu_temp = await self.get_cpu_temp_from_kernel() - system_info["cpu_temperature"] = cpu_temp - - mobo_temp = await self.get_mobo_temp_from_kernel() - system_info["motherboard_temperature"] = mobo_temp - + # 一次性获取CPU和主板温度 + temps = await self.get_temperatures_from_sensors() + system_info["cpu_temperature"] = temps["cpu"] + system_info["motherboard_temperature"] = temps["motherboard"] mem_info = await self.get_memory_info() system_info.update(mem_info) @@ -58,41 +86,163 @@ class SystemManager: "volumes": {} } + async def get_temperatures_from_sensors(self) -> dict: + """一次性获取CPU和主板温度""" + try: + command = "sensors" + self._debug_log(f"执行sensors命令获取温度: {command}") + + sensors_output = await self.coordinator.run_command(command) + if self.debug_enabled: + self._debug_log(f"sensors命令输出长度: {len(sensors_output) if sensors_output else 0}") + + if not sensors_output: + self._warning_log("sensors命令无输出") + return {"cpu": "未知", "motherboard": "未知"} + + # 同时解析CPU和主板温度 + cpu_temp = self.extract_cpu_temp_from_sensors(sensors_output) + mobo_temp = self.extract_mobo_temp_from_sensors(sensors_output) + + # 记录获取结果 + if cpu_temp != "未知": + self._info_log(f"通过sensors获取CPU温度成功: {cpu_temp}") + else: + self._warning_log("sensors命令未找到CPU温度") + + if mobo_temp != "未知": + self._info_log(f"通过sensors获取主板温度成功: {mobo_temp}") + else: + self._warning_log("sensors命令未找到主板温度") + + return {"cpu": cpu_temp, "motherboard": mobo_temp} + + except Exception as e: + self._error_log(f"使用sensors命令获取温度失败: {e}") + return {"cpu": "未知", "motherboard": "未知"} + async def get_cpu_temp_from_kernel(self) -> str: - # 获取CPU温度 - for i in range(5): - for j in range(5): - label_path = f"/sys/class/hwmon/hwmon{i}/temp{j}_label" - label = await self.coordinator.run_command(f"cat {label_path} 2>/dev/null") - if label and ("cpu" in label.lower() or "package" in label.lower()): - temp_path = f"/sys/class/hwmon/hwmon{i}/temp{j}_input" - temp_str = await self.coordinator.run_command(f"cat {temp_path} 2>/dev/null") - if temp_str and temp_str.isdigit(): - temp = float(temp_str) / 1000.0 - return f"{temp:.1f} °C" - return "未知" + """获取CPU温度 - 向后兼容""" + temps = await self.get_temperatures_from_sensors() + return temps["cpu"] async def get_mobo_temp_from_kernel(self) -> str: - # 获取主板温度 - for i in range(5): - for j in range(5): - label_path = f"/sys/class/hwmon/hwmon{i}/temp{j}_label" - label = await self.coordinator.run_command(f"cat {label_path} 2>/dev/null") - if label and ("mobo" in label.lower() or "mb" in label.lower() or "sys" in label.lower() or "pch" in label.lower()): - temp_path = f"/sys/class/hwmon/hwmon{i}/temp{j}_input" - temp_str = await self.coordinator.run_command(f"cat {temp_path} 2>/dev/null") - if temp_str and temp_str.isdigit(): - temp = float(temp_str) / 1000.0 - return f"{temp:.1f} °C" - return "未知" - - def extract_cpu_temp(self, sensors_output: str) -> str: - """兼容旧接口,直接返回未知""" - return "未知" + """获取主板温度 - 向后兼容""" + temps = await self.get_temperatures_from_sensors() + return temps["motherboard"] - def extract_mobo_temp(self, sensors_output: str) -> str: - """兼容旧接口,直接返回未知""" - return "未知" + async def get_cpu_temp_from_sensors(self) -> str: + """使用sensors命令获取CPU温度 - 向后兼容""" + temps = await self.get_temperatures_from_sensors() + return temps["cpu"] + + async def get_mobo_temp_from_sensors(self) -> str: + """使用sensors命令获取主板温度 - 向后兼容""" + temps = await self.get_temperatures_from_sensors() + return temps["motherboard"] + + def extract_cpu_temp_from_sensors(self, sensors_output: str) -> str: + """从sensors输出中提取CPU温度""" + try: + lines = sensors_output.split('\n') + self._debug_log(f"解析sensors输出,共{len(lines)}行") + + for i, line in enumerate(lines): + line_lower = line.lower().strip() + if self.debug_enabled: + self._debug_log(f"第{i+1}行: {line_lower}") + + # AMD CPU温度关键词 + if any(keyword in line_lower for keyword in [ + "tctl", "tdie", "k10temp" + ]): + self._debug_log(f"找到AMD CPU温度行: {line}") + if '+' in line and '°c' in line_lower: + try: + temp_match = line.split('+')[1].split('°')[0].strip() + temp = float(temp_match) + if 0 < temp < 150: + self._info_log(f"从sensors提取AMD CPU温度: {temp:.1f}°C") + return f"{temp:.1f} °C" + except (ValueError, IndexError) as e: + self._debug_log(f"解析AMD温度失败: {e}") + continue + + # Intel CPU温度关键词 + if any(keyword in line_lower for keyword in [ + "package id", "core 0", "coretemp" + ]) and not any(exclude in line_lower for exclude in ["fan"]): + self._debug_log(f"找到Intel CPU温度行: {line}") + if '+' in line and '°c' in line_lower: + try: + temp_match = line.split('+')[1].split('°')[0].strip() + temp = float(temp_match) + if 0 < temp < 150: + self._info_log(f"从sensors提取Intel CPU温度: {temp:.1f}°C") + return f"{temp:.1f} °C" + except (ValueError, IndexError) as e: + self._debug_log(f"解析Intel温度失败: {e}") + continue + + # 通用CPU温度模式 + if ('cpu' in line_lower or 'processor' in line_lower) and '+' in line and '°c' in line_lower: + self._debug_log(f"找到通用CPU温度行: {line}") + try: + temp_match = line.split('+')[1].split('°')[0].strip() + temp = float(temp_match) + if 0 < temp < 150: + self._info_log(f"从sensors提取通用CPU温度: {temp:.1f}°C") + return f"{temp:.1f} °C" + except (ValueError, IndexError) as e: + self._debug_log(f"解析通用CPU温度失败: {e}") + continue + + self._warning_log("未在sensors输出中找到CPU温度") + return "未知" + + except Exception as e: + self._error_log(f"解析sensors CPU温度输出失败: {e}") + return "未知" + + def extract_mobo_temp_from_sensors(self, sensors_output: str) -> str: + """从sensors输出中提取主板温度""" + try: + lines = sensors_output.split('\n') + self._debug_log(f"解析主板温度,共{len(lines)}行") + + for i, line in enumerate(lines): + line_lower = line.lower().strip() + + # 主板温度关键词 + if any(keyword in line_lower for keyword in [ + "motherboard", "mobo", "mb", "system", "chipset", + "ambient", "temp1:", "temp2:", "temp3:", "systin" + ]) and not any(cpu_keyword in line_lower for cpu_keyword in [ + "cpu", "core", "package", "processor", "tctl", "tdie" + ]) and not any(exclude in line_lower for exclude in ["fan", "rpm"]): + + self._debug_log(f"找到可能的主板温度行: {line}") + + if '+' in line and '°c' in line_lower: + try: + temp_match = line.split('+')[1].split('°')[0].strip() + temp = float(temp_match) + # 主板温度通常在15-70度之间 + if 15 <= temp <= 70: + self._info_log(f"从sensors提取主板温度: {temp:.1f}°C") + return f"{temp:.1f} °C" + else: + self._debug_log(f"主板温度值超出合理范围: {temp:.1f}°C") + except (ValueError, IndexError) as e: + self._debug_log(f"解析主板温度失败: {e}") + continue + + self._warning_log("未在sensors输出中找到主板温度") + return "未知" + + except Exception as e: + self._error_log(f"解析sensors主板温度输出失败: {e}") + return "未知" def format_uptime(self, seconds: float) -> str: """格式化运行时间为易读格式""" @@ -139,7 +289,7 @@ class SystemManager: } except Exception as e: - self.logger.error("获取内存信息失败: %s", str(e)) + self._error_log(f"获取内存信息失败: {str(e)}") return {} async def get_vol_usage(self) -> dict: @@ -229,28 +379,28 @@ class SystemManager: async def reboot_system(self): """重启系统""" - self.logger.info("Initiating system reboot...") + self._info_log("Initiating system reboot...") try: await self.coordinator.run_command("sudo reboot") - self.logger.info("Reboot command sent") + self._info_log("Reboot command sent") if "system" in self.coordinator.data: self.coordinator.data["system"]["status"] = "rebooting" self.coordinator.async_update_listeners() except Exception as e: - self.logger.error("Failed to reboot system: %s", str(e)) + self._error_log(f"Failed to reboot system: {str(e)}") raise async def shutdown_system(self): """关闭系统""" - self.logger.info("Initiating system shutdown...") + self._info_log("Initiating system shutdown...") try: await self.coordinator.run_command("sudo shutdown -h now") - self.logger.info("Shutdown command sent") + self._info_log("Shutdown command sent") if "system" in self.coordinator.data: self.coordinator.data["system"]["status"] = "off" self.coordinator.async_update_listeners() except Exception as e: - self.logger.error("Failed to shutdown system: %s", str(e)) + self._error_log(f"Failed to shutdown system: {str(e)}") raise \ No newline at end of file diff --git a/custom_components/fn_nas/ups_manager.py b/custom_components/fn_nas/ups_manager.py index 3a38c2a..f79359c 100644 --- a/custom_components/fn_nas/ups_manager.py +++ b/custom_components/fn_nas/ups_manager.py @@ -11,9 +11,27 @@ class UPSManager: def __init__(self, coordinator): self.coordinator = coordinator self.logger = _LOGGER.getChild("ups_manager") - self.logger.setLevel(logging.DEBUG) - self.debug_enabled = False # UPS调试模式开关 - self.ups_debug_path = "/config/fn_nas_ups_debug" # UPS调试文件保存路径 + # 根据Home Assistant的日志级别动态设置 + self.logger.setLevel(logging.DEBUG if _LOGGER.isEnabledFor(logging.DEBUG) else logging.INFO) + self.debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) # 基于HA调试模式 + self.ups_debug_path = "/config/fn_nas_ups_debug" + + def _debug_log(self, message: str): + """只在调试模式下输出详细日志""" + if self.debug_enabled: + self.logger.debug(message) + + def _info_log(self, message: str): + """重要信息日志""" + self.logger.info(message) + + def _warning_log(self, message: str): + """警告日志""" + self.logger.warning(message) + + def _error_log(self, message: str): + """错误日志""" + self.logger.error(message) async def get_ups_info(self) -> dict: """获取连接的UPS信息""" @@ -31,7 +49,7 @@ class UPSManager: try: # 尝试使用NUT工具获取UPS信息 - self.logger.debug("尝试使用NUT工具获取UPS信息") + self._debug_log("尝试使用NUT工具获取UPS信息") output = await self.coordinator.run_command("upsc -l") if output and "No such file" not in output: @@ -39,11 +57,11 @@ class UPSManager: ups_names = output.splitlines() if ups_names: ups_name = ups_names[0].strip() - self.logger.debug("发现UPS: %s", ups_name) + self._debug_log(f"发现UPS: {ups_name}") # 获取详细的UPS信息 ups_details = await self.coordinator.run_command(f"upsc {ups_name}") - self.logger.debug("UPS详细信息: %s", ups_details) + self._debug_log(f"UPS详细信息: {ups_details}") # 保存UPS数据以便调试 self.save_ups_data_for_debug(ups_details) @@ -51,20 +69,20 @@ class UPSManager: # 解析UPS信息 return self.parse_nut_ups_info(ups_details) else: - self.logger.debug("未找到连接的UPS") + self._debug_log("未找到连接的UPS") else: - self.logger.debug("未安装NUT工具,尝试备用方法") + self._debug_log("未安装NUT工具,尝试备用方法") # 备用方法:尝试直接读取UPS状态 return await self.get_ups_info_fallback() except Exception as e: - self.logger.error("获取UPS信息时出错: %s", str(e), exc_info=True) + self._error_log(f"获取UPS信息时出错: {str(e)}") return ups_info async def get_ups_info_fallback(self) -> dict: """备用方法获取UPS信息""" - self.logger.info("尝试备用方法获取UPS信息") + self._info_log("尝试备用方法获取UPS信息") ups_info = { "status": "未知", "battery_level": "未知", @@ -81,7 +99,7 @@ class UPSManager: # 方法1: 检查USB连接的UPS usb_ups_output = await self.coordinator.run_command("lsusb | grep -i ups || echo 'No USB UPS'") if usb_ups_output and "No USB UPS" not in usb_ups_output: - self.logger.debug("检测到USB UPS设备: %s", usb_ups_output) + self._debug_log(f"检测到USB UPS设备: {usb_ups_output}") ups_info["ups_type"] = "USB" # 尝试从输出中提取型号 @@ -111,7 +129,7 @@ class UPSManager: return ups_info except Exception as e: - self.logger.error("备用方法获取UPS信息失败: %s", str(e)) + self._error_log(f"备用方法获取UPS信息失败: {str(e)}") return ups_info def parse_nut_ups_info(self, ups_output: str) -> dict: @@ -253,6 +271,6 @@ class UPSManager: with open(filename, "w") as f: f.write(ups_output) - self.logger.info("保存UPS数据到 %s 用于调试", filename) + self._info_log(f"保存UPS数据到 {filename} 用于调试") except Exception as e: - self.logger.error("保存UPS数据失败: %s", str(e)) \ No newline at end of file + self._error_log(f"保存UPS数据失败: {str(e)}") \ No newline at end of file diff --git a/custom_components/fn_nas/vm_manager.py b/custom_components/fn_nas/vm_manager.py index 996dd5a..20a6777 100644 --- a/custom_components/fn_nas/vm_manager.py +++ b/custom_components/fn_nas/vm_manager.py @@ -8,15 +8,40 @@ class VMManager: def __init__(self, coordinator): self.coordinator = coordinator self.vms = [] + self.logger = _LOGGER.getChild("vm_manager") + # 根据Home Assistant的日志级别动态设置 + self.logger.setLevel(logging.DEBUG if _LOGGER.isEnabledFor(logging.DEBUG) else logging.INFO) + self.debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) + + def _debug_log(self, message: str): + """只在调试模式下输出详细日志""" + if self.debug_enabled: + self.logger.debug(message) + + def _info_log(self, message: str): + """重要信息日志""" + self.logger.info(message) + + def _warning_log(self, message: str): + """警告日志""" + self.logger.warning(message) + + def _error_log(self, message: str): + """错误日志""" + self.logger.error(message) async def get_vm_list(self): """获取虚拟机列表及其状态""" try: + self._debug_log("开始获取虚拟机列表") output = await self.coordinator.run_command("virsh list --all") + self._debug_log(f"virsh命令输出: {output}") + self.vms = self._parse_vm_list(output) + self._info_log(f"获取到{len(self.vms)}个虚拟机") return self.vms except Exception as e: - _LOGGER.error("获取虚拟机列表失败: %s", str(e)) + self._error_log(f"获取虚拟机列表失败: {str(e)}") return [] def _parse_vm_list(self, output): @@ -43,14 +68,18 @@ class VMManager: async def get_vm_title(self, vm_name): """获取虚拟机的标题""" try: + self._debug_log(f"获取虚拟机{vm_name}的标题") output = await self.coordinator.run_command(f"virsh dumpxml {vm_name}") # 在XML输出中查找