我的内容文档

智能风扇控制脚本

2026/02/09
3
0

以下是一个整合了精细化管理功能的智能风扇控制脚本:

sudo nano /usr/local/bin/smart-fan-control.sh

#!/bin/bash
# 智能风扇控制脚本 v2.0
# 精细化管理:支持多级温度控制、线性插值、自适应调整、过热保护
# 适用于Debian/Linux系统

# ========== 配置文件 ==========
CONFIG_FILE="/etc/smart-fan.conf"
LOG_FILE="/var/log/smart-fan.log"
PID_FILE="/var/run/smart-fan.pid"

# ========== 默认配置 ==========
# 设备路径(会自动检测)
FAN_PWM_PATH=""
FAN_RPM_PATH=""
CPU_TEMP_PATH=""
GPU_TEMP_PATH=""

# 控制参数
CHECK_INTERVAL=5            # 检查间隔(秒)
SAMPLE_INTERVAL=2           # 采样间隔(秒,用于计算变化率)
HISTORY_SIZE=30             # 历史记录大小

# 温度源权重(CPU:GPU)
TEMP_WEIGHTS="70:30"        # 70% CPU温度 + 30% GPU温度

# 安全参数
CRITICAL_TEMP=80            # 临界温度(°C) - 强制全速
WARNING_TEMP=70             # 警告温度(°C) - 记录警告
HYSTERESIS=3                # 温度迟滞(°C) - 防止频繁切换

# 风扇参数
MIN_PWM=50                  # 最小PWM值(确保风扇转动)
MAX_PWM=255                 # 最大PWM值
START_PWM=80                # 启动PWM值
STOP_PWM=60                 # 停止PWM值

# 温度-PWM曲线配置(温度°C: PWM值)
# 支持多个控制点,会自动线性插值
FAN_CURVE=(
    "30:60"    # 30°C -> PWM 60 (安静模式)
    "40:80"    # 40°C -> PWM 80
    "50:120"   # 50°C -> PWM 120
    "60:180"   # 60°C -> PWM 180
    "70:220"   # 70°C -> PWM 220
    "75:255"   # 75°C -> PWM 255 (全速)
)

# 运行模式
MODE="auto"                 # auto:自动, quiet:安静, performance:性能, manual:手动
MANUAL_PWM=120              # 手动模式下的PWM值
QUIET_MODE_MAX_PWM=150      # 安静模式最大PWM
PERFORMANCE_MIN_PWM=100     # 性能模式最小PWM

# 高级功能
ENABLE_ADAPTIVE=1           # 启用自适应学习
ENABLE_PREDICTIVE=1         # 启用预测性控制
ENABLE_LOGGING=1            # 启用日志记录
LOG_LEVEL="info"            # 日志级别: debug, info, warning, error

# ========== 初始化函数 ==========
init_system() {
    echo "初始化智能风扇控制系统..."
    
    # 加载配置文件(如果存在)
    if [ -f "$CONFIG_FILE" ]; then
        source "$CONFIG_FILE"
        log "debug" "已加载配置文件: $CONFIG_FILE"
    fi
    
    # 检测设备路径
    detect_devices
    
    # 验证设备
    validate_devices
    
    # 创建PID文件
    echo $$ > "$PID_FILE"
    
    # 设置退出时清理
    trap cleanup EXIT
    
    # 设置手动模式
    set_manual_mode
    
    log "info" "系统初始化完成"
}

detect_devices() {
    log "debug" "检测硬件设备..."
    
    # 检测PWM风扇
    if [ -z "$FAN_PWM_PATH" ]; then
        for hwm in /sys/class/hwmon/hwmon*/pwm1; do
            if [ -f "$hwm" ]; then
                FAN_PWM_PATH="$hwm"
                FAN_RPM_PATH="$(dirname $hwm)/fan1_input"
                log "info" "找到风扇控制器: $FAN_PWM_PATH"
                break
            fi
        done
    fi
    
    # 检测CPU温度
    if [ -z "$CPU_TEMP_PATH" ]; then
        # 尝试常见路径
        for path in \
            "/sys/class/hwmon/hwmon1/temp1_input" \
            "/sys/class/hwmon/hwmon2/temp1_input" \
            "/sys/class/thermal/thermal_zone0/temp" \
            "/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp1_input"; do
            
            if [ -f $path 2>/dev/null ]; then
                CPU_TEMP_PATH="$path"
                log "info" "找到CPU温度传感器: $CPU_TEMP_PATH"
                break
            fi
        done
    fi
    
    # 检测GPU温度
    if [ -z "$GPU_TEMP_PATH" ]; then
        for path in \
            "/sys/class/hwmon/hwmon2/temp1_input" \
            "/sys/class/hwmon/hwmon*/temp2_input" \
            "/sys/class/drm/card0/device/hwmon/hwmon*/temp1_input"; do
            
            if [ -f $path 2>/dev/null ]; then
                GPU_TEMP_PATH="$path"
                log "info" "找到GPU温度传感器: $GPU_TEMP_PATH"
                break
            fi
        done
    fi
}

