January 24Jan 24 I've got an Asus Pro WS W880-ACE SE motherboard in a Jonsbo N5 case with Noctua chassis fans and an LSI 9305-16i HBA. It is a cool case, but hard drive cooling is not exactly one of its strong suits. I'm considering a 3D-printed hard drive fan addon, but first, I'd really very much like my chassis fans to just spin faster when the drives heat up.Seems like FanCtrl Plus only works with a PWM controller, and I can't find any place in the BIOS to set the W880's fan control to PWM. I guess it's IPMI only.And I can't find any settings in IPMI Tools to turn the fans faster when the drives heat up.A user in the W680 thread got ChatGPT to write a script to do this. But this seems like such a basic function, just curious if anyone has any other ideas? Something I'm overlooking?
February 16Feb 16 I'm the guy with the script from the other thread. I searched and tested extensively... nothing worked if you want to use HDD temperatures as a trigger. I also tried modifying the IPMItools plugin... to no avail. The only thing is, my IPMI card from the mainboard has three temperature sensors. You could put one on an HDD and control it based on that. But that's inaccurate if you have many HDDs.I'd be happy to give you my current script, which has been refined over several months. Analyze it with AI and test it if you like. I'm very happy with it now, and after all this time, I have a lot of confidence in it.Fan_Control (at startup of array):Fan_Control#!/bin/bash# Fan_Control# Only for ASUS Pro WS W680-ACE IPMI PCIe-CARD# V1.9 - Thermal-focused disks.ini Parsing - 27.01.2026# Umbau auf device-basiertes Parsing ohne Status-Filter.# Inkl. Sicherheitswerten (secure_pwm) und Peak-Hold für alle Sensoren.# Fix: SecureMode greift nur noch bei echtem Array-Stop (mdState Check). # =======================# ⚙️ Globale Einstellungen# ======================= INTERVAL=5 # Intervall in Sekunden für SchleifendurchlaufDRY_RUN=0 # 1 = Testmodus (kein IPMI-Befehl), 0 = aktivFAILSAFE_FAN_SPEED=60 # Notfallgeschwindigkeit bei IPMI-Fehler AFTER_ACTIVITY_COOLDOWN_HDD=900 # Zeit (Sekunden), Unraid legt HDDs schlafen - Zeit für Lüfter auf letzten Wert bevor BaseAFTER_PEAK_TEMP_COOLDOWN_HDD=300 # Zeit (Sekunden), Peak-Hold HDDAFTER_PEAK_TEMP_COOLDOWN_NVME=180 # Zeit (Sekunden), Peak-Hold NVMe # Individuelle Peak-Cooldowns für TR-SensorenAFTER_PEAK_TEMP_COOLDOWN_TR1=180 # RAM CooldownAFTER_PEAK_TEMP_COOLDOWN_TR2=180 # HBA CooldownAFTER_PEAK_TEMP_COOLDOWN_TR3=180 # GPU Cooldown MAX_SAFE_HDD_TEMP=53 # Temperaturgrenze HDDs → Failsafe 100%MAX_SAFE_NVME_TEMP=80 # Temperaturgrenze NVMe → Failsafe 100%MAX_SAFE_CPU_TEMP=95 # Temperaturgrenze CPU → Failsafe 100% # Individuelle Max-Safe-Temperaturen für TR-SensorenMAX_SAFE_TR1_TEMP=65 # RAM MaxMAX_SAFE_TR2_TEMP=75 # HBA MaxMAX_SAFE_TR3_TEMP=85 # GPU Max LOGFILE="/tmp/user.scripts/tmpScripts/Fan_Control/log.txt" # ====================================# ⚙️ Sicherheits-Lüfterwerte (NUR wenn Array wirklich GESTOPPT ist)# ====================================declare -A secure_pwmsecure_pwm["CHA_FAN1"]=59 # unten links HDDssecure_pwm["CHA_FAN2"]=59 # unten mitte HDDssecure_pwm["CHA_FAN3"]=59 # unten rechts HDDssecure_pwm["CHA_FAN4"]=60 # oben links MB & links hinten HBAsecure_pwm["CHA_FAN5"]=30 # oben mittesecure_pwm["CHA_FAN6"]=30 # oben rechts Richtung CPUsecure_pwm["CHA_FAN7"]=62 # hinten Gehäuseentlüftungsecure_pwm["CHA_FAN8"]=42 # CPU Kühler # ====================================# 🔍 Preflight: benötigte Tools prüfen# ==================================== missing_tools=()for cmd in sensors ipmi-raw ipmi-sensors awk; do if ! command -v "$cmd" >/dev/null 2>&1; then missing_tools+=("$cmd") fidone if (( ${#missing_tools[@]} > 0 )); then timestamp=$(date '+%F | %T') echo "❌ [$timestamp] Missing required tools: ${missing_tools[*]}" >> "$LOGFILE" exit 1fi # ====================================# 🧭 Steuerlogik je CHA_FANx (1–8)# ====================================declare -A fan_sourcesfan_sources["CHA_FAN1"]="HDD" # unten links HDDsfan_sources["CHA_FAN2"]="HDD" # unten mitte HDDsfan_sources["CHA_FAN3"]="HDD" # unten rechts HDDsfan_sources["CHA_FAN4"]="HDD CPU NVME TR2 TR3" # oben links MB & links hinten HBAfan_sources["CHA_FAN5"]="CPU NVME TR3" # oben mittefan_sources["CHA_FAN6"]="CPU TR1 TR3" # oben rechts Richtung CPUfan_sources["CHA_FAN7"]="HDD CPU NVME TR1 TR3" # hinten Gehäuseentlüftungfan_sources["CHA_FAN8"]="CPU" # CPU Kühler # ====================================# 💾 Device-Zuordnungen# ====================================declare -A fan_disksfan_disks["CHA_FAN1"]="sde sdf sdg sdh sdc sdd"fan_disks["CHA_FAN2"]="sde sdf sdg sdh sdc sdd"fan_disks["CHA_FAN3"]="sde sdf sdg sdh sdc sdd"fan_disks["CHA_FAN4"]="sde sdf sdg sdh sdc sdd"fan_disks["CHA_FAN7"]="sde sdf sdg sdh sdc sdd" declare -A fan_nvmesfan_nvmes["CHA_FAN4"]="nvme0n1 nvme1n1 nvme2n1"fan_nvmes["CHA_FAN5"]="nvme0n1 nvme1n1 nvme2n1"fan_nvmes["CHA_FAN7"]="nvme0n1 nvme1n1 nvme2n1" # ====================================# 📈 Kurven-Definitionen# ====================================declare -A hdd_curve_maphdd_curve_map["CHA_FAN1"]="1=25 40=59 45=70 50=90 55=100"hdd_curve_map["CHA_FAN2"]="1=25 40=59 45=70 50=90 55=100"hdd_curve_map["CHA_FAN3"]="1=25 40=59 45=70 50=90 55=100"hdd_curve_map["CHA_FAN4"]="1=30 30=50 40=60 45=72 55=85"hdd_curve_map["CHA_FAN5"]="1=30 40=50 45=70 50=90 55=100"hdd_curve_map["CHA_FAN6"]="1=30 40=50 45=70 50=90 55=100"hdd_curve_map["CHA_FAN7"]="1=30 30=52 40=62 45=72 55=100"hdd_curve_map["CHA_FAN8"]="1=42 40=50 45=70 50=90 55=100" declare -A nvme_curve_mapnvme_curve_map["CHA_FAN4"]="1=30 45=50 50=60 70=90 80=100"nvme_curve_map["CHA_FAN5"]="1=30 45=50 50=60 70=90 80=100"nvme_curve_map["CHA_FAN7"]="1=30 45=62 60=72 70=90 80=100" declare -A cpu_curve_mapcpu_curve_map["CHA_FAN1"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN2"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN3"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN4"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN5"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN6"]="1=30 75=50 80=70 90=90 95=100"cpu_curve_map["CHA_FAN7"]="1=30 75=62 80=70 90=90 95=100"cpu_curve_map["CHA_FAN8"]="1=42 75=50 80=80 90=90 95=100" declare -A tr1_curve_map # RAMtr1_curve_map["CHA_FAN6"]="1=30 45=49 55=60 60=80 65=100"tr1_curve_map["CHA_FAN7"]="1=30 45=62 55=72 60=90 65=100" declare -A tr2_curve_map # HBAtr2_curve_map["CHA_FAN4"]="1=30 50=50 60=75 65=90 70=100" declare -A tr3_curve_map # GPUtr3_curve_map["CHA_FAN4"]="1=30 50=50 60=65 70=85 80=100"tr3_curve_map["CHA_FAN5"]="1=30 50=50 60=65 70=85 80=100"tr3_curve_map["CHA_FAN6"]="1=30 50=50 60=65 70=85 80=100"tr3_curve_map["CHA_FAN7"]="1=30 50=62 60=65 70=85 80=100" # ====================================# 🧠 Zustands-Tracking & Helfer# ====================================declare -A last_pwm cached_hdd_temp cached_hdd_time nvme_temp_current nvme_time_currentdeclare -A hdd_peak_pwm hdd_peak_time nvme_peak_pwm nvme_peak_time ipmi_tempsdeclare -A tr_peak_pwm tr_peak_time warned_missing_curve log_echo() { echo "$1" >> "$LOGFILE" [[ $(wc -l < "$LOGFILE" 2>/dev/null) -gt 150 ]] && tail -n 100 "$LOGFILE" > "${LOGFILE}.tmp" && mv "${LOGFILE}.tmp" "$LOGFILE"} step_curve_pwm() { local temp=$1 curve_string=$2 last_p=0 [[ -z "$curve_string" ]] && echo 0 && return for pair in $curve_string; do local t="${pair%=*}" pwm="${pair#*=}" (( temp < t )) && echo "$last_p" && return last_p=$pwm done echo "$last_p"} get_first_pwm_from_curve() { for pair in $1; do echo "${pair#*=}"; return; done echo 10} # ====================# 🔁 Hauptschleife# ====================while true; do now=$(date +%s) # --- Array Status Check --- array_state=$(grep "mdState=" /var/local/emhttp/var.ini | cut -d'"' -f2) # 1. CPU Temp cpu_temp=$(sensors 2>/dev/null | awk '/Package id 0:/ {print int($4)}' | head -n1) # 2. IPMI-Sensoren Snapshot while read -r line; do name=$(echo "$line" | cut -d'|' -f2 | xargs) val=$(echo "$line" | cut -d'|' -f5 | xargs) case "$name" in "TR1 Temperature") ipmi_temps["TR1"]=$val ;; "TR2 Temperature") ipmi_temps["TR2"]=$val ;; "TR3 Temperature") ipmi_temps["TR3"]=$val ;; esac done < <(ipmi-sensors --quiet-cache --output-sensor-state --sensor-types Temperature) # 3. HDD & NVMe Scan via disks.ini (Robustes State-Machine Parsing) while IFS='|' read -r dev temp spun status; do if [[ -n "$dev" ]]; then if [[ "$dev" == sd* ]]; then if [[ "$spun" == "0" && "$temp" =~ ^[0-9]+$ ]]; then cached_hdd_temp[$dev]=$temp cached_hdd_time[$dev]=$now fi elif [[ "$dev" == nvme* ]]; then if [[ "$temp" =~ ^[0-9]+$ ]]; then nvme_temp_current[$dev]=$temp nvme_time_current[$dev]=$now fi fi fi done < <(awk -F'=' ' function flush() { if (dev != "") { print dev "|" temp "|" spun "|" stat } } { gsub(/["\[\],]/, "", $0) if ($1 == "device") { flush() dev = $2 temp=""; spun=""; stat="" } else if ($1 == "temp") { temp = $2 } else if ($1 == "spundown") { spun = $2 } else if ($1 == "status") { stat = $2 } } END { flush() } ' /var/local/emhttp/disks.ini) # 5. Fan-Loop for i in {1..8}; do fan="CHA_FAN$i"; sources="${fan_sources[$fan]}" [[ -z "$sources" ]] && continue final_speed=0 final_driver="" data_found=0 hdd_display="(not used)"; nvme_display="(not used)"; tr_display="" zone_hex=$(printf '0x%02x' $((i - 1))) for src in $sources; do case $src in HDD) curve_str="${hdd_curve_map[$fan]}" [[ -z "$curve_str" ]] && continue cur_h_p=0; max_h=-1; max_h_dev="" for d in ${fan_disks[$fan]}; do if [[ -n "${cached_hdd_time[$d]}" && $(( now - cached_hdd_time[$d] )) -lt $AFTER_ACTIVITY_COOLDOWN_HDD ]]; then data_found=1 t=${cached_hdd_temp[$d]} if (( t > max_h )); then max_h=$t max_h_dev=$d fi p_v=$(step_curve_pwm "$t" "$curve_str") (( p_v > cur_h_p )) && cur_h_p=$p_v fi done # Peak-Hold Logik HDD if (( cur_h_p > ${hdd_peak_pwm[$fan]:-0} )) || (( now - ${hdd_peak_time[$fan]:-0} > AFTER_PEAK_TEMP_COOLDOWN_HDD )); then hdd_peak_pwm[$fan]=$cur_h_p; hdd_peak_time[$fan]=$now fi pwm_h=${hdd_peak_pwm[$fan]} if (( max_h >= 0 )); then hdd_display="${max_h}°C (Peak:${pwm_h}%)" if (( pwm_h > final_speed )); then final_speed=$pwm_h final_driver="$max_h_dev=${max_h}°C(Peak)" fi else if [[ -n "${fan_disks[$fan]}" ]]; then hdd_display="(sleeping/cooldown)" fi fi ;; CPU) curve_str="${cpu_curve_map[$fan]}" [[ -z "$curve_str" ]] && continue p=$(step_curve_pwm "$cpu_temp" "$curve_str") if (( p > final_speed )); then final_speed=$p final_driver="CPU=${cpu_temp}°C" fi ;; NVME) curve_str="${nvme_curve_map[$fan]}" [[ -z "$curve_str" ]] && continue cur_p=0; cur_t=-1; cur_t_dev="" if [[ -n "${fan_nvmes[$fan]}" ]]; then for d in ${fan_nvmes[$fan]}; do if [[ -n "${nvme_time_current[$d]}" && $(( now - nvme_time_current[$d] )) -lt 60 ]]; then data_found=1 t=${nvme_temp_current[$d]} if (( t > cur_t )); then cur_t=$t cur_t_dev=$d fi p_v=$(step_curve_pwm "$t" "$curve_str") (( p_v > cur_p )) && cur_p=$p_v fi done # Peak-Hold Logik NVMe if (( cur_p > ${nvme_peak_pwm[$fan]:-0} )) || (( now - ${nvme_peak_time[$fan]:-0} > AFTER_PEAK_TEMP_COOLDOWN_NVME )); then nvme_peak_pwm[$fan]=$cur_p; nvme_peak_time[$fan]=$now fi pwm_n=${nvme_peak_pwm[$fan]} if [[ -n "$pwm_n" && "$pwm_n" -gt 0 ]]; then nvme_display="${cur_t}°C (Peak:${pwm_n}%)" if (( pwm_n > final_speed )); then final_speed=$pwm_n final_driver="$cur_t_dev=${cur_t}°C(Peak)" fi else nvme_display="(sleeping)" fi fi ;; TR1|TR2|TR3) t_val=${ipmi_temps[$src]} if [[ "$t_val" =~ ^[0-9] ]]; then t_int=${t_val%.*} case $src in TR1) curve_str="${tr1_curve_map[$fan]}" ;; TR2) curve_str="${tr2_curve_map[$fan]}" ;; TR3) curve_str="${tr3_curve_map[$fan]}" ;; esac [[ -z "$curve_str" ]] && continue p_now=$(step_curve_pwm "$t_int" "$curve_str") idx="${fan}_${src}" cooldown_ref="AFTER_PEAK_TEMP_COOLDOWN_$src" if (( p_now > ${tr_peak_pwm[$idx]:-0} )) || (( now - ${tr_peak_time[$idx]:-0} > ${!cooldown_ref} )); then tr_peak_pwm[$idx]=$p_now; tr_peak_time[$idx]=$now fi p_final=${tr_peak_pwm[$idx]} if (( p_final > final_speed )); then final_speed=$p_final final_driver="$src=${t_int}°C" fi tr_display+="$src:${t_int}°C(P:${p_final}%) " fi ;; esac done # --- Array Status Logik --- if [[ "$array_state" != "STARTED" ]]; then if [[ "$sources" =~ "HDD" || "$sources" =~ "NVME" ]]; then if (( data_found == 0 )); then s_pwm=${secure_pwm[$fan]} if [[ -n "$s_pwm" ]]; then final_speed=$s_pwm final_driver="SecureMode (Array Stopped)" fi fi fi fi # Falls nach allen Quellen final_speed immer noch 0 if (( final_speed == 0 )); then main_src=$(echo "$sources" | awk '{print $1}') case $main_src in HDD) base_curve="${hdd_curve_map[$fan]}" ;; CPU) base_curve="${cpu_curve_map[$fan]}" ;; NVME) base_curve="${nvme_curve_map[$fan]}" ;; *) base_curve="${cpu_curve_map[$fan]:-${hdd_curve_map[$fan]}}" ;; esac first_p=$(get_first_pwm_from_curve "$base_curve") final_speed=${first_p:-10} final_driver="Idle/Base" fi # Emergency Check emergency=0; emergency_reasons=() for d in "${!cached_hdd_temp[@]}"; do if [[ -n "${cached_hdd_time[$d]}" && $(( now - cached_hdd_time[$d] )) -lt $AFTER_ACTIVITY_COOLDOWN_HDD && ${cached_hdd_temp[$d]} -ge $MAX_SAFE_HDD_TEMP ]]; then emergency=1; emergency_reasons+=("HDD:$d") fi done for d in "${!nvme_temp_current[@]}"; do if [[ -n "${nvme_time_current[$d]}" && $(( now - nvme_time_current[$d] )) -lt 60 && ${nvme_temp_current[$d]} -ge $MAX_SAFE_NVME_TEMP ]]; then emergency=1; emergency_reasons+=("NVME:$d") fi done if (( cpu_temp >= MAX_SAFE_CPU_TEMP )); then emergency=1; emergency_reasons+=("CPU"); fi if (( emergency )); then final_speed=100 reasons_str=$(printf "%s\n" "${emergency_reasons[@]}" | sort -u | paste -sd ',' - | sed 's/,/, /g') log_indicator="EMERGENCY [$reasons_str]" else log_indicator="Target=${final_speed}%" fi # PWM Safety Cap (0-100) (( final_speed > 100 )) && final_speed=100 (( final_speed < 0 )) && final_speed=0 # Setzen & Loggen if [[ "$final_speed" -ne "${last_pwm[$fan]:--1}" || "$DRY_RUN" -eq 1 ]]; then last_pwm[$fan]="$final_speed" timestamp=$(date '+%F | %T') if (( emergency )); then log_msg="🚀 Set $fan | $log_indicator → Target=${final_speed}% | $timestamp" else log_msg="🚀 Set $fan | HDD=$hdd_display | NVMe=$nvme_display | CPU=${cpu_temp}°C" [[ -n "$tr_display" ]] && log_msg+=" | TR=$tr_display" log_msg+=" → $final_driver → $log_indicator | $timestamp" fi log_echo "$log_msg" if (( ! DRY_RUN )); then hex_pwm=$(printf '0x%02x' "$final_speed") if ! ipmi-raw 0x00 0x30 0x0e 0x04 "$zone_hex" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" > /dev/null 2>&1; then log_echo "⚠️ IPMI Error! Set Failsafe: $FAILSAFE_FAN_SPEED%" hex_fs=$(printf '0x%02x' "$FAILSAFE_FAN_SPEED") ipmi-raw 0x00 0x30 0x0e 0x04 "$zone_hex" 0x01 "$hex_fs" 0x01 "$hex_fs" 0x01 "$hex_fs" 0x01 "$hex_fs" 0x01 "$hex_fs" > /dev/null 2>&1 fi fi fi done sleep "$INTERVAL"doneFan_Control_Secure_Mode (at stopping of array):Fan_Control_Secure_Mode#!/bin/bash# Fan_Control_Secure_Mode# Only for ASUS Pro WS W680-ACE IPMI PCIe-CARD# Purpose: One-Shot execution at "At stopping of array" # ====================================# ⚙️ Sicherheits-Lüfterwerte# ====================================declare -A secure_pwmsecure_pwm["CHA_FAN1"]=59 # unten links HDDssecure_pwm["CHA_FAN2"]=59 # unten mitte HDDssecure_pwm["CHA_FAN3"]=59 # unten rechts HDDssecure_pwm["CHA_FAN4"]=60 # oben links MB & links hinten HBAsecure_pwm["CHA_FAN5"]=30 # oben mittesecure_pwm["CHA_FAN6"]=30 # oben rechts Richtung CPUsecure_pwm["CHA_FAN7"]=62 # hinten Gehäuseentlüftungsecure_pwm["CHA_FAN8"]=42 # CPU Kühler LOG_DIR="/tmp/user.scripts/tmpScripts/Fan_Control_Secure_Mode"LOGFILE="$LOG_DIR/log.txt" # Verzeichnis erstellen, falls nicht vorhandenmkdir -p "$LOG_DIR" log_echo() { timestamp=$(date '+%F | %T') echo "🛡️ [$timestamp] Secure Mode: $1" >> "$LOGFILE"} log_echo "Array stop detected. Setting fans to secure PWM values..."logger "Fan_Control_Secure_Mode: Array stop detected. Setting fans to secure PWM values..." # Schleife über alle 8 Lüfterfor i in {1..8}; do fan="CHA_FAN$i" pwm="${secure_pwm[$fan]}" if [[ -n "$pwm" ]]; then zone_hex=$(printf '0x%02x' $((i - 1))) hex_pwm=$(printf '0x%02x' "$pwm") # IPMI Befehl absetzen if ipmi-raw 0x00 0x30 0x0e 0x04 "$zone_hex" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" 0x01 "$hex_pwm" > /dev/null 2>&1; then log_echo "$fan set to ${pwm}%" else log_echo "❌ ERROR setting $fan" fi fidone log_echo "Fan Secure Mode values applied."logger "Fan_Control_Secure_Mode: Fan Secure Mode values applied."
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.