收起左侧

UNAS201P安装飞牛系统后风扇控制脚本分享

0
回复
22
查看
[ 复制链接 ]

1

主题

25

回帖

0

牛值

江湖小虾

几年前买的UNAS 201P,当时硬件性价比还可以,就买了,但系统不是太好用。去年接触了飞牛系统后觉得很不错,就在另一个小电脑上测试学习。年前刚刚把UNAS安装了飞牛的系统,但是无法直接使用飞牛系统控制风扇,所以才弄了这个脚本。当然也都是基于网络搜索各位大神的资料后,学习优化后改为可以用于UNAS的脚本。

需要新建两个文件

1.fan_control.sh 具体控制风扇的程序

2.fan_control.service 控制定时执行脚本的服务

在这之前可能需要安装基础依赖:

  • hddtemp 测试硬盘温度
  • lm-sensors 测试CPU温度
  • i2c-tools 写入风扇数值
  • hdparm 检测硬盘是否休眠
  • smartmontools 检测硬盘smart信息
apt update && apt install -y hdparm smartmontools lm-sensors i2c-tools hddtemp
sensors-detect  # 按提示完成传感器检测(一路按回车即可)
sudo sensors-detect --auto #这个命令会自动检测你的CPU温度传感器并加载驱动,全程自动无需手动操作

注:hddtemp 在 Debian/Ubuntu 22.04 及以后(包括 fnOS/Debian bookworm)已被官方仓库移除,所以 apt install hddtemp 会报“Package ‘hddtemp’ has no installation candidate”。

仍想用原版 hddtemp,可手动装旧 deb Ubuntu 20.04 的最后一个构建在 22.04/Debian12 上依旧能跑,依赖无变化:

wget http://archive.ubuntu.com/ubuntu/pool/universe/h/hddtemp/hddtemp\_0.3-beta15-53\_amd64.deb 
sudo apt install ./hddtemp\_0.3-beta15-53\_amd64.deb

装完即可sudo hddtemp /dev/sd?测试是否安装成功

若以后不再需要,用 sudo apt remove hddtemp 即可干净卸载 。

安装完依赖后就是具体脚本内容:

vim /usr/local/bin/fan_control.sh,使用命令新建sh文件后,把脚本内容拷贝进去


#!/bin/bash
#PIDFILE=/run/fan_control.pid
#echo $$ > "$PIDFILE"
#Bash(以及兼容的 shell)里常见的“严格模式”起手式,它把两条选项一次打开,让脚本在出错时立刻停止,防止“带病继续跑”。
set -eo pipefail
# ================================== 配置项 ==================================
STATE_FILE="/var/run/fan_control.state"    # 持久化存储当前状态
LOG_FILE="/var/log/fan_control.log"        # 日志文件
FAN_CTRL_BIN="/usr/sbin/i2cset"            # 风扇设置命令
FAN_CTRL=(/usr/sbin/i2cset -y 0 0x54 0xf0) # 风扇设置命令带参数
CHECK_INTERVAL=60                          # 检测间隔(秒)
SAFE_CHECKS_MAX=3                          # 连续n次检测正常才降速后降低风扇速度
MAX_CPU_TEMP=65                            # 降低CPU阈值,更保守
MAX_HDD_TEMP=45                            # 降低硬盘阈值,保护数据
SAFE_FAN_SPEED=25                          # 降低默认转速,更安静
INCREASE_STEP=5                            # 减小步进,更平滑
DECREASE_STEP=5                            # 新增降温步进
MAX_FAN_SPEED=100                          # 最高转速
MIN_FAN_SPEED=20                           # 最低转速
LO**AX_SIZE=10485760                      # 日志最大10MB
# 颜色定义(避免未定义导致的输出异常)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# =============================================================================