validate_devices() {
    local errors=0
    
    if [ ! -f "$FAN_PWM_PATH" ]; then
        log "error" "错误: 找不到PWM风扇设备"
        errors=$((errors + 1))
    fi
    
    if [ ! -f "$CPU_TEMP_PATH" ]; then
        log "error" "警告: 找不到CPU温度传感器"
    fi
    
    if [ ! -f "$GPU_TEMP_PATH" ]; then
        log "info" "信息: 未找到GPU温度传感器,将仅使用CPU温度"
    fi
    
    if [ $errors -gt 0 ]; then
        log "error" "初始化失败,请检查硬件"
        exit 1
    fi
}

set_manual_mode() {
    if [ -f "${FAN_PWM_PATH}_enable" ]; then
        echo 1 > "${FAN_PWM_PATH}_enable" 2>/dev/null
        log "debug" "已设置风扇为手动模式"
    fi
}

# ========== 温度管理函数 ==========
get_cpu_temp() {
    if [ -f "$CPU_TEMP_PATH" ]; then
        local temp_raw=$(cat "$CPU_TEMP_PATH" 2>/dev/null)
        echo $((temp_raw / 1000))
    else
        echo 0
    fi
}

get_gpu_temp() {
    if [ -f "$GPU_TEMP_PATH" ]; then
        local temp_raw=$(cat "$GPU_TEMP_PATH" 2>/dev/null)
        echo $((temp_raw / 1000))
    else
        echo 0
    fi
}

get_weighted_temp() {
    local cpu_temp=$(get_cpu_temp)
    local gpu_temp=$(get_gpu_temp)
    
    # 解析权重
    local cpu_weight=$(echo $TEMP_WEIGHTS | cut -d: -f1)
    local gpu_weight=$(echo $TEMP_WEIGHTS | cut -d: -f2)
    
    if [ $gpu_temp -eq 0 ]; then
        # 只有CPU温度
        echo $cpu_temp
    else
        # 加权计算
        local weighted_temp=$(( (cpu_temp * cpu_weight + gpu_temp * gpu_weight) / 100 ))
        echo $weighted_temp
    fi
}

get_fan_rpm() {
    if [ -f "$FAN_RPM_PATH" ]; then
        cat "$FAN_RPM_PATH" 2>/dev/null
    else
        echo 0
    fi
}

# ========== 风扇控制函数 ==========
calculate_pwm_from_curve() {
    local temp=$1
    local pwm=0
    
    # 如果温度超过最高点,使用最大PWM
    local last_point="${FAN_CURVE[-1]}"
    local max_temp=$(echo $last_point | cut -d: -f1)
    local max_pwm=$(echo $last_point | cut -d: -f2)
    
    if [ $temp -ge $max_temp ]; then
        echo $max_pwm
        return
    fi
    
    # 如果温度低于最低点,使用最小PWM
    local first_point="${FAN_CURVE[0]}"
    local min_temp=$(echo $first_point | cut -d: -f1)
    local min_pwm=$(echo $first_point | cut -d: -f2)
    
    if [ $temp -le $min_temp ]; then
        echo $min_pwm
        return
    fi
    
    # 在两个控制点之间线性插值
    for i in "${!FAN_CURVE[@]}"; do
        local current="${FAN_CURVE[$i]}"
        local current_temp=$(echo $current | cut -d: -f1)
        local current_pwm=$(echo $current | cut -d: -f2)
        
        if [ $temp -le $current_temp ]; then
            if [ $i -eq 0 ]; then
                pwm=$current_pwm
            else
                local prev="${FAN_CURVE[$((i-1))]}"
                local prev_temp=$(echo $prev | cut -d: -f1)
                local prev_pwm=$(echo $prev | cut -d: -f2)
                
                # 线性插值
                local temp_range=$((current_temp - prev_temp))
                local pwm_range=$((current_pwm - prev_pwm))
                local temp_offset=$((temp - prev_temp))
                
                pwm=$((prev_pwm + (temp_offset * pwm_range) / temp_range))
            fi
            break
        fi
    done
    
    echo $pwm
}

