#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
系统诊断模块（本机执行）
移植自 auto_diag/diagnostic_tests.py，改为本地直接执行
"""

import datetime
import json
import os
import re
import shutil
import socket
import subprocess
import time
from pathlib import Path
from typing import Any, Dict, List, Optional


def _run_cmd(command: str, timeout: int = 30) -> Dict[str, Any]:
    """本地执行命令，替代原 SSH execute_command"""
    try:
        result = subprocess.run(
            command,
            shell=True,
            capture_output=True,
            text=True,
            timeout=timeout,
            executable="/bin/bash",
        )
        return {
            "success": result.returncode == 0,
            "exit_code": result.returncode,
            "stdout": result.stdout,
            "stderr": result.stderr,
        }
    except subprocess.TimeoutExpired:
        return {
            "success": False,
            "exit_code": -1,
            "stdout": "",
            "stderr": "命令执行超时",
        }
    except Exception as e:
        return {
            "success": False,
            "exit_code": -1,
            "stdout": "",
            "stderr": str(e),
        }


# 测试项定义（id, name, method_name）
TEST_ITEMS: List[tuple] = [
    ("check_network_interface", "网卡设置检查"),
    ("check_lidar_connection",  "雷达连通性检查"),
    ("check_lidar_data_rate",   "雷达数据频率检查"),
    ("check_hostname",          "主机名格式检查"),
    ("check_frpc_config",       "frpc配置检查"),
    ("check_sysd_ctrl",         "sysd-ctrl检查"),
    ("check_px4ctrl_node",      "主控程序检查"),
    ("check_gxzn_certificate",  "主控证书检查"),
    ("check_fsm_state",         "FSM状态检查"),
    ("check_pm",                "PM检查"),
    ("check_ws_server",         "WS Server检查"),
    ("check_upload_server",     "Updater检查"),
    ("check_disk_space",        "磁盘容量检查"),
    ("check_camera_stream",     "相机流可访问性检查"),
    ("check_log_output_config", "日志输出配置检查"),
    ("check_cpu_temperature",   "CPU温度检查"),
]


class LocalDiagnosticTests:
    def __init__(self):
        pass
        
    def check_network_interface(self):
        """
        检查网卡设置
        检测是否有网卡的 IP 地址为 192.168.1.25
        """
        target_ip = "192.168.1.25"
        
        try:
            # 执行 ip addr 命令获取所有网络接口信息
            result = _run_cmd("ip addr show")
            
            if not result['success']:
                return {
                    'success': False,
                    'message': '[X] 无法获取网卡信息',
                    'details': result['stderr']
                }
            
            output = result['stdout']
            
            # 解析网络接口信息
            interfaces = self._parse_ip_addr(output)
            
            # 查找目标 IP
            found_interface = None
            for iface_name, iface_info in interfaces.items():
                if target_ip in iface_info['ip_addresses']:
                    found_interface = iface_name
                    break
            
            # 生成详细信息
            details = "网卡信息:\n"
            for iface_name, iface_info in interfaces.items():
                details += "\n接口: {}\n".format(iface_name)
                details += "  状态: {}\n".format(iface_info['state'])
                if iface_info['mac']:
                    details += "  MAC: {}\n".format(iface_info['mac'])
                if iface_info['ip_addresses']:
                    details += "  IP地址:\n"
                    for ip in iface_info['ip_addresses']:
                        marker = " [OK] [目标IP]" if ip == target_ip else ""
                        details += "    - {}{}\n".format(ip, marker)
                else:
                    details += "  IP地址: 无\n"
            
            if found_interface:
                return {
                    'success': True,
                    'message': '[OK] 网卡检查通过: 找到IP地址 {} 在接口 {}'.format(target_ip, found_interface),
                    'details': details,
                    'interface': found_interface
                }
            else:
                return {
                    'success': False,
                    'message': '[X] 网卡检查失败: 未找到IP地址 {}'.format(target_ip),
                    'details': details
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 网卡检查出错',
                'details': str(e)
            }
    
    def _parse_ip_addr(self, output):
        """
        解析 ip addr show 命令的输出
        返回格式: {interface_name: {state, mac, ip_addresses}}
        """
        interfaces = {}
        current_interface = None
        
        lines = output.split('\n')
        
        for line in lines:
            # 匹配接口行，例如: 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
            interface_match = re.match(r'^\d+:\s+(\S+):\s+<(.+?)>.*state\s+(\S+)', line)
            if interface_match:
                iface_name = interface_match.group(1)
                state = interface_match.group(3)
                
                current_interface = iface_name
                interfaces[iface_name] = {
                    'state': state,
                    'mac': None,
                    'ip_addresses': []
                }
                continue
            
            if current_interface:
                # 匹配 MAC 地址行，例如: link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff
                mac_match = re.search(r'link/ether\s+([0-9a-f:]+)', line)
                if mac_match:
                    interfaces[current_interface]['mac'] = mac_match.group(1)
                
                # 匹配 IPv4 地址行，例如: inet 192.168.1.25/24 brd 192.168.1.255 scope global eth0
                ipv4_match = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', line)
                if ipv4_match:
                    ip = ipv4_match.group(1)
                    interfaces[current_interface]['ip_addresses'].append(ip)
        
        return interfaces
    
    def check_lidar_connection(self):
        """
        检查雷达是否联通
        先检测激活的 systemctl 服务（livox 或 livox_s）确定雷达类型，
        再从对应工作空间的配置文件获取雷达 IP 地址，ping 测试连通性
          livox   -> ~/livox_ws/src/livox_ros_driver2/config/MID360_config.json
          livox_s -> ~/livox_s_ws/src/livox_ros_driver2/config/MID360s_config.json
        """
        excluded_ip = "192.168.1.25"
        
        # 服务名 -> (工作空间目录, 配置文件名) 的映射
        service_ws_map = {
            'livox':   ('livox_ws',   'MID360_config.json'),
            'livox_s': ('livox_s_ws', 'MID360s_config.json'),
        }
        
        try:
            details = ""
            
            # 步骤1: 检测哪个 systemctl 服务处于活跃状态
            details += "步骤1: 检测激活的雷达服务\n"
            active_service = None
            service_details = ""
            
            for svc in ('livox', 'livox_s'):
                status_cmd = "systemctl is-active {} 2>/dev/null".format(svc)
                svc_result = _run_cmd(status_cmd, timeout=5)
                svc_state = svc_result['stdout'].strip() if svc_result['stdout'] else 'unknown'
                service_details += "  {}: {}\n".format(svc, svc_state)
                if svc_state == 'active' and active_service is None:
                    active_service = svc
            
            details += service_details
            
            if active_service is None:
                # 两个服务都不活跃，尝试用进程名兜底判断
                details += "  两个服务均未激活，尝试通过进程名判断...\n"
                for svc in ('livox_s', 'livox'):
                    proc_result = _run_cmd(
                        "pgrep -f {}_ros_driver 2>/dev/null | head -1".format(svc), timeout=5
                    )
                    if proc_result['stdout'].strip():
                        active_service = svc
                        details += "  通过进程名检测到: {}\n".format(svc)
                        break
            
            if active_service is None:
                details += "  [X] 无法确定激活的雷达服务\n"
                return {
                    'success': False,
                    'message': '[X] 无法确定激活的雷达服务 (livox / livox_s 均未运行)',
                    'details': details
                }
            
            ws_dir, config_file = service_ws_map[active_service]
            config_path = "~/{}/src/livox_ros_driver2/config/{}".format(ws_dir, config_file)
            details += "  [OK] 激活服务: {}，工作空间: {}，配置文件: {}\n".format(active_service, ws_dir, config_file)
            details += "\n步骤2: 读取配置文件\n"
            details += "  路径: {}\n".format(config_path)
            
            # 步骤2: 读取配置文件
            result = _run_cmd("cat {}".format(config_path))
            
            if not result['success']:
                return {
                    'success': False,
                    'message': '[X] 无法读取雷达配置文件',
                    'details': details + '错误: {}'.format(result['stderr'])
                }
            
            # 解析 JSON 配置
            try:
                config = json.loads(result['stdout'])
            except json.JSONDecodeError as e:
                return {
                    'success': False,
                    'message': '[X] 配置文件格式错误',
                    'details': details + 'JSON 解析失败: {}'.format(str(e))
                }
            
            # 查找雷达 IP 地址（排除 192.168.1.25）
            lidar_ips = self._extract_lidar_ips(config, excluded_ip)
            
            if not lidar_ips:
                return {
                    'success': False,
                    'message': '[X] 未在配置文件中找到雷达 IP 地址',
                    'details': details + '配置内容:\n{}'.format(json.dumps(config, indent=2, ensure_ascii=False))
                }
            
            # 步骤3: 测试每个雷达 IP 的连通性
            details += "\n步骤3: Ping 测试\n找到的雷达 IP 地址:\n"
            all_success = True
            ping_results = []
            
            for ip in lidar_ips:
                details += "\n测试 IP: {}\n".format(ip)
                
                # Ping 测试 (发送 3 个包，超时 2 秒)
                ping_cmd = "ping -c 3 -W 2 {}".format(ip)
                ping_result = _run_cmd(ping_cmd, timeout=10)
                
                if ping_result['success']:
                    # 解析 ping 结果
                    ping_stats = self._parse_ping_output(ping_result['stdout'])
                    details += "  状态: [OK] 联通\n"
                    details += "  往返时间: {}\n".format(ping_stats['rtt'])
                    details += "  丢包率: {}\n".format(ping_stats['packet_loss'])
                    ping_results.append({
                        'ip': ip,
                        'success': True,
                        'stats': ping_stats
                    })
                else:
                    details += "  状态: [X] 不通\n"
                    details += "  错误: {}\n".format(ping_result['stderr'] or '超时')
                    all_success = False
                    ping_results.append({
                        'ip': ip,
                        'success': False
                    })
            
            if all_success:
                return {
                    'success': True,
                    'message': '[OK] 雷达连通性检查通过 ({}): 所有雷达 ({}) 都可以 ping 通'.format(active_service, len(lidar_ips)),
                    'details': details,
                    'lidar_ips': lidar_ips,
                    'ping_results': ping_results,
                    'active_service': active_service
                }
            else:
                failed_count = sum(1 for r in ping_results if not r['success'])
                return {
                    'success': False,
                    'message': '[X] 雷达连通性检查失败 ({}): {}/{} 个雷达无法 ping 通'.format(active_service, failed_count, len(lidar_ips)),
                    'details': details,
                    'lidar_ips': lidar_ips,
                    'ping_results': ping_results,
                    'active_service': active_service
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 雷达连通性检查出错',
                'details': str(e)
            }
    
    def _extract_lidar_ips(self, config, excluded_ip):
        """
        从配置中提取雷达 IP 地址（排除指定的 IP）
        优先从 lidar_configs 数组中提取，否则递归搜索整个配置
        """
        ips = []
        
        # 优先检查 lidar_configs 数组（标准 MID360 配置格式）
        if 'lidar_configs' in config and isinstance(config['lidar_configs'], list):
            for lidar_config in config['lidar_configs']:
                if isinstance(lidar_config, dict) and 'ip' in lidar_config:
                    ip = lidar_config['ip']
                    if isinstance(ip, str) and self._is_valid_ip(ip) and ip != excluded_ip:
                        if ip not in ips:
                            ips.append(ip)
        
        # 如果在 lidar_configs 中找到了 IP，直接返回
        if ips:
            return ips
        
        # 否则递归搜索配置中的所有 IP 地址字段（兼容其他格式）
        def search_ips(obj):
            if isinstance(obj, dict):
                # 常见的 IP 字段名
                for key in ['ip', 'host', 'device_ip', 'lidar_ip', 'sensor_ip']:
                    if key in obj:
                        value = obj[key]
                        if isinstance(value, str):
                            # 验证是否为有效的 IP 地址
                            if self._is_valid_ip(value) and value != excluded_ip:
                                if value not in ips:
                                    ips.append(value)
                        elif isinstance(value, dict):
                            search_ips(value)
                
                # 递归搜索所有值
                for value in obj.values():
                    if isinstance(value, (dict, list)):
                        search_ips(value)
            
            elif isinstance(obj, list):
                for item in obj:
                    search_ips(item)
        
        search_ips(config)
        return ips
    
    def _is_valid_ip(self, ip_str):
        """验证是否为有效的 IPv4 地址"""
        pattern = re.compile(r'^(\d{1,3}\.){3}\d{1,3}$')
        if not pattern.match(ip_str):
            return False
        
        parts = ip_str.split('.')
        return all(0 <= int(part) <= 255 for part in parts)
    
    def _parse_ping_output(self, output):
        """
        解析 ping 命令的输出
        """
        stats = {
            'rtt': '未知',
            'packet_loss': '0%'
        }
        
        # 解析丢包率，例如: 3 packets transmitted, 3 received, 0% packet loss
        loss_match = re.search(r'(\d+)% packet loss', output)
        if loss_match:
            stats['packet_loss'] = '{}%'.format(loss_match.group(1))
        
        # 解析 RTT，例如: rtt min/avg/max/mdev = 0.123/0.456/0.789/0.101 ms
        rtt_match = re.search(r'rtt min/avg/max/mdev = ([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+) ms', output)
        if rtt_match:
            stats['rtt'] = 'min/avg/max = {}/{}/{} ms'.format(
                rtt_match.group(1),
                rtt_match.group(2),
                rtt_match.group(3)
            )
        
        return stats
    
    def _check_process_status(self, process_name):
        """
        检查指定进程是否在运行
        返回格式化的进程状态信息
        """
        info = "进程检查 ({}):\n".format(process_name)
        
        try:
            # 使用 pgrep 查找进程
            cmd = "pgrep -f {}".format(process_name)
            result = _run_cmd(cmd, timeout=5)
            
            if result['success'] and result['stdout'].strip():
                pids = result['stdout'].strip().split('\n')
                info += "  状态: [OK] 运行中\n"
                info += "  进程数: {}\n".format(len(pids))
                info += "  PID: {}\n".format(', '.join(pids))
                
                # 获取进程详细信息
                ps_cmd = "ps -p {} -o pid,etime,cmd --no-headers".format(','.join(pids[:3]))  # 最多显示3个
                ps_result = _run_cmd(ps_cmd, timeout=5)
                if ps_result['success']:
                    info += "  详情:\n"
                    for line in ps_result['stdout'].strip().split('\n'):
                        info += "    {}\n".format(line.strip())
            else:
                info += "  状态: [X] 未运行\n"
                info += "  建议: 请检查 {} 是否已启动\n".format(process_name)
                
        except Exception as e:
            info += "  状态: 检查失败\n"
            info += "  错误: {}\n".format(str(e))
        
        return info
    
    def check_lidar_data_rate(self):
        """
        检查雷达数据是否到达
        使用 rostopic hz /livox/imu 查看数据频率，接近 200Hz 为通过
        """
        topic_name = "/livox/imu"
        expected_rate = 200.0
        tolerance = 10.0  # 允许的偏差范围 ±10Hz
        sample_duration = 8  # 采样时长（秒）
        
        try:
            # 使用 timeout 命令限制 rostopic hz 的执行时间
            # rostopic hz 会持续运行，需要在一定时间后终止
            cmd = "timeout {} bash -c 'source /opt/ros/*/setup.bash && rostopic hz {}'".format(
                sample_duration, topic_name
            )
            
            details = "正在监测话题: {}\n".format(topic_name)
            details += "采样时长: {} 秒\n".format(sample_duration)
            details += "期望频率: {} Hz (±{} Hz)\n\n".format(expected_rate, tolerance)
            
            result = _run_cmd(cmd, timeout=sample_duration + 5)
            
            # timeout 命令正常超时会返回退出码 124，这是预期行为
            if result['exit_code'] not in [0, 124]:
                # 检查是否是因为话题不存在
                if 'unknown topic' in result['stderr'].lower() or 'ERROR' in result['stderr']:
                    # 检查进程状态
                    process_info = self._check_process_status('livox_ros_driver2')
                    details += '\n' + process_info
                    
                    return {
                        'success': False,
                        'message': '[X] 话题 {} 不存在或无法访问'.format(topic_name),
                        'details': details + '\n错误信息:\n{}'.format(result['stderr'])
                    }
            
            output = result['stdout'] + result['stderr']
            
            # 解析输出中的平均频率
            # 示例输出: "average rate: 199.928"
            rate_match = re.search(r'average rate:\s*([\d.]+)', output)
            
            if not rate_match:
                # 检查进程状态
                process_info = self._check_process_status('livox_ros_driver2')
                details += '\n' + process_info
                
                return {
                    'success': False,
                    'message': '[X] 无法获取话题频率数据',
                    'details': details + '\n命令输出:\n{}'.format(output[:500])
                }
            
            actual_rate = float(rate_match.group(1))
            rate_diff = abs(actual_rate - expected_rate)
            
            details += "实际频率: {:.3f} Hz\n".format(actual_rate)
            details += "期望频率: {:.3f} Hz\n".format(expected_rate)
            details += "频率偏差: {:.3f} Hz\n".format(rate_diff)
            
            # 检查频率是否在允许范围内
            if rate_diff <= tolerance:
                return {
                    'success': True,
                    'message': '[OK] 雷达数据频率检查通过: {:.3f} Hz (期望 {} Hz)'.format(actual_rate, expected_rate),
                    'details': details,
                    'actual_rate': actual_rate,
                    'expected_rate': expected_rate
                }
            else:
                # 频率异常，检查进程状态
                process_info = self._check_process_status('livox_ros_driver2')
                details += '\n' + process_info
                
                return {
                    'success': False,
                    'message': '[X] 雷达数据频率异常: {:.3f} Hz (期望 {} Hz ±{} Hz)'.format(
                        actual_rate, expected_rate, tolerance
                    ),
                    'details': details,
                    'actual_rate': actual_rate,
                    'expected_rate': expected_rate
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 雷达数据频率检查出错',
                'details': str(e)
            }
    
    def check_hostname(self):
        """
        检查主机名格式
        检测 hostname 是否符合 "LL三位数字-三位数字" 的格式，例如 LL123-456
        """
        expected_pattern = r'^LL\d{3}-\d{3}$'
        
        try:
            # 获取主机名
            result = _run_cmd("hostname")
            
            if not result['success']:
                return {
                    'success': False,
                    'message': '[X] 无法获取主机名',
                    'details': '错误: {}'.format(result['stderr'])
                }
            
            hostname = result['stdout'].strip()
            
            details = "主机名信息:\n"
            details += "  当前主机名: {}\n".format(hostname)
            details += "  期望格式: LL三位数字-三位数字 (例如: LL123-456)\n"
            details += "  正则表达式: {}\n\n".format(expected_pattern)
            
            # 检查格式是否匹配
            if re.match(expected_pattern, hostname):
                return {
                    'success': True,
                    'message': '[OK] 主机名格式检查通过: {}'.format(hostname),
                    'details': details + '格式验证: [OK] 符合要求',
                    'hostname': hostname
                }
            else:
                # 提供详细的不匹配原因
                details += "格式验证: [X] 不符合要求\n"
                details += "\n不符合原因分析:\n"
                
                if not hostname.startswith('LL'):
                    details += "  - 未以 'LL' 开头\n"
                
                if '-' not in hostname:
                    details += "  - 缺少中间的连字符 '-'\n"
                else:
                    parts = hostname.split('-')
                    if len(parts) != 2:
                        details += "  - 连字符数量不正确\n"
                    else:
                        prefix = parts[0]
                        suffix = parts[1]
                        
                        if len(prefix) != 5 or not prefix.startswith('LL'):
                            details += "  - 前缀部分格式错误 (应为 LL + 三位数字)\n"
                        elif not prefix[2:].isdigit() or len(prefix[2:]) != 3:
                            details += "  - 前缀的数字部分不是三位数字\n"
                        
                        if not suffix.isdigit() or len(suffix) != 3:
                            details += "  - 后缀部分不是三位数字\n"
                
                return {
                    'success': False,
                    'message': '[X] 主机名格式检查失败: {}'.format(hostname),
                    'details': details,
                    'hostname': hostname
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 主机名格式检查出错',
                'details': str(e)
            }
    
    def check_frpc_config(self):
        """
        检查 frpc 配置
        1. 检查 frpc 进程是否运行
        2. 检查配置文件 /etc/frp/frpc.ini 是否包含 [ssh-$HOSTNAME] 字段
        """
        config_file = "/etc/frp/frpc.ini"
        
        try:
            details = "frpc 配置检查:\n\n"
            issues = []
            
            # 1. 检查 frpc 进程是否运行
            process_info = self._check_process_status('frpc')
            details += process_info
            
            # 判断进程是否运行
            process_running = '[OK] 运行中' in process_info
            if not process_running:
                issues.append('frpc 进程未运行')
            
            # 2. 获取主机名
            hostname_result = _run_cmd("hostname")
            if not hostname_result['success']:
                return {
                    'success': False,
                    'message': '[X] 无法获取主机名',
                    'details': details + '\n错误: {}'.format(hostname_result['stderr'])
                }
            
            hostname = hostname_result['stdout'].strip()
            expected_section = "[ssh-{}]".format(hostname)
            
            details += "\n配置文件检查 ({}):\n".format(config_file)
            details += "  期望字段: {}\n\n".format(expected_section)
            
            # 3. 读取配置文件
            config_result = _run_cmd("cat {}".format(config_file))
            
            if not config_result['success']:
                issues.append('配置文件不存在或无法读取')
                details += "  状态: [X] 无法读取\n"
                details += "  错误: {}\n".format(config_result['stderr'])
            else:
                config_content = config_result['stdout']
                
                # 检查是否包含期望的 section
                if expected_section in config_content:
                    details += "  状态: [OK] 找到字段 {}\n".format(expected_section)
                    
                    # 提取该 section 的内容
                    lines = config_content.split('\n')
                    in_section = False
                    section_content = []
                    
                    for line in lines:
                        if line.strip() == expected_section:
                            in_section = True
                            section_content.append(line)
                        elif in_section:
                            if line.strip().startswith('['):
                                break
                            section_content.append(line)
                    
                    if section_content:
                        details += "  配置内容:\n"
                        for line in section_content[:10]:  # 最多显示10行
                            details += "    {}\n".format(line)
                else:
                    issues.append('配置文件中未找到 {} 字段'.format(expected_section))
                    details += "  状态: [X] 未找到字段 {}\n".format(expected_section)
                    details += "\n配置文件中已有的 section:\n"
                    
                    # 显示配置文件中所有的 section
                    sections = [line.strip() for line in config_content.split('\n') 
                               if line.strip().startswith('[') and line.strip().endswith(']')]
                    for section in sections[:20]:  # 最多显示20个
                        details += "    {}\n".format(section)
            
            # 汇总结果
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] frpc 配置检查通过',
                    'details': details,
                    'hostname': hostname,
                    'section': expected_section
                }
            else:
                return {
                    'success': False,
                    'message': '[X] frpc 配置检查失败: {}'.format('; '.join(issues)),
                    'details': details,
                    'issues': issues
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] frpc 配置检查出错',
                'details': str(e)
            }
    
    def check_sysd_ctrl(self):
        """
        检查 sysd-ctrl 守护进程
        1. 重启 sysd-ctrl 服务
        2. 检查进程是否运行
        3. 如果未运行，检查配置文件是否存在
        """
        config_file = "/etc/sysd-ctrl/sysd-ctrl.conf"
        service_name = "sysd-ctrl"
        
        try:
            details = "sysd-ctrl 守护进程检查:\n\n"
            
            # 1. 尝试重启 sysd-ctrl 服务
            details += "步骤1: 重启 sysd-ctrl 服务\n"
            restart_cmd = "systemctl restart {} 2>&1 || true".format(service_name)
            restart_result = _run_cmd(restart_cmd, timeout=10)
            
            if restart_result['success'] or restart_result['exit_code'] == 0:
                details += "  重启命令执行完成\n"
            else:
                details += "  重启命令返回: {}\n".format(restart_result['stdout'] or restart_result['stderr'])
            
            # 等待一下让服务启动

            time.sleep(2)
            
            # 2. 检查进程是否运行
            details += "\n步骤2: 检查进程状态\n"
            process_info = self._check_process_status(service_name)
            details += process_info
            
            # 判断进程是否运行
            process_running = '[OK] 运行中' in process_info
            
            if process_running:
                # 3. 检查 TCP 端口 9527 是否可访问
                details += "\n步骤3: 检查 TCP 端口 9527\n"
                port = 9527
                
                port_test_cmd = "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/{}' 2>&1 && echo 'OPEN' || echo 'CLOSED'".format(port)
                port_result = _run_cmd(port_test_cmd, timeout=5)
                
                port_accessible = False
                if port_result['success'] and 'OPEN' in port_result['stdout']:
                    port_accessible = True
                    details += "  状态: [OK] 端口 {} 可访问\n".format(port)
                    
                    # 尝试使用 netstat 或 ss 查看端口监听情况
                    listen_cmd = "ss -tlnp | grep ':{} ' || netstat -tlnp | grep ':{} '".format(port, port)
                    listen_result = _run_cmd(listen_cmd, timeout=5)
                    if listen_result['success'] and listen_result['stdout'].strip():
                        details += "  监听信息:\n"
                        for line in listen_result['stdout'].strip().split('\n')[:3]:
                            details += "    {}\n".format(line.strip())
                else:
                    details += "  状态: [X] 端口 {} 不可访问\n".format(port)
                    details += "  错误: {}\n".format(port_result['stdout'] or port_result['stderr'])
                
                # 根据端口访问情况决定检查结果
                if port_accessible:
                    return {
                        'success': True,
                        'message': '[OK] sysd-ctrl 守护进程检查通过',
                        'details': details,
                        'process_running': True,
                        'port_accessible': True,
                        'port': port
                    }
                else:
                    return {
                        'success': False,
                        'message': '[X] sysd-ctrl 进程运行中但端口 {} 不可访问'.format(port),
                        'details': details,
                        'process_running': True,
                        'port_accessible': False,
                        'port': port
                    }
            else:
                # 3. 进程未运行，检查配置文件
                details += "\n步骤3: 检查配置文件\n"
                details += "  配置文件路径: {}\n".format(config_file)
                
                config_check = _run_cmd("test -f {} && echo 'EXISTS' || echo 'NOT_FOUND'".format(config_file))
                
                if config_check['success'] and 'EXISTS' in config_check['stdout']:
                    details += "  状态: [OK] 文件存在\n"
                    
                    # 读取配置文件信息
                    stat_result = _run_cmd("ls -lh {}".format(config_file))
                    if stat_result['success']:
                        details += "  文件信息: {}\n".format(stat_result['stdout'].strip())
                    
                    return {
                        'success': False,
                        'message': '[X] sysd-ctrl 进程未运行，但配置文件存在',
                        'details': details,
                        'process_running': False,
                        'config_exists': True
                    }
                else:
                    details += "  状态: [X] 文件不存在\n"
                    
                    return {
                        'success': False,
                        'message': '[X] sysd-ctrl 进程未运行且配置文件不存在',
                        'details': details,
                        'process_running': False,
                        'config_exists': False
                    }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] sysd-ctrl 检查出错',
                'details': str(e)
            }
    
    def check_upload_server(self):
        """
        检查更新服务
        1. 检查 ~/updater 目录是否存在且可写
        2. 检查本机 8080 端口是否可达
        """
        port = 8080
        upload_dir = Path("~/updater").expanduser().resolve()
        
        try:
            details = "Updater 检查:\n\n"
            issues = []
            
            # 1. 检查上传目录
            details += "步骤1: 检查上传目录 {}\n".format(upload_dir)
            dir_exists = upload_dir.exists()
            dir_writable = False
            if dir_exists:
                try:
                    test_file = upload_dir / ".write_test"
                    test_file.write_text("test")
                    test_file.unlink()
                    dir_writable = True
                    details += "  状态: [OK] 目录存在且可写\n"
                except Exception as e:
                    details += "  状态: [X] 目录不可写: {}\n".format(e)
                    issues.append('上传目录不可写')
            else:
                details += "  状态: [X] 目录不存在\n"
                issues.append('上传目录不存在')
            
            # 2. 检查 TCP 端口 8080 是否可访问
            details += "\n步骤2: 检查 TCP 端口 {}\n".format(port)
            port_test_cmd = "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/{}' 2>&1 && echo 'OPEN' || echo 'CLOSED'".format(port)
            port_result = _run_cmd(port_test_cmd, timeout=5)
            
            port_accessible = False
            if port_result['success'] and 'OPEN' in port_result['stdout']:
                port_accessible = True
                details += "  状态: [OK] 端口 {} 可访问\n".format(port)
            else:
                details += "  状态: [X] 端口 {} 不可访问\n".format(port)
                details += "  错误: {}\n".format(port_result['stdout'] or port_result['stderr'])
                issues.append('端口 {} 不可访问'.format(port))
            
            # 汇总结果
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] Updater检查通过',
                    'details': details,
                    'dir_exists': dir_exists,
                    'dir_writable': dir_writable,
                    'port_accessible': port_accessible,
                    'port': port
                }
            else:
                return {
                    'success': False,
                    'message': '[X] Updater检查失败: {}'.format('; '.join(issues)),
                    'details': details,
                    'dir_exists': dir_exists,
                    'dir_writable': dir_writable,
                    'port_accessible': port_accessible,
                    'port': port,
                    'issues': issues
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] Updater检查出错',
                'details': str(e)
            }
    
    def check_disk_space(self):
        """
        检查磁盘空间（汇总所有真实分区）
        要求：所有分区汇总剩余空间 >= 300GB，整体使用率 < 20%
        """
        min_free_gb = 300
        max_usage_percent = 20
        # 需要排除的伪文件系统
        skip_fs = {'tmpfs', 'devtmpfs', 'overlay', 'squashfs', 'proc', 'sysfs',
                   'devpts', 'cgroup', 'pstore', 'bpf', 'tracefs', 'securityfs',
                   'configfs', 'debugfs', 'hugetlbfs', 'mqueue', 'fusectl',
                   'rpc_pipefs', 'nfsd', 'binfmt_misc', 'fuse.gvfsd-fuse'}
        
        try:
            # 获取所有分区信息
            result = _run_cmd("df -BG")
            
            if not result['success']:
                return {
                    'success': False,
                    'message': '[X] 无法获取磁盘信息',
                    'details': result['stderr']
                }
            
            output = result['stdout'].strip()
            if not output:
                return {
                    'success': False,
                    'message': '[X] 磁盘信息为空',
                    'details': '命令返回空结果'
                }
            
            # 解析 df 输出，汇总所有真实分区
            # 格式：Filesystem 1G-blocks Used Available Use% Mounted on
            total_gb = 0
            used_gb = 0
            avail_gb = 0
            details = "磁盘空间信息（所有分区汇总）:\n"
            lines = output.splitlines()
            parsed = []
            
            for line in lines[1:]:  # 跳过表头
                parts = line.split()
                if len(parts) < 6:
                    continue
                filesystem = parts[0]
                if filesystem in skip_fs or filesystem.startswith('tmpfs'):
                    continue
                try:
                    fs_total = int(parts[1].rstrip('G'))
                    fs_used = int(parts[2].rstrip('G'))
                    fs_avail = int(parts[3].rstrip('G'))
                    fs_usage = int(parts[4].rstrip('%'))
                    mount = parts[5]
                except ValueError:
                    continue
                total_gb += fs_total
                used_gb += fs_used
                avail_gb += fs_avail
                parsed.append({
                    'fs': filesystem,
                    'mount': mount,
                    'total': fs_total,
                    'used': fs_used,
                    'avail': fs_avail,
                    'usage': fs_usage
                })
                details += "  {:<12} {:<10} 总:{}G 已用:{}G 可用:{}G 使用率:{}%\n".format(
                    filesystem, mount, fs_total, fs_used, fs_avail, fs_usage)
            
            if not parsed:
                return {
                    'success': False,
                    'message': '[X] 未找到有效磁盘分区',
                    'details': 'df 原始输出:\n{}'.format(output)
                }
            
            # 计算整体使用率
            usage_percent = int(round(used_gb * 100.0 / total_gb)) if total_gb > 0 else 0
            
            details += "\n  汇总: 总容量 {} GB, 已用 {} GB, 可用 {} GB, 使用率 {}%\n\n".format(
                total_gb, used_gb, avail_gb, usage_percent)
            
            # 检查条件
            issues = []
            if avail_gb < min_free_gb:
                issues.append('可用空间不足 {} GB (当前: {} GB)'.format(min_free_gb, avail_gb))
                details += "  [X] 可用空间不足: {} GB < {} GB (要求)\n".format(avail_gb, min_free_gb)
            else:
                details += "  [OK] 可用空间充足: {} GB >= {} GB (要求)\n".format(avail_gb, min_free_gb)
            
            if usage_percent >= max_usage_percent:
                issues.append('使用率过高 {}% (要求 < {}%)'.format(usage_percent, max_usage_percent))
                details += "  [X] 使用率过高: {}% >= {}% (要求 < {}%)\n".format(usage_percent, max_usage_percent, max_usage_percent)
            else:
                details += "  [OK] 使用率正常: {}% < {}% (要求)\n".format(usage_percent, max_usage_percent)
            
            filesystem = parsed[0]['fs'] if parsed else ''
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] 磁盘空间检查通过',
                    'details': details,
                    'total_gb': total_gb,
                    'used_gb': used_gb,
                    'avail_gb': avail_gb,
                    'usage_percent': usage_percent,
                    'filesystem': filesystem
                }
            else:
                return {
                    'success': False,
                    'message': '[X] 失败: {}'.format('; '.join(issues)),
                    'details': details,
                    'total_gb': total_gb,
                    'used_gb': used_gb,
                    'avail_gb': avail_gb,
                    'usage_percent': usage_percent,
                    'filesystem': filesystem,
                    'issues': issues
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 磁盘空间检查出错',
                'details': str(e)
            }
    
    def check_camera_stream(self):
        """
        检查相机流可访问性
        从待检测机（远程设备）测试能否访问 TCP 192.168.144.25:8554
        """
        target_ip = "192.168.144.25"
        target_port = 8554
        timeout = 5  # 连接超时时间（秒）
        
        try:
            details = "相机流可访问性检查:\n"
            details += "  目标地址: {}:{}\n".format(target_ip, target_port)
            details += "  连接超时: {} 秒\n".format(timeout)
            details += "  测试方式: 从远程设备使用 telnet 发起连接\n\n"
            
            # 使用 telnet 进行端口测试
            # telnet 连接成功后会进入交互模式，用 timeout 1 秒后终止
            # 如果能连接上，timeout 会返回 124；如果连接失败，telnet 会立即失败
            cmd = 'timeout 1 telnet {} {} 2>&1'.format(target_ip, target_port)
            
            details += "执行命令: {}\n\n".format(cmd)
            
            # 记录开始时间

            start_time = time.time()
            
            result = _run_cmd(cmd)
            elapsed_time = time.time() - start_time
            
            details += "执行时间: {:.3f} 秒\n".format(elapsed_time)
            details += "返回码: {}\n".format(result['exit_code'])
            
            # telnet 连接成功会显示 "Connected to" 或 "Escape character"
            # 连接成功后 timeout 会在1秒后终止，返回码 124
            output = result['stdout'] + result['stderr']
            
            if result['exit_code'] == 124 or 'Connected to' in output or 'Escape character' in output:
                details += "状态: [OK] 端口可访问\n"
                if 'Connected to' in output:
                    details += "连接信息: 已成功建立连接\n"
                
                return {
                    'success': True,
                    'message': '[OK] 相机流可访问',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port,
                    'response_time': elapsed_time
                }
            elif 'Connection refused' in output:
                # 连接被拒绝
                details += "状态: [X] 端口不可访问\n"
                details += "原因: 连接被拒绝（端口未监听或服务未启动）\n"
                
                return {
                    'success': False,
                    'message': '[X] 相机流不可访问: 连接被拒绝',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port,
                    'error_code': result['exit_code']
                }
            elif 'No route to host' in output:
                # 无法到达主机
                details += "状态: [X] 无法到达主机\n"
                details += "原因: 无法到达主机（网络不通）\n"
                
                return {
                    'success': False,
                    'message': '[X] 相机流不可访问: 无法到达主机',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port,
                    'error_code': result['exit_code']
                }
            elif 'Network is unreachable' in output:
                # 网络不可达
                details += "状态: [X] 网络不可达\n"
                details += "原因: 网络不可达\n"
                
                return {
                    'success': False,
                    'message': '[X] 相机流不可访问: 网络不可达',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port,
                    'error_code': result['exit_code']
                }
            elif elapsed_time >= timeout - 0.5:
                # 如果执行时间接近超时时间，判定为连接超时
                details += "状态: [X] 连接超时\n"
                details += "可能原因: 目标主机不可达、网络不通或防火墙阻止\n"
                
                return {
                    'success': False,
                    'message': '[X] 相机流连接超时',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port
                }
            else:
                # 其他错误
                details += "状态: [X] 连接失败\n"
                details += "输出信息: {}\n".format(output[:200] if output else '无')
                
                return {
                    'success': False,
                    'message': '[X] 相机流不可访问: 连接失败',
                    'details': details,
                    'target_ip': target_ip,
                    'target_port': target_port,
                    'error_code': result['exit_code']
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 相机流检查出错',
                'details': 'Exception: {}\n{}'.format(str(e), details if 'details' in locals() else '')
            }
    
    def check_px4ctrl_node(self):
        """
        检查主控程序 px4ctrl_node 进程是否运行
        """
        process_name = "px4ctrl_node"
        
        try:
            details = "主控程序检查:\n\n"
            
            # 检查 px4ctrl_node 进程是否运行
            details += "步骤1: 检查 px4ctrl_node 进程\n"
            process_cmd = "pgrep -f '{}'".format(process_name)
            process_result = _run_cmd(process_cmd, timeout=5)
            
            process_running = False
            if process_result['success'] and process_result['stdout'].strip():
                pids = process_result['stdout'].strip().split('\n')
                process_running = True
                details += "  状态: [OK] 运行中\n"
                details += "  进程数: {}\n".format(len(pids))
                details += "  PID: {}\n".format(', '.join(pids))
                
                # 获取进程详细信息
                ps_cmd = "ps -p {} -o pid,etime,cmd --no-headers".format(','.join(pids[:3]))
                ps_result = _run_cmd(ps_cmd, timeout=5)
                if ps_result['success'] and ps_result['stdout'].strip():
                    details += "  详情:\n"
                    for line in ps_result['stdout'].strip().split('\n'):
                        details += "    {}\n".format(line.strip())
                
                return {
                    'success': True,
                    'message': '[OK] px4ctrl_node 进程运行中',
                    'details': details,
                    'process_running': True,
                    'pid_count': len(pids)
                }
            else:
                details += "  状态: [X] 未运行\n"
                details += "  建议: 请检查主控程序是否已启动\n"
                
                return {
                    'success': False,
                    'message': '[X] px4ctrl_node 进程未运行',
                    'details': details,
                    'process_running': False
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] px4ctrl_node 检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_ws_server(self):
        """
        检查 WS Server 端口可访问性
        测试本地 53001 端口是否可以 TCP 访问
        """
        port = 53001
        
        try:
            details = "WS Server 端口检查:\n\n"
            details += "步骤1: 检查本地 TCP 端口 {}\n".format(port)
            
            # 使用 /dev/tcp 测试端口连接
            port_test_cmd = "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/{}' 2>&1 && echo 'OPEN' || echo 'CLOSED'".format(port)
            port_result = _run_cmd(port_test_cmd, timeout=5)
            
            port_accessible = False
            if port_result['success'] and 'OPEN' in port_result['stdout']:
                port_accessible = True
                details += "  状态: [OK] 端口 {} 可访问\n".format(port)
                
                # 尝试使用 netstat 或 ss 查看端口监听情况
                details += "\n步骤2: 查看端口监听信息\n"
                listen_cmd = "ss -tlnp | grep ':{} ' || netstat -tlnp | grep ':{} '".format(port, port)
                listen_result = _run_cmd(listen_cmd, timeout=5)
                
                if listen_result['success'] and listen_result['stdout'].strip():
                    details += "  监听信息:\n"
                    for line in listen_result['stdout'].strip().split('\n')[:3]:
                        details += "    {}\n".format(line.strip())
                else:
                    details += "  监听信息: 无法获取详细信息\n"
                
                return {
                    'success': True,
                    'message': '[OK] WS Server 端口 {} 可访问'.format(port),
                    'details': details,
                    'port_accessible': True,
                    'port': port
                }
            else:
                details += "  状态: [X] 端口 {} 不可访问\n".format(port)
                details += "  错误信息: {}\n".format(port_result['stdout'] or port_result['stderr'])
                
                # 尝试查看是否有进程监听该端口
                details += "\n步骤2: 检查是否有进程监听该端口\n"
                listen_cmd = "ss -tlnp | grep ':{} ' || netstat -tlnp | grep ':{} '".format(port, port)
                listen_result = _run_cmd(listen_cmd, timeout=5)
                
                if listen_result['success'] and listen_result['stdout'].strip():
                    details += "  发现监听进程:\n"
                    for line in listen_result['stdout'].strip().split('\n')[:3]:
                        details += "    {}\n".format(line.strip())
                    details += "  提示: 端口有监听但连接测试失败，可能是防火墙或权限问题\n"
                else:
                    details += "  没有进程监听在端口 {}\n".format(port)
                    details += "  建议: 检查 WS Server 是否已启动\n"
                
                return {
                    'success': False,
                    'message': '[X] WS Server 端口 {} 不可访问'.format(port),
                    'details': details,
                    'port_accessible': False,
                    'port': port
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] WS Server 检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_gxzn_certificate(self):
        """
        检查主控证书文件
        检查 /etc/gxzn/gxzn_pm.json 文件是否存在
        """
        cert_file = "/etc/gxzn/gxzn_pm.json"
        
        try:
            details = "主控证书检查:\n\n"
            details += "步骤1: 检查证书文件\n"
            details += "  文件路径: {}\n".format(cert_file)
            
            # 检查文件是否存在
            check_cmd = "test -f {} && echo 'EXISTS' || echo 'NOT_FOUND'".format(cert_file)
            check_result = _run_cmd(check_cmd, timeout=5)
            
            if check_result['success'] and 'EXISTS' in check_result['stdout']:
                details += "  状态: [OK] 文件存在\n"
                
                # 获取文件详细信息
                details += "\n步骤2: 获取文件信息\n"
                stat_cmd = "ls -lh {}".format(cert_file)
                stat_result = _run_cmd(stat_cmd, timeout=5)
                
                if stat_result['success'] and stat_result['stdout'].strip():
                    details += "  文件信息: {}\n".format(stat_result['stdout'].strip())
                    
                    # 尝试读取文件大小
                    size_cmd = "stat -c %s {} 2>/dev/null || stat -f %z {} 2>/dev/null".format(cert_file, cert_file)
                    size_result = _run_cmd(size_cmd, timeout=5)
                    
                    if size_result['success'] and size_result['stdout'].strip():
                        file_size = size_result['stdout'].strip()
                        details += "  文件大小: {} 字节\n".format(file_size)
                
                # 检查文件权限
                perm_cmd = "stat -c '%a %U:%G' {} 2>/dev/null || stat -f '%Lp %Su:%Sg' {} 2>/dev/null".format(cert_file, cert_file)
                perm_result = _run_cmd(perm_cmd, timeout=5)
                
                if perm_result['success'] and perm_result['stdout'].strip():
                    details += "  权限信息: {}\n".format(perm_result['stdout'].strip())
                
                return {
                    'success': True,
                    'message': '[OK] 主控证书文件存在',
                    'details': details,
                    'file_exists': True,
                    'file_path': cert_file
                }
            else:
                details += "  状态: [X] 文件不存在\n"
                details += "  建议: 请检查主控证书是否已正确安装\n"
                
                # 检查目录是否存在
                details += "\n步骤2: 检查证书目录\n"
                dir_path = "/etc/gxzn"
                dir_check_cmd = "test -d {} && echo 'EXISTS' || echo 'NOT_FOUND'".format(dir_path)
                dir_result = _run_cmd(dir_check_cmd, timeout=5)
                
                if dir_result['success'] and 'EXISTS' in dir_result['stdout']:
                    details += "  目录 {} 存在\n".format(dir_path)
                    
                    # 列出目录内容
                    ls_cmd = "ls -la {}".format(dir_path)
                    ls_result = _run_cmd(ls_cmd, timeout=5)
                    if ls_result['success'] and ls_result['stdout'].strip():
                        details += "  目录内容:\n"
                        for line in ls_result['stdout'].strip().split('\n')[:10]:
                            details += "    {}\n".format(line.strip())
                else:
                    details += "  目录 {} 不存在\n".format(dir_path)
                
                return {
                    'success': False,
                    'message': '[X] 主控证书文件不存在',
                    'details': details,
                    'file_exists': False,
                    'file_path': cert_file
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 主控证书检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_fsm_state(self):
        """
        检查 FSM 状态
        1. 检查 /ctrlfsm/fsmstate 话题的 data 值是否为 2
        2. 检查 /ctrlfsm/status 话题的 data 值是否为 "OK"
        """
        try:
            details = "FSM 状态检查:\n\n"
            issues = []
            fsmstate_value = None
            status_value = None
            
            # 步骤1: 检查 /ctrlfsm/fsmstate
            details += "步骤1: 检查 /ctrlfsm/fsmstate\n"
            fsmstate_topic = "/ctrlfsm/fsmstate"
            
            # 使用 rostopic echo -n 1 只获取一条消息，timeout 5秒
            cmd1 = "timeout 5 bash -c 'source /opt/ros/*/setup.bash && rostopic echo -n 1 {}'".format(fsmstate_topic)
            result1 = _run_cmd(cmd1, timeout=8)
            
            if result1['success'] or result1['exit_code'] == 124:
                output1 = result1['stdout']
                details += "  话题输出:\n"
                for line in output1.split('\n')[:5]:
                    if line.strip():
                        details += "    {}\n".format(line.strip())
                
                # 从输出中提取 data 值

                data_match = re.search(r'data:\s*(\d+)', output1)
                if data_match:
                    fsmstate_value = int(data_match.group(1))
                    details += "  提取到 data 值: {}\n".format(fsmstate_value)
                    
                    if fsmstate_value == 2:
                        details += "  状态: [OK] fsmstate = 2\n"
                    elif fsmstate_value == 1:
                        details += "  状态: [X] fsmstate = 1 - 未连接到飞控，检查串口\n"
                        issues.append("fsmstate 值为 1 - 未连接到飞控，检查串口")
                    else:
                        details += "  状态: [X] fsmstate = {} (期望值: 2)\n".format(fsmstate_value)
                        issues.append("fsmstate 值为 {} (期望: 2)".format(fsmstate_value))
                else:
                    details += "  状态: [X] 无法提取 data 值\n"
                    details += "  原始输出: {}\n".format(output1[:200])
                    issues.append("无法从 fsmstate 话题提取 data 值")
            else:
                details += "  状态: [X] 无法获取话题数据\n"
                details += "  错误: {}\n".format(result1['stderr'][:200] if result1['stderr'] else '未知错误')
                issues.append("无法访问 fsmstate 话题")
            
            # 步骤2: 检查 /ctrlfsm/status
            details += "\n步骤2: 检查 /ctrlfsm/status\n"
            status_topic = "/ctrlfsm/status"
            
            cmd2 = "timeout 5 bash -c 'source /opt/ros/*/setup.bash && rostopic echo -n 1 {}'".format(status_topic)
            result2 = _run_cmd(cmd2, timeout=8)
            
            if result2['success'] or result2['exit_code'] == 124:
                output2 = result2['stdout']
                details += "  话题输出:\n"
                for line in output2.split('\n')[:5]:
                    if line.strip():
                        details += "    {}\n".format(line.strip())
                
                # 从输出中提取 data 值（字符串）

                # 匹配 data: "OK" 或 data: 'OK' 或 data: OK
                data_match = re.search(r'data:\s*["\']?([^"\'\n]+)["\']?', output2)
                if data_match:
                    status_value = data_match.group(1).strip()
                    details += "  提取到 data 值: \"{}\"\n".format(status_value)
                    
                    if status_value == "OK":
                        details += "  状态: [OK] status = \"OK\"\n"
                    else:
                        details += "  状态: [X] status = \"{}\" (期望值: \"OK\")\n".format(status_value)
                        issues.append("status 值为 \"{}\" (期望: \"OK\")".format(status_value))
                else:
                    details += "  状态: [X] 无法提取 data 值\n"
                    details += "  原始输出: {}\n".format(output2[:200])
                    issues.append("无法从 status 话题提取 data 值")
            else:
                details += "  状态: [X] 无法获取话题数据\n"
                details += "  错误: {}\n".format(result2['stderr'][:200] if result2['stderr'] else '未知错误')
                issues.append("无法访问 status 话题")
            
            # 判断检查结果
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] FSM 状态正常',
                    'details': details,
                    'fsmstate': fsmstate_value,
                    'status': status_value
                }
            else:
                return {
                    'success': False,
                    'message': '[X] FSM 状态异常: {}'.format('; '.join(issues)),
                    'details': details,
                    'fsmstate': fsmstate_value,
                    'status': status_value,
                    'issues': issues
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] FSM 状态检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_pm(self):
        """
        检查 PM 状态
        上传 ws_client 到远程设备并运行 wsclient --cmd state
        检查输出中：cf 应为 -1, pm 应 > 0, bat.vol 应 > 2000 (recv_pc 仅展示不判断)
        """
        remote_ws_client = '/tmp/ws_client'
        
        try:
            details = "PM 状态检查:\n\n"
            
            # 步骤1: 上传 ws_client 到远程设备
            details += "步骤1: 上传 ws_client 到远程设备\n"

            # ws_client 文件路径（从 auto_diag 目录读取）
            script_dir = os.path.dirname(os.path.abspath(__file__))
            local_ws_client = os.path.join(script_dir, "ws_client")
            
            if not os.path.exists(local_ws_client):
                details += "  状态: [X] 本地 ws_client 文件不存在\n"
                details += "  路径: {}\n".format(local_ws_client)
                return {
                    'success': False,
                    'message': '[X] 本地 ws_client 文件不存在',
                    'details': details
                }
            
            try:
                shutil.copy2(local_ws_client, remote_ws_client)
                details += "  状态: [OK] 文件上传成功\n"
                details += "  远程路径: {}\n".format(remote_ws_client)
            except Exception as e:
                details += "  状态: [X] 文件上传失败\n"
                details += "  错误: {}\n".format(str(e))
                return {
                    'success': False,
                    'message': '[X] ws_client 上传失败',
                    'details': details
                }
            
            # 使用 try-finally 确保清理工作一定会执行
            try:
                # 步骤2: 设置执行权限
                details += "\n步骤2: 设置执行权限\n"
                chmod_cmd = "chmod +x {}".format(remote_ws_client)
                chmod_result = _run_cmd(chmod_cmd, timeout=5)
                
                if chmod_result['success']:
                    details += "  状态: [OK] 权限设置成功\n"
                else:
                    details += "  状态: [X] 权限设置失败: {}\n".format(chmod_result['stderr'])
                
                # 步骤3: 运行 wsclient --cmd state
                details += "\n步骤3: 运行 wsclient --cmd state\n"
                # 使用 timeout 20 秒，因为 wsclient 可能需要连接 websocket
                # cd /tmp, 确保产生的其他临时文件也在 /tmp 目录下，方便清理
                run_cmd = "cd /tmp && timeout 20 {} --cmd state 2>&1".format(remote_ws_client)
                run_result = _run_cmd(run_cmd, timeout=25)
                
                output = run_result['stdout']
                details += "  命令输出:\n"
                for line in output.split('\n')[:20]:
                    if line.strip():
                        details += "    {}\n".format(line.strip())
                
                # 步骤4: 解析输出
                details += "\n步骤4: 解析结果\n"

                recv_pc = None
                cf = None
                pm = None
                bat_vol = None
                issues = []
                
                # 解析 recv_pc（仅展示，不作为判断条件）
                recv_pc_match = re.search(r'state\.lidar\.recv_pc:(\d+)', output)
                if recv_pc_match:
                    recv_pc = int(recv_pc_match.group(1))
                    details += "  recv_pc: {} (仅展示)\n".format(recv_pc)
                else:
                    details += "  recv_pc: 未找到 (仅展示)\n"
                
                # 解析 bat.vol
                bat_vol_match = re.search(r'state\.uav\.bat\.vol:(\d+)', output)
                if bat_vol_match:
                    bat_vol = int(bat_vol_match.group(1))
                    details += "  bat.vol: {}\n".format(bat_vol)
                    if bat_vol <= 2000:
                        issues.append("bat.vol={} (期望: >2000)".format(bat_vol))
                else:
                    details += "  bat.vol: [X] 未找到\n"
                    issues.append("未找到 bat.vol 值")
                
                # 解析 cf/pm
                cf_pm_match = re.search(r'state\.cf/pm:(-?\d+)/(-?\d+)', output)
                if cf_pm_match:
                    cf = int(cf_pm_match.group(1))
                    pm = int(cf_pm_match.group(2))
                    details += "  cf: {}\n".format(cf)
                    details += "  pm: {}\n".format(pm)
                    
                    if cf != -1:
                        issues.append("cf={} (期望: -1)".format(cf))
                    if pm <= 0:
                        issues.append("pm={} (期望: >0)".format(pm))
                else:
                    details += "  cf/pm: [X] 未找到\n"
                    issues.append("未找到 cf/pm 值")
                
            finally:
                # 步骤5: 清理临时文件（无论测试成功或失败都执行）
                details += "\n步骤5: 清理临时文件\n"
                cleanup_cmd = "rm -f {}".format(remote_ws_client)
                cleanup_result = _run_cmd(cleanup_cmd, timeout=5)
                if cleanup_result['success']:
                    details += "  状态: [OK] 临时文件已清理\n"
                else:
                    details += "  状态: [WARNING] 临时文件清理失败\n"
            
            # 判断检查结果
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] PM 状态正常 (pm={}, bat.vol={})'.format(pm, bat_vol),
                    'details': details,
                    'recv_pc': recv_pc,
                    'cf': cf,
                    'pm': pm,
                    'bat_vol': bat_vol
                }
            else:
                return {
                    'success': False,
                    'message': '[X] PM 状态异常: {}'.format('; '.join(issues)),
                    'details': details,
                    'recv_pc': recv_pc,
                    'cf': cf,
                    'pm': pm,
                    'bat_vol': bat_vol,
                    'issues': issues
                }
                
        except Exception as e:
            # 发生异常时也尝试清理文件
            try:
                cleanup_cmd = "rm -f {}".format(remote_ws_client)
                _run_cmd(cleanup_cmd, timeout=5)
            except:
                pass
            
            return {
                'success': False,
                'message': '[X] PM 检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_log_output_config(self):
        """
        检查日志输出配置
        1. 检查 /home/ramy/lidar_ws/shfiles/run_ctrl_mavros.launch 是否包含 ROSCONSOLE_FORMAT
        2. 检查 /home/ramy/lidar_ws/shfiles/run_ctrl_mavros.sh 是否包含 INVOCATION_ID
        3. 检查 /etc/systemd/journald.conf 中 SystemMaxUse 的值是否 < 10G
        """
        try:
            details = "日志输出配置检查:\n\n"
            issues = []
            
            # 检查1: run_ctrl_mavros.launch 包含 ROSCONSOLE_FORMAT
            details += "步骤1: 检查 run_ctrl_mavros.launch 文件\n"
            file1 = "/home/ramy/lidar_ws/shfiles/run_ctrl_mavros.launch"
            details += "  文件路径: {}\n".format(file1)
            details += "  检查项: 是否包含 ROSCONSOLE_FORMAT\n"
            
            result1 = _run_cmd("grep -q 'ROSCONSOLE_FORMAT' {} && echo 'FOUND' || echo 'NOT_FOUND'".format(file1))
            
            if result1['success'] and 'FOUND' in result1['stdout']:
                details += "  [OK] 文件包含 ROSCONSOLE_FORMAT\n"
            else:
                details += "  [X] 文件不包含 ROSCONSOLE_FORMAT\n"
                issues.append("run_ctrl_mavros.launch 缺少 ROSCONSOLE_FORMAT")
            
            # 检查2: run_ctrl_mavros.sh 包含 INVOCATION_ID
            details += "\n步骤2: 检查 run_ctrl_mavros.sh 文件\n"
            file2 = "/home/ramy/lidar_ws/shfiles/run_ctrl_mavros.sh"
            details += "  文件路径: {}\n".format(file2)
            details += "  检查项: 是否包含 INVOCATION_ID\n"
            
            result2 = _run_cmd("grep -q 'INVOCATION_ID' {} && echo 'FOUND' || echo 'NOT_FOUND'".format(file2))
            
            if result2['success'] and 'FOUND' in result2['stdout']:
                details += "  [OK] 文件包含 INVOCATION_ID\n"
            else:
                details += "  [X] 文件不包含 INVOCATION_ID\n"
                issues.append("run_ctrl_mavros.sh 缺少 INVOCATION_ID")
            
            # 检查3: journald.conf 的 SystemMaxUse < 10G
            details += "\n步骤3: 检查 journald.conf 文件\n"
            file3 = "/etc/systemd/journald.conf"
            details += "  文件路径: {}\n".format(file3)
            details += "  检查项: SystemMaxUse 的值是否 < 10G\n"
            
            # 读取配置文件，查找 SystemMaxUse 设置（可能被注释）
            result3 = _run_cmd("cat {}".format(file3))
            
            if not result3['success']:
                details += "  [X] 无法读取配置文件\n"
                issues.append("无法读取 journald.conf")
            else:
                # 查找 SystemMaxUse 设置（忽略注释行）
                config_content = result3['stdout']
                system_max_use_found = False
                system_max_use_value = None
                
                for line in config_content.split('\n'):
                    stripped = line.strip()
                    # 跳过空行和注释行
                    if not stripped or stripped.startswith('#'):
                        continue
                    
                    # 检查是否是 SystemMaxUse 设置
                    if stripped.startswith('SystemMaxUse='):
                        system_max_use_found = True
                        system_max_use_value = stripped.split('=', 1)[1].strip()
                        break
                
                if not system_max_use_found:
                    details += "  [X] 未找到 SystemMaxUse 设置（或被注释）\n"
                    issues.append("journald.conf 未设置 SystemMaxUse")
                else:
                    details += "  当前值: {}\n".format(system_max_use_value)
                    
                    # 解析值并判断是否 < 10G
                    # 支持的格式: 数字+单位（K/M/G），例如 "8G", "9500M", "10240M"
                    value_valid = self._parse_size_value(system_max_use_value)
                    
                    if value_valid is None:
                        details += "  [X] 无法解析 SystemMaxUse 值\n"
                        issues.append("journald.conf SystemMaxUse 值格式无效")
                    elif value_valid >= 10 * 1024 * 1024 * 1024:  # 10G in bytes
                        details += "  [X] SystemMaxUse >= 10G\n"
                        issues.append("journald.conf SystemMaxUse >= 10G")
                    else:
                        details += "  [OK] SystemMaxUse < 10G\n"
            
            # 汇总结果
            if not issues:
                return {
                    'success': True,
                    'message': '[OK] 日志输出配置检查通过',
                    'details': details
                }
            else:
                return {
                    'success': False,
                    'message': '[X] 日志输出配置检查失败: {}'.format(", ".join(issues)),
                    'details': details,
                    'issues': issues
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] 日志输出配置检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def _parse_size_value(self, value_str):
        """
        解析大小值字符串（支持 K/M/G 单位）
        返回字节数，失败返回 None
        例如: "8G" -> 8589934592, "9500M" -> 9961472000
        """
        if not value_str:
            return None
        
        value_str = value_str.upper().strip()
        
        # 提取数字和单位
        match = re.match(r'^(\d+(?:\.\d+)?)\s*([KMG]?)$', value_str)
        if not match:
            return None
        
        number = float(match.group(1))
        unit = match.group(2)
        
        # 转换为字节
        multiplier = {
            '': 1,
            'K': 1024,
            'M': 1024 * 1024,
            'G': 1024 * 1024 * 1024
        }
        
        return int(number * multiplier.get(unit, 1))
    
    def check_cpu_temperature(self):
        """
        检查 CPU 温度
        读取 /sys/class/thermal/thermal_zone1/temp 文件
        要求：值必须存在且不得超过 60000（即 60°C）
        """
        temp_file = "/sys/class/thermal/thermal_zone1/temp"
        max_temp = 60000  # 60°C (文件中的值单位是毫摄氏度)
        
        try:
            details = "CPU 温度检查:\n\n"
            details += "步骤1: 读取温度文件\n"
            details += "  文件路径: {}\n".format(temp_file)
            details += "  温度上限: {} ({}°C)\n".format(max_temp, max_temp / 1000)
            
            # 读取温度文件
            result = _run_cmd("cat {}".format(temp_file))
            
            if not result['success']:
                return {
                    'success': False,
                    'message': '[X] CPU 温度检查失败: 无法读取温度文件',
                    'details': details + "\n  [X] 无法读取文件\n  错误: {}".format(result['stderr'])
                }
            
            temp_str = result['stdout'].strip()
            
            # 检查值是否为空
            if not temp_str:
                return {
                    'success': False,
                    'message': '[X] CPU 温度检查失败: 温度值为空',
                    'details': details + "\n  [X] 温度文件内容为空"
                }
            
            # 尝试解析温度值
            try:
                temp_value = int(temp_str)
            except ValueError:
                return {
                    'success': False,
                    'message': '[X] CPU 温度检查失败: 温度值格式无效',
                    'details': details + "\n  [X] 无法解析温度值: {}".format(temp_str)
                }
            
            # 转换为摄氏度显示
            temp_celsius = temp_value / 1000.0
            
            details += "\n步骤2: 温度值检查\n"
            details += "  当前温度: {} ({}°C)\n".format(temp_value, temp_celsius)
            details += "  温度上限: {} ({}°C)\n".format(max_temp, max_temp / 1000)
            
            # 检查是否超过上限
            if temp_value > max_temp:
                details += "  [X] 温度超过上限\n"
                return {
                    'success': False,
                    'message': '[X] CPU 温度过高: {}°C (上限: {}°C)'.format(temp_celsius, max_temp / 1000),
                    'details': details,
                    'temperature': temp_value
                }
            else:
                details += "  [OK] 温度正常\n"
                return {
                    'success': True,
                    'message': '[OK] CPU 温度正常: {}°C'.format(temp_celsius),
                    'details': details,
                    'temperature': temp_value
                }
                
        except Exception as e:
            return {
                'success': False,
                'message': '[X] CPU 温度检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
    
    def check_cpu_frequency(self):
        """
        检查 CPU 实际运行最高主频
        要求：通过压力测试，实测 CPU 实际运行频率必须 >= 4GHz (4000 MHz)
        """
        min_freq_mhz = 4000  # 4GHz
        stress_duration = 5  # 压力测试持续时间（秒）
        
        try:
            details = "CPU 主频检查 (压力测试):\n\n"
            details += "测试方法: 运行 CPU 密集型任务，检测实际运行频率\n"
            details += "要求主频: >= {} MHz ({}GHz)\n".format(min_freq_mhz, min_freq_mhz / 1000)
            details += "测试时长: {} 秒\n\n".format(stress_duration)
            
            # 步骤1: 获取 CPU 核心数
            details += "步骤1: 获取 CPU 核心数\n"
            cpu_count_cmd = "nproc"
            cpu_count_result = _run_cmd(cpu_count_cmd, timeout=5)
            
            cpu_cores = 4  # 默认值
            if cpu_count_result['success'] and cpu_count_result['stdout'].strip():
                try:
                    cpu_cores = int(cpu_count_result['stdout'].strip())
                    details += "  CPU 核心数: {}\n".format(cpu_cores)
                except ValueError:
                    details += "  无法获取核心数，使用默认值: {}\n".format(cpu_cores)
            else:
                details += "  无法获取核心数，使用默认值: {}\n".format(cpu_cores)
            
            # 步骤2: 启动 CPU 压力测试（使用 dd 命令，所有系统都有）
            details += "\n步骤2: 启动 CPU 压力测试\n"
            details += "  启动 {} 个 CPU 密集型进程...\n".format(cpu_cores)
            
            # 使用多个 dd 进程进行 CPU 密集计算
            # 每个进程在后台运行，输出到 /dev/null
            stress_pids = []
            for i in range(cpu_cores):
                stress_cmd = "dd if=/dev/zero of=/dev/null bs=1M count=999999 2>/dev/null & echo $!"
                stress_result = _run_cmd(stress_cmd, timeout=2)
                if stress_result['success'] and stress_result['stdout'].strip():
                    pid = stress_result['stdout'].strip()
                    stress_pids.append(pid)
            
            if len(stress_pids) == 0:
                details += "  [X] 无法启动压力测试进程\n"
                return {
                    'success': False,
                    'message': '[X] 无法启动 CPU 压力测试',
                    'details': details
                }
            
            details += "  已启动 {} 个进程，PID: {}\n".format(len(stress_pids), ', '.join(stress_pids[:5]))
            
            try:
                # 步骤3: 等待 CPU 频率提升到最大值
                details += "\n步骤3: 等待 CPU 频率提升\n"
                details += "  等待 {} 秒让 CPU 达到最高频率...\n".format(stress_duration)
                time.sleep(stress_duration)
                
                # 步骤4: 采样实际运行频率和温度（多次采样取最大值）
                details += "\n步骤4: 采样实际运行频率和温度\n"
                max_actual_freq_mhz = 0
                max_temp_celsius = 0
                sample_count = 5
                freq_samples = []
                temp_samples = []
                temp_file = "/sys/class/thermal/thermal_zone1/temp"
                
                for i in range(sample_count):
                    # 采样频率
                    cmd_cur = "cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq 2>/dev/null | sort -nr | head -1"
                    result_cur = _run_cmd(cmd_cur, timeout=2)
                    
                    if result_cur['success'] and result_cur['stdout'].strip():
                        try:
                            cur_freq_khz = int(result_cur['stdout'].strip())
                            cur_freq_mhz = cur_freq_khz / 1000.0
                            freq_samples.append(cur_freq_mhz)
                            if cur_freq_mhz > max_actual_freq_mhz:
                                max_actual_freq_mhz = cur_freq_mhz
                        except ValueError:
                            pass
                    
                    # 同时采样温度
                    cmd_temp = "cat {} 2>/dev/null".format(temp_file)
                    result_temp = _run_cmd(cmd_temp, timeout=2)
                    
                    if result_temp['success'] and result_temp['stdout'].strip():
                        try:
                            temp_value = int(result_temp['stdout'].strip())
                            temp_celsius = temp_value / 1000.0
                            temp_samples.append(temp_celsius)
                            if temp_celsius > max_temp_celsius:
                                max_temp_celsius = temp_celsius
                        except ValueError:
                            pass
                    
                    if i < sample_count - 1:
                        time.sleep(0.5)  # 每次采样间隔 0.5 秒
                
                details += "  采样次数: {}\n".format(len(freq_samples))
                if freq_samples:
                    details += "  采样频率: {} MHz\n".format(', '.join(['{:.0f}'.format(f) for f in freq_samples]))
                    details += "  最高实际频率: {} MHz ({:.2f}GHz)\n".format(max_actual_freq_mhz, max_actual_freq_mhz / 1000)
                else:
                    details += "  [X] 无法读取实际运行频率\n"
                
                if temp_samples:
                    details += "  采样温度: {} °C\n".format(', '.join(['{:.1f}'.format(t) for t in temp_samples]))
                    details += "  最高温度: {:.1f}°C\n".format(max_temp_celsius)
                else:
                    details += "  温度: 无法读取\n"
                
            finally:
                # 步骤5: 停止压力测试进程（必须执行，确保清理）
                details += "\n步骤5: 停止压力测试\n"
                kill_cmd = "kill -9 {} 2>/dev/null || true".format(' '.join(stress_pids))
                _run_cmd(kill_cmd, timeout=5)
                details += "  已停止所有测试进程\n"
            
            # 检查是否成功采样到频率
            if max_actual_freq_mhz == 0 or len(freq_samples) == 0:
                details += "\n[X] 无法获取 CPU 实际运行频率\n"
                details += "\n诊断信息:\n"
                details += "  请检查系统是否支持 cpufreq 子系统\n"
                details += "  尝试手动运行: cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq\n"
                
                return {
                    'success': False,
                    'message': '[X] 无法获取 CPU 实际运行频率',
                    'details': details
                }
            
            # 步骤6: 判断频率是否满足要求
            details += "\n步骤6: 频率检查结果\n"
            details += "  实测最高频率: {} MHz ({:.2f}GHz)\n".format(max_actual_freq_mhz, max_actual_freq_mhz / 1000)
            details += "  要求最低频率: {} MHz ({:.0f}GHz)\n".format(min_freq_mhz, min_freq_mhz / 1000)
            if max_temp_celsius > 0:
                details += "  测试期间最高温度: {:.1f}°C\n".format(max_temp_celsius)
            
            if max_actual_freq_mhz >= min_freq_mhz:
                details += "  结果: [OK] 实际频率满足要求\n"
                message = '[OK] CPU 主频检查通过: 实测 {} MHz ({:.2f}GHz)'.format(
                    max_actual_freq_mhz, max_actual_freq_mhz / 1000
                )
                if max_temp_celsius > 0:
                    message += ', 最高温度 {:.1f}°C'.format(max_temp_celsius)
                return {
                    'success': True,
                    'message': message,
                    'details': details,
                    'max_actual_freq_mhz': max_actual_freq_mhz,
                    'required_freq_mhz': min_freq_mhz,
                    'freq_samples': freq_samples,
                    'max_temp_celsius': max_temp_celsius,
                    'temp_samples': temp_samples
                }
            else:
                details += "  结果: [X] 实际频率不足\n"
                details += "  差距: {:.0f} MHz ({:.2f}GHz)\n".format(
                    min_freq_mhz - max_actual_freq_mhz,
                    (min_freq_mhz - max_actual_freq_mhz) / 1000
                )
                details += "\n可能原因:\n"
                details += "  - CPU 性能限制（节能模式）\n"
                details += "  - BIOS 设置限制了最高频率\n"
                details += "  - 散热问题导致降频\n"
                details += "  - CPU 型号本身不支持 4GHz\n"
                if max_temp_celsius > 60:
                    details += "  - 温度过高 ({:.1f}°C) 可能触发自动降频\n".format(max_temp_celsius)
                
                message = '[X] CPU 实际频率不足: 实测 {:.0f} MHz < {} MHz (要求 >= {}GHz)'.format(
                    max_actual_freq_mhz, min_freq_mhz, min_freq_mhz / 1000
                )
                if max_temp_celsius > 0:
                    message += ', 最高温度 {:.1f}°C'.format(max_temp_celsius)
                
                return {
                    'success': False,
                    'message': message,
                    'details': details,
                    'max_actual_freq_mhz': max_actual_freq_mhz,
                    'required_freq_mhz': min_freq_mhz,
                    'freq_samples': freq_samples,
                    'max_temp_celsius': max_temp_celsius,
                    'temp_samples': temp_samples
                }
                
        except Exception as e:
            # 确保即使出错也清理压力测试进程
            try:
                if 'stress_pids' in locals() and stress_pids:
                    kill_cmd = "kill -9 {} 2>/dev/null || true".format(' '.join(stress_pids))
                    _run_cmd(kill_cmd, timeout=5)
            except:
                pass
            
            return {
                'success': False,
                'message': '[X] CPU 主频检查出错',
                'details': 'Exception: {}'.format(str(e))
            }