# 初始化日志和状态文件
init_files() {
    # 创建日志文件(确保权限)
    if [ ! -f "$LOG_FILE" ]; then
        touch "$LOG_FILE" 2>/dev/null || {
            echo "错误:无法创建日志文件 $LOG_FILE"
            exit 1
        }
        chmod 644 "$LOG_FILE"
    fi

    # 日志轮转检查
    if [ -f "$LOG_FILE" ]; then
        local log_size=$(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null)
        if [ "$log_size" -gt "$LO**AX_SIZE" ]; then
            mv "$LOG_FILE" "${LOG_FILE}.old"
            touch "$LOG_FILE"
            log "日志文件已轮转(大小超过 ${LO**AX_SIZE} 字节)"
        fi
    fi

    # 确保状态文件目录存在(/var/run可能被清空)
    mkdir -p "$(dirname "$STATE_FILE")"
  
    if [ ! -f "$STATE_FILE" ]; then
        # 使用原子写入,避免文件内容不完整
        {
            echo "current_speed=$SAFE_FAN_SPEED"
            echo "last_action=initialized"
            echo "last_temp_check=$(date +%s)"
            echo "consecutive_safe_checks=0"
        } > "$STATE_FILE"
        chmod 644 "$STATE_FILE"

        # 初始化风扇转速
        if control_fan "$SAFE_FAN_SPEED" >/dev/null 2>&1; then
            log "初始化風扇轉速為 $SAFE_FAN_SPEED%"
        else
            log "警告:初始化風扇轉速失敗 (退出碼 $?)"
        fi
    fi
}

log() {
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] $1" >> "$LOG_FILE" 2>/dev/null
}

read_state() {
    # 先检查文件是否存在,不存在则初始化
    if [ ! -f "$STATE_FILE" ]; then
        init_files
    fi

    # 安全读取状态文件,避免单个变量错误导致整体失败
    # 逐个读取变量,而不是直接source整个文件
    current_speed=$(grep -E '^current_speed=' "$STATE_FILE" | cut -d'=' -f2 | head -1)
    last_action=$(grep -E '^last_action=' "$STATE_FILE" | cut -d'=' -f2- | head -1)
    last_temp_check=$(grep -E '^last_temp_check=' "$STATE_FILE" | cut -d'=' -f2 | head -1)
    consecutive_safe_checks=$(grep -E '^consecutive_safe_checks=' "$STATE_FILE" | cut -d'=' -f2 | head -1)

    # 变量默认值兜底
    current_speed=${current_speed:-$SAFE_FAN_SPEED}
    last_action=${last_action:-"unknown"}
    last_temp_check=${last_temp_check:-$(date +%s)}
    consecutive_safe_checks=${consecutive_safe_checks:-0}

    # 验证变量有效性,无效则重置
    if ! [[ "$current_speed" =~ ^[0-9]+$ ]]; then
        current_speed=$SAFE_FAN_SPEED
    fi
    if ! [[ "$consecutive_safe_checks" =~ ^[0-9]+$ ]]; then
        consecutive_safe_checks=0
    fi
}

update_state() {
    local speed=$1
    local action=$2
    local safe_checks=${3:-0}
  
    # 先记录日志(避免日志污染状态文件)
    # log "更新状态 - 转速: $speed | 操作: $action | 连续安全检测: $safe_checks"
  
    # 使用临时文件原子写入,避免写入过程中文件损坏
    local temp_file=$(mktemp /tmp/fan_control.XXXXXX)
    {
        echo "current_speed=$speed"
        echo "last_action=$action"
        echo "last_temp_check=$(date +%s)"
        echo "consecutive_safe_checks=$safe_checks"
    } > "$temp_file"
  
    # 原子替换状态文件,避免读取时文件内容不完整
    mv "$temp_file" "$STATE_FILE" || {
        log "警告:更新状态文件失败"
        rm -f "$temp_file"
    }
}