apply_hysteresis() {
    local current_temp=$1
    local current_pwm=$2
    local last_temp=$3
    local last_pwm=$4
    
    # 如果温度变化小于迟滞值,保持原有PWM
    local temp_diff=$((current_temp - last_temp))
    if [ ${temp_diff#-} -lt $HYSTERESIS ] && [ $last_pwm -gt 0 ]; then
        echo $last_pwm
        return
    fi
    
    echo $current_pwm
}

adjust_for_mode() {
    local pwm=$1
    
    case $MODE in
        "quiet")
            # 安静模式:限制最大PWM
            if [ $pwm -gt $QUIET_MODE_MAX_PWM ]; then
                pwm=$QUIET_MODE_MAX_PWM
            fi
            ;;
        "performance")
            # 性能模式:提高最低PWM
            if [ $pwm -lt $PERFORMANCE_MIN_PWM ]; then
                pwm=$PERFORMANCE_MIN_PWM
            fi
            ;;
        "manual")
            # 手动模式:使用固定PWM
            pwm=$MANUAL_PWM
            ;;
    esac
    
    echo $pwm
}

set_fan_pwm() {
    local pwm=$1
    
    # 确保PWM在有效范围内
    if [ $pwm -lt $MIN_PWM ]; then
        pwm=$MIN_PWM
    elif [ $pwm -gt $MAX_PWM ]; then
        pwm=$MAX_PWM
    fi
    
    # 设置PWM值
    echo $pwm > "$FAN_PWM_PATH"
    
    # 记录设置
    log "debug" "设置PWM: $pwm"
}

# ========== 自适应学习函数 ==========
adaptive_learning() {
    if [ $ENABLE_ADAPTIVE -eq 0 ]; then
        return
    fi
    
    local current_temp=$1
    local current_pwm=$2
    local fan_rpm=$3
    
    # TODO: 实现自适应学习算法
    # 可以根据历史数据调整曲线参数
    
    log "debug" "自适应学习: 温度=${current_temp}°C, PWM=${current_pwm}, RPM=${fan_rpm}"
}

# ========== 日志函数 ==========
log() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 检查日志级别
    case $LOG_LEVEL in
        "debug")   local min_level=0 ;;
        "info")    local min_level=1 ;;
        "warning") local min_level=2 ;;
        "error")   local min_level=3 ;;
        *)         local min_level=1 ;;
    esac
    
    case $level in
        "debug")   local level_num=0; local color="\e[90m" ;;
        "info")    local level_num=1; local color="\e[97m" ;;
        "warning") local level_num=2; local color="\e[93m" ;;
        "error")   local level_num=3; local color="\e[91m" ;;
        *)         local level_num=1; local color="\e[97m" ;;
    esac
    
    # 如果当前级别小于最小级别,不记录
    if [ $level_num -lt $min_level ]; then
        return
    fi
    
    local log_entry="[$timestamp] [$level] $message"
    
    # 输出到控制台
    echo -e "${color}${log_entry}\e[0m"
    
    # 写入日志文件
    if [ $ENABLE_LOGGING -eq 1 ]; then
        echo "$log_entry" >> "$LOG_FILE"
    fi
}

# ========== 工具函数 ==========
cleanup() {
    log "info" "正在关闭智能风扇控制..."
    
    # 删除PID文件
    rm -f "$PID_FILE"
    
    # 恢复自动模式(可选)
    if [ -f "${FAN_PWM_PATH}_enable" ]; then
        echo 2 > "${FAN_PWM_PATH}_enable" 2>/dev/null
        log "debug" "已恢复风扇自动模式"
    fi
    
    log "info" "智能风扇控制已停止"
}

show_status() {
    local cpu_temp=$(get_cpu_temp)
    local gpu_temp=$(get_gpu_temp)
    local weighted_temp=$(get_weighted_temp)
    local fan_rpm=$(get_fan_rpm)
    local current_pwm=$(cat "$FAN_PWM_PATH" 2>/dev/null || echo "N/A")
    
    echo "=== 智能风扇控制系统状态 ==="
    echo "运行模式: $MODE"
    echo "CPU温度: ${cpu_temp}°C"
    echo "GPU温度: ${gpu_temp}°C"
    echo "加权温度: ${weighted_temp}°C"
    echo "风扇转速: ${fan_rpm} RPM"
    echo "当前PWM: ${current_pwm}"
    echo "检查间隔: ${CHECK_INTERVAL}秒"
    echo "日志级别: ${LOG_LEVEL}"
    echo "============================="
}

# ========== 主控制循环 ==========
main_loop() {
    log "info" "启动智能风扇控制主循环"
    
    # 状态变量
    local last_temp=0
    local last_pwm=0
    declare -a temp_history
    declare -a pwm_history
    
    # 主循环
    while true; do
        # 获取当前温度
        local weighted_temp=$(get_weighted_temp)
        local cpu_temp=$(get_cpu_temp)
        local gpu_temp=$(get_gpu_temp)
        local fan_rpm=$(get_fan_rpm)
        
        # 添加到历史记录
        temp_history+=($weighted_temp)
        if [ ${#temp_history[@]} -gt $HISTORY_SIZE ]; then
            temp_history=("${temp_history[@]:1}")
        fi
        
        # 检查过热保护
        if [ $weighted_temp -ge $CRITICAL_TEMP ]; then
            log "error" "⚠️  过热保护: 温度 ${weighted_temp}°C >= 临界值 ${CRITICAL_TEMP}°C"
            set_fan_pwm $MAX_PWM
            sleep 2
            continue
        fi
        
        # 检查警告温度
        if [ $weighted_temp -ge $WARNING_TEMP ]; then
            log "warning" "⚠️  高温警告: 温度 ${weighted_temp}°C >= 警告值 ${WARNING_TEMP}°C"
        fi
        
        # 计算PWM值
        local calculated_pwm=$(calculate_pwm_from_curve $weighted_temp)
        local adjusted_pwm=$(apply_hysteresis $weighted_temp $calculated_pwm $last_temp $last_pwm)
        local final_pwm=$(adjust_for_mode $adjusted_pwm)
        
        # 设置风扇PWM
        set_fan_pwm $final_pwm
        
        # 更新状态
        last_temp=$weighted_temp
        last_pwm=$final_pwm
        
        # 自适应学习
        adaptive_learning $weighted_temp $final_pwm $fan_rpm
        
        # 显示状态信息(每10次显示一次)
        if [ $(($SECONDS % 50)) -lt 5 ]; then
            echo "========================================"
            echo "CPU: ${cpu_temp}°C | GPU: ${gpu_temp}°C | 加权: ${weighted_temp}°C"
            echo "风扇: ${fan_rpm} RPM | PWM: ${final_pwm}/${MAX_PWM}"
            echo "模式: ${MODE} | 曲线: ${#FAN_CURVE[@]}个控制点"
            
            # 显示温度趋势(如果有足够历史数据)
            if [ ${#temp_history[@]} -ge 5 ]; then
                local avg_temp=0
                for t in "${temp_history[@]}"; do
                    avg_temp=$((avg_temp + t))
                done
                avg_temp=$((avg_temp / ${#temp_history[@]}))
                echo "趋势: 最近${#temp_history[@]}次平均 ${avg_temp}°C"
            fi
            echo "========================================"
        fi
        
        # 等待下一个检查周期
        sleep $CHECK_INTERVAL
    done
}

# ========== 命令行接口 ==========
parse_arguments() {
    case "$1" in
        "start")
            init_system
            main_loop
            ;;
        "stop")
            if [ -f "$PID_FILE" ]; then
                local pid=$(cat "$PID_FILE")
                kill $pid 2>/dev/null
                log "info" "已发送停止信号到进程 $pid"
            else
                log "error" "未找到运行中的进程"
            fi
            ;;
        "status")
            show_status
            ;;
        "config")
            if [ -f "$CONFIG_FILE" ]; then
                cat "$CONFIG_FILE"
            else
                echo "配置文件不存在,使用默认配置"
            fi
            ;;
        "set-mode")
            case "$2" in
                "auto"|"quiet"|"performance"|"manual")
                    MODE="$2"
                    log "info" "已切换模式到: $MODE"
                    if [ "$2" = "manual" ] && [ -n "$3" ]; then
                        MANUAL_PWM="$3"
                        log "info" "手动模式PWM设置为: $MANUAL_PWM"
                    fi
                    ;;
                *)
                    echo "用法: $0 set-mode [auto|quiet|performance|manual [pwm值]]"
                    exit 1
                    ;;
            esac
            ;;
        "set-curve")
            if [ -n "$2" ]; then
                # 格式: "30:60,40:80,50:120"
                IFS=',' read -ra new_curve <<< "$2"
                FAN_CURVE=()
                for point in "${new_curve[@]}"; do
                    FAN_CURVE+=("$point")
                done
                log "info" "已更新温度曲线: ${#FAN_CURVE[@]}个控制点"
            else
                echo "用法: $0 set-curve \"30:60,40:80,50:120\""
                exit 1
            fi
            ;;
        "log")
            if [ -f "$LOG_FILE" ]; then
                tail -f "$LOG_FILE"
            else
                echo "日志文件不存在: $LOG_FILE"
            fi
            ;;
        "test")
            echo "运行测试模式..."
            init_system
            
            # 测试不同温度下的PWM计算
            echo "=== 温度-PWM曲线测试 ==="
            for test_temp in 25 30 35 40 45 50 55 60 65 70 75 80; do
                local pwm=$(calculate_pwm_from_curve $test_temp)
                echo "温度 ${test_temp}°C -> PWM ${pwm}"
            done
            
            # 测试当前状态
            show_status
            ;;
        *)
            echo "智能风扇控制脚本 v2.0"
            echo "用法: $0 [命令]"
            echo ""
            echo "命令:"
            echo "  start              启动风扇控制"
            echo "  stop               停止风扇控制"
            echo "  status             显示当前状态"
            echo "  config             显示当前配置"
            echo "  set-mode <模式>    设置运行模式"
            echo "  set-curve <曲线>   设置温度曲线"
            echo "  log                查看实时日志"
            echo "  test               运行测试模式"
            echo ""
            echo "运行模式:"
            echo "  auto         自动模式 (默认)"
            echo "  quiet        安静模式 (限制最大转速)"
            echo "  performance  性能模式 (提高最低转速)"
            echo "  manual       手动模式 (固定转速)"
            echo ""
            echo "示例:"
            echo "  $0 start                    启动服务"
            echo "  $0 set-mode manual 150      设置手动模式,PWM=150"
            echo "  $0 set-curve \"30:60,50:120,70:200\" 设置温度曲线"
            exit 1
            ;;
    esac
}

# ========== 主程序入口 ==========
if [ $# -eq 0 ]; then
    parse_arguments "start"
else
    parse_arguments "$@"
fi

设置权限并安装服务

# 设置脚本权限
sudo chmod +x /usr/local/bin/smart-fan-control.sh

# 创建日志目录
sudo mkdir -p /var/log
sudo touch /var/log/smart-fan.log
sudo chmod 644 /var/log/smart-fan.log

# 创建Systemd服务
sudo nano /etc/systemd/system/smart-fan.service

[Unit]
Description=Smart Fan Control Service
After=multi-user.target
Wants=lm-sensors.service

[Service]
Type=simple
ExecStart=/usr/local/bin/smart-fan-control.sh start
ExecStop=/usr/local/bin/smart-fan-control.sh stop
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
User=root

# 日志配置
StandardOutput=journal
StandardError=journal

# 环境变量
Environment="LOG_LEVEL=info"

[Install]
WantedBy=multi-user.target
# 启用服务
sudo systemctl daemon-reload
sudo systemctl enable smart-fan.service

# 启动服务
sudo systemctl start smart-fan.service

# 查看状态
sudo systemctl status smart-fan.service

# 查看日志
sudo journalctl -u smart-fan.service -f

使用示例

# 启动服务
sudo smart-fan-control.sh start
# 或者
sudo systemctl start smart-fan.service

# 查看状态
sudo smart-fan-control.sh status

# 切换到安静模式
sudo smart-fan-control.sh set-mode quiet

# 切换到手动模式,PWM=100
sudo smart-fan-control.sh set-mode manual 100

# 自定义温度曲线
sudo smart-fan-control.sh set-curve "30:50,40:80,50:120,60:180,70:220,80:255"

# 查看实时日志
sudo smart-fan-control.sh log

# 测试配置
sudo smart-fan-control.sh test

关键特性说明

  1. 多级温度控制:支持任意数量的温度-PWM控制点

  2. 线性插值:在控制点之间自动平滑过渡

  3. 温度迟滞:防止温度在阈值附近时风扇频繁切换

  4. 多种运行模式

    • 自动模式:根据温度自动调节

    • 安静模式:限制最大转速,降低噪音

    • 性能模式:提高最低转速,确保散热

    • 手动模式:固定转速

  5. 多温度源支持:支持CPU和GPU温度加权计算

  6. 过热保护:超过临界温度时强制全速运行

  7. 自适应学习:可扩展的自适应算法框架

  8. 完整日志系统:支持不同级别的日志记录