# 优化的CPU温度获取(多种方法,增加容错)
get_cpu_temp() {
    local temp=""
    # 方法1: sensors命令(需要lm-sensors)获取多核的最高温度
    if [ -z "$temp" ] && command -v sensors >/dev/null 2>&1; then
        raw_temps=( $(sensors 2>/dev/null | awk '/^Core [0-9]:/ {gsub(/\+|°C/,"",$3); print $3}') )
        # 最高温度
        temp=$(printf '%s\n' "${raw_temps[@]}" | sort -nr | head -n1)
        # log "cpu(sensors): $temp"
    fi

    # 方法2: thermal_zone(最常见)
    if [ -z "$temp" ]; then
        raw_temps=( $(cat /sys/class/thermal/thermal_zone*/temp 2>/dev/null) )
        # 最高温度
        max_temp=$(printf '%s\n' "${raw_temps[@]}" | sort -nr | head -n1)
        if [ -n "$max_temp" ] && [ "$max_temp" -gt 1000 ]; then
            temp=$((max_temp / 1000))
        fi
        # log "cpu(thermal): $temp"
    fi

    # 方法3: hwmon(适用于某些主板)
    if [ -z "$temp" ]; then
        local hwmon_temp=$(find /sys/class/hwmon -name "temp*_input" 2>/dev/null | head -1)
        if [ -n "$hwmon_temp" ]; then
            local raw_temp=$(cat "$hwmon_temp" 2>/dev/null)
            if [ -n "$raw_temp" ] && [ "$raw_temp" -gt 1000 ]; then
                temp=$((raw_temp / 1000))
                #log "cpu(hwmon): $temp"
            fi
        fi
    fi

    # 方法4: 尝试ACPI
    if [ -z "$temp" ] && command -v acpi >/dev/null 2>&1; then
        temp=$(acpi -t 2>/dev/null | grep -oP '\d+\.\d+' | cut -d. -f1 | head -1)
        #log "cpu(acpi): $temp"
    fi

    # 方法5:尝试proc文件系统
    if [ -z "$temp" ]; then
        local proc_temp=$(cat /proc/cpuinfo 2>/dev/null | grep -i 'temperature' | grep -oP '\d+' | head -1)
        if [ -n "$proc_temp" ]; then
            temp=$proc_temp
            #log "cpu(proc): $proc_temp"
        fi
    fi

    temp_int=${temp%.*}  # 去掉小数点及后面的内容,即取整
    if [ -n "$temp_int" ] && [ "$temp_int" -gt 0 ] && [ "$temp_int" -lt 150 ]; then
        echo "$temp_int"
        return 0
    fi
    log "警告:所有方法失败,无法获取CPU温度"
    return 1
}

# 优化的硬盘温度获取
get_hdd_temp() {
    local max_hdd_temp=0
    local hdd_count=0

    # 获取所有硬盘设备(排除回环设备和分区)
    local hdd_devices=$(lsblk -d -o NAME,TYPE 2>/dev/null | awk '$2=="disk" && $1!~/loop/ {print $1}')

    for dev in $hdd_devices; do
        local device="/dev/$dev"
        # 检查设备是否存在
        [ -b "$device" ] || continue

        # 检查是否休眠(避免唤醒)
        local power_state=$(hdparm -C "$device" 2>/dev/null | grep -i 'drive state')
        if echo "$power_state" | grep -qiE 'standby|sleeping'; then
            log "$dev 已休眠,跳过温度检测"
            continue
        fi

        local temp=""

        # 优先使用smartctl
        if command -v smartctl >/dev/null 2>&1; then
            temp=$(smartctl -A "$device" 2>/dev/null | \
                   grep -iE '194|Temperature_Celsius' | \
                   awk '{print $10}' | head -1)
            # log "$dev 温度(smartctl): $temp"
        fi

        # 备用hddtemp
        if [ -z "$temp" ] && command -v hddtemp >/dev/null 2>&1; then
            temp=$(hddtemp -n "$device" 2>/dev/null)
            # log "$dev 温度(hddtemp): $temp"
        fi

        # 验证温度有效性
        if [ -n "$temp" ] && [ "$temp" -gt 0 ] && [ "$temp" -lt 100 ]; then
            hdd_count=$((hdd_count + 1))
            if [ "$temp" -gt "$max_hdd_temp" ]; then
                max_hdd_temp=$temp
            fi
        fi
    done

    if [ "$max_hdd_temp" -gt 0 ]; then
        echo "$max_hdd_temp"
        return 0
    fi

    return 1
}

control_fan() {
    # 判断命令可用
    if [ ! -x "$FAN_CTRL_BIN" ]; then
        log "错误:未找到风扇控制程序或不可执行 $FAN_CTRL_BIN"
        return 1
    fi
    local target_speed=$1

    # 限制转速范围
    if [ "$target_speed" -lt "$MIN_FAN_SPEED" ]; then
        target_speed=$MIN_FAN_SPEED
    elif [ "$target_speed" -gt "$MAX_FAN_SPEED" ]; then
        target_speed=$MAX_FAN_SPEED
    fi

    # 执行风速设置
    if eval "${FAN_CTRL[@]}" "$target_speed" >/dev/null 2>&1; then
        return 0
    else
        log "错误:设置风扇转速 $target_speed% 失败 (退出码: $?)"
        return 1
    fi
}

# 优化的温度监控(更平滑的调速策略)
monitor_temps() {
    read_state
    local current_speed=${current_speed:-$SAFE_FAN_SPEED}
    local safe_checks=${consecutive_safe_checks:-0}
    local cpu_temp=$(get_cpu_temp)
    local hdd_temp=$(get_hdd_temp)

    # 至少需要一个有效温度
    if [ -z "$cpu_temp" ] && [ -z "$hdd_temp" ]; then
        log "警告:无法获取任何温度数据,保持当前转速"
        return
    fi

    # 计算温度超标程度
    local cpu_overheat=0
    local hdd_overheat=0

    if [ -n "$cpu_temp" ]; then
        cpu_overheat=$((cpu_temp - MAX_CPU_TEMP))
        [ $cpu_overheat -lt 0 ] && cpu_overheat=0
    fi

    if [ -n "$hdd_temp" ]; then
        hdd_overheat=$((hdd_temp - MAX_HDD_TEMP))
        [ $hdd_overheat -lt 0 ] && hdd_overheat=0
    fi

    local max_overheat=$cpu_overheat
    [ $hdd_overheat -gt $max_overheat ] && max_overheat=$hdd_overheat

    local target_speed=$current_speed
    local action="温度正常"

    if [ $max_overheat -gt 0 ]; then
        # 超温:根据超标程度调整增幅
        safe_checks=0
        if [ $max_overheat -ge 10 ]; then
            # 严重超温:快速增加
            target_speed=$((current_speed + INCREASE_STEP * 3))
            action="严重超温!快速提升转速"
        elif [ $max_overheat -ge 5 ]; then
            # 中度超温
            target_speed=$((current_speed + INCREASE_STEP * 2))
            action="中度超温,提升转速"
        else
            # 轻度超温
            target_speed=$((current_speed + INCREASE_STEP))
            action="轻度超温,微调转速"
        fi
    else
        # 温度正常:逐步降低转速(避免频繁波动)
        safe_checks=$((safe_checks + 1))
        if [ $safe_checks -ge $SAFE_CHECKS_MAX ] && [ $current_speed -gt $SAFE_FAN_SPEED ]; then
            # 连续n次检测正常才降速
            target_speed=$((current_speed - DECREASE_STEP))
            [ $target_speed -lt $SAFE_FAN_SPEED ] && target_speed=$SAFE_FAN_SPEED
            action="温度持续正常,降低转速"
            safe_checks=0
        else
            # 根据检测间隔*次数计算温度正常持续时间
            total_time=$(convert_seconds $((CHECK_INTERVAL * safe_checks)))
            if [ $safe_checks -gt $SAFE_CHECKS_MAX ]; then
                action="温度正常(${total_time})"
            else
                action="温度正常(${safe_checks}/$SAFE_CHECKS_MAX)"
            fi
        fi
    fi

    # 执行调整
    if [ $target_speed -ne $current_speed ]; then
        if control_fan "$target_speed"; then
            log "⚙️  $action | CPU: ${cpu_temp:-N/A}°C | HDD: ${hdd_temp:-N/A}°C | 转速: ${current_speed}% → ${target_speed}%"
            update_state "$target_speed" "$action" "$safe_checks"
        fi
    else
        log "✓ CPU: ${cpu_temp:-N/A}°C | HDD: ${hdd_temp:-N/A}°C | 转速: ${current_speed}% | ${action}"
        update_state "$target_speed" "$action" "$safe_checks"
    fi
}

show_status() {
    read_state
    local cpu_temp=$(get_cpu_temp)
    local hdd_temp=$(get_hdd_temp)

    echo -e "${GREEN}===== 飞牛NAS风扇温控状态 =====${NC}"
    # echo "from state:consecutive_safe_checks=${consecutive_safe_checks}"
    echo -e "当前风扇转速: ${YELLOW}${current_speed}%${NC}"
    # 判断CPU温度值是否有值
    if [ -n "$cpu_temp" ]; then
        echo -e "CPU温度: ${cpu_temp}°C (阈值: ${MAX_CPU_TEMP}°C)"
    else
        echo -e "CPU温度: 未检测到"
    fi
    # 判断硬盘温度值是否有值
    if [ -n "$hdd_temp" ]; then
        echo -e "硬盘温度: ${hdd_temp}°C (阈值: ${MAX_HDD_TEMP}°C)"
    else
        echo -e "硬盘温度: 未检测到或者硬盘均休眠"
    fi
    echo ""
    echo "最后操作: $last_action"
    echo "检测间隔: ${CHECK_INTERVAL}秒"
    echo "转速范围: ${MIN_FAN_SPEED}%-${MAX_FAN_SPEED}%"
    echo -e "${GREEN}==============================${NC}"
}

# 手动设置转速
set_speed() {
    local speed=$1
    if [ -z "$speed" ]; then
        echo "用法: $0 --set-speed <20-100>"
        exit 1
    fi

    if control_fan "$speed"; then
        update_state "$speed" "手动设置为 ${speed}%" 0
        echo -e "${GREEN}✓ 风扇转速已设置为 ${speed}%${NC}"
    else
        echo -e "${RED}✗ 设置失败${NC}"
        exit 1
    fi
}

# 秒数转人性化时长:x年x月x天x小时x分钟x秒
convert_seconds() {
    # 检查传入参数是否为正整数
    if [ $# -ne 1 ] || ! [[ $1 =~ ^[0-9]+$ ]]; then
        return 1
    fi

    # 定义时间换算基准(日常通用标准,可根据需求修改)
    local SECONDS=$1
    local SEC_PER_MIN=60
    local SEC_PER_HOUR=$((SEC_PER_MIN * 60))
    local SEC_PER_DAY=$((SEC_PER_HOUR * 24))
    local SEC_PER_MONTH=$((SEC_PER_DAY * 30))  # 按30天/月
    local SEC_PER_YEAR=$((SEC_PER_DAY * 365))  # 按365天/年

    # 计算各单位的数值,取整后更新剩余秒数
    local year=$((SECONDS / SEC_PER_YEAR))
    local remain=$((SECONDS % SEC_PER_YEAR))

    local month=$((remain / SEC_PER_MONTH))
    remain=$((remain % SEC_PER_MONTH))

    local day=$((remain / SEC_PER_DAY))
    remain=$((remain % SEC_PER_DAY))

    local hour=$((remain / SEC_PER_HOUR))
    remain=$((remain % SEC_PER_HOUR))

    local min=$((remain / SEC_PER_MIN))
    local sec=$((remain % SEC_PER_MIN))

    # 拼接结果(仅保留有值的单位,避免0年0月等无效显示)
    local result=""
    [ $year -gt 0 ] && result+="${year}年"
    [ $month -gt 0 ] && result+="${month}月"
    [ $day -gt 0 ] && result+="${day}天"
    [ $hour -gt 0 ] && result+="${hour}小时"
    [ $min -gt 0 ] && result+="${min}分钟"
    # 最后保留秒,即使秒数为0(比如刚好1分钟,显示1分钟0秒)
    result+="${sec}秒"

    # 输出最终结果
    echo "${result}"
    return 0
}

main() {
    # 必须使用root运行
    if [ "$(id -u)" -ne 0 ]; then
        echo -e "${RED}错误:请使用 sudo 运行${NC}"
        exit 1
    fi
    # 加载i2c工具
    modprobe i2c-dev >/dev/null 2>&1 || log "警告:加载i2c-dev模块失败"

    init_files

    case "$1" in
        --monitor)
            log "========== 执行单次风扇温控检测 =========="
            echo -e "${GREEN}执行单次风扇温控检测${NC}"
            monitor_temps
            exit 0
            ;;
        --service)
            log "========== 风扇温控服务启动 =========="
            echo -e "${GREEN}风扇温控服务已启动(间隔: ${CHECK_INTERVAL}秒)${NC}"
            echo "使用 Ctrl+C 停止,或运行: sudo $0 --status 查看状态"
            ( while true; do
                 monitor_temps
                 sleep $CHECK_INTERVAL
             done ) &
            exit 0
            ;;
        --status)
            show_status
            ;;
        --log)
            if [ -f "$LOG_FILE" ]; then
                tail -n 50 -f "$LOG_FILE"
            else
                echo "日志文件不存在"
            fi
            ;;
        --set-speed)
            set_speed "$2"
            ;;
        --restart)
            log "手动重启服务"
            control_fan "$SAFE_FAN_SPEED"
            update_state "$SAFE_FAN_SPEED" "手动重启" 0
            echo -e "${GREEN}✓ 服务已重启,转速重置为 ${SAFE_FAN_SPEED}%${NC}"
            ;;
        --test)
            echo "执行温度检测测试..."
            local cpu=$(get_cpu_temp || echo "未检测到")
            local hdd=$(get_hdd_temp || echo "未检测到(可能硬盘均休眠)")
            echo "CPU温度: $cpu°C"
            echo "硬盘温度: $hdd°C"
            ;;
        *)
            echo "飞牛NAS风扇温控脚本"
            echo ""
            echo "用法:"
            echo "  sudo $0 --monitor          执行单次温控检测"
            echo "  sudo $0 --service          启动温控服务"
            echo "  sudo $0 --status           查看当前状态"
            echo "  sudo $0 --log              查看实时日志"
            echo "  sudo $0 --set-speed <值>   设置风扇转速(20-100)"
            echo "  sudo $0 --restart          重启服务并重置风扇转速"
            echo "  sudo $0 --test             测试温度检测"
            exit 1
            ;;
    esac
}

main "$@"

新建service文件

vim /etc/systemd/system/fan_control.service

内容如下:

[Unit]
Description=飞牛风扇控制服务

[Service]
Type=simple
ExecStartPre=/bin/sleep 60
ExecStart=/usr/local/bin/fan_control.sh --monitor
Restart=always
RestartSec=60
User=root
WorkingDirectory=/root
StandardOutput=append:/var/log/fan_control.log
StandardError=append:/var/log/fan_control.log

[Install]
WantedBy=multi-user.target

使用命令启用服务和设置开机启动

# 重新加载 systemd 配置(必须执行)
sudo systemctl daemon-reload

#设置开机启动
sudo systemctl enable fan_control.service

# 检查状态(最重要!)
sudo systemctl status fan_control.service   # 第一次执行后的状态

# 查看日志
sudo journalctl -u fan_control.service -f   # 实时跟踪
# 或直接看你脚本里的日志文件
tail -f /var/log/fan_control.log

也可使用sh脚本指令查看状态信息等:

执行单次检测 
/usr/local/bin/fan\_control.sh --monitor

查看状态(验证是否正常运行) 
/usr/local/bin/fan\_control.sh --status

查看log 
/usr/local/bin/fan\_control.sh --log

设置风扇转速(20-100)
/usr/local/bin/fan\_control.sh --set-speed <值>

收藏
送赞
分享
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则