From 8c4a5478bbc37faf49166e4d1a44272394cecabc Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 31 Aug 2026 16:32:19 +0100 Subject: [PATCH] Add fan speed override for Mover and Parity Check Adds a per-fan "Override on Mover/Parity Check" option so a fan can be forced to a fixed speed whenever the array is busy, instead of following the normal disk/CPU curve. Useful for drive bays that need full airflow during a parity check or mover run but can otherwise idle quietly. The loop script detects mover via pgrep and parity/sync/rebuild via mdcmd status (mdResync), takes priority over the disk/CPU calculation, and reports back as "(Mover)" or "(Parity)" in the dashboard/log so it's clear why the fan ramped up. Off by default, so existing configs are unaffected until a fan opts in. Tested locally against a mocked mdcmd/PWM setup and on a live server during an active parity check. --- fanctrlplus.page | 38 ++++++++++++++++++++++++++++++++-- include/FanBlockRender.php | 30 +++++++++++++++++++++++++++ include/FanctrlLogic.php | 2 ++ include/update.fanctrlplus.php | 9 ++++++++ scripts/fanctrlplus_loop.sh | 26 +++++++++++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) diff --git a/fanctrlplus.page b/fanctrlplus.page index 76d65f9..a61f41f 100644 --- a/fanctrlplus.page +++ b/fanctrlplus.page @@ -59,6 +59,8 @@ cpu_enable="0" cpu_sensor="" cpu_min_temp="" cpu_max_temp="" +array_override_enable="0" +array_override_pwm="255" INI ); } @@ -454,6 +456,7 @@ $(function() { { selector: 'input[id^="pwm_percent_input_"]', unit: '%', min: 0, max: 100 }, { selector: 'input[id^="max_percent_input_"]', unit: '%', min: 0, max: 100 }, { selector: `#idle_percent_input_${index}`, unit: '%', min: 0, max: 100 }, + { selector: `#array_override_percent_input_${index}`, unit: '%', min: 0, max: 100 }, { selector: 'input[id^="low_temp_input_"]', unit: '°C', min: 1, max: 100 }, { selector: 'input[id^="high_temp_input_"]', unit: '°C', min: 1, max: 100 }, { selector: 'input[id^="cpu_low_temp_input_"]', unit: '°C', min: 1, max: 95 }, @@ -466,6 +469,11 @@ $(function() { window.handleCpuEnableChange(this, index); }); + $html.find('select[id^="array-override-enable-"]').each(function () { + const index = this.id.replace('array-override-enable-', ''); + window.handleArrayOverrideChange(this, index); + }); + bindFanSpeedEvents(); bindIdleEvents(); }, 100); @@ -605,6 +613,7 @@ $(function() { { selector: 'input[id^="pwm_percent_input_"]', unit: '%', min: 0, max: 100 }, { selector: 'input[id^="max_percent_input_"]', unit: '%', min: 0, max: 100 }, { selector: 'input[id^="idle_percent_input_"]', unit: '%', min: 0, max: 100 }, + { selector: 'input[id^="array_override_percent_input_"]', unit: '%', min: 0, max: 100 }, { selector: 'input[id^="low_temp_input_"]', unit: '°C', min: 1, max: 100 }, { selector: 'input[id^="high_temp_input_"]', unit: '°C', min: 1, max: 100 }, { selector: 'input[id^="cpu_low_temp_input_"]', unit: '°C', min: 1, max: 95 }, @@ -871,7 +880,9 @@ $(function() { cpu_enable: $block.find('select[name^="cpu_enable["]').val(), cpu_sensor: $block.find('select[name^="cpu_sensor["]').val(), cpu_min_temp: stripUnit($block.find('input[name^="cpu_min_temp["]').val()), - cpu_max_temp: stripUnit($block.find('input[name^="cpu_max_temp["]').val()) + cpu_max_temp: stripUnit($block.find('input[name^="cpu_max_temp["]').val()), + array_override_enable: $block.find('select[name^="array_override_enable["]').val(), + array_override_percent: stripUnit($block.find('input[name^="array_override_percent["]').val()) }; $block.data('original', original); } @@ -896,7 +907,9 @@ $(function() { cpu_enable: $block.find('select[name^="cpu_enable["]').val(), cpu_sensor: $block.find('select[name^="cpu_sensor["]').val(), cpu_min_temp: stripUnit($block.find('input[name^="cpu_min_temp["]').val()), - cpu_max_temp: stripUnit($block.find('input[name^="cpu_max_temp["]').val()) + cpu_max_temp: stripUnit($block.find('input[name^="cpu_max_temp["]').val()), + array_override_enable: $block.find('select[name^="array_override_enable["]').val(), + array_override_percent: stripUnit($block.find('input[name^="array_override_percent["]').val()) }; if (JSON.stringify(original) !== JSON.stringify(current)) { @@ -1414,6 +1427,18 @@ $(function() { } }; + window.handleArrayOverrideChange = function(selectEl, index) { + const enabled = selectEl.value === "1"; + const input = document.querySelector(`#array_override_percent_input_${index}`); + if (input) { + input.disabled = !enabled; + input.classList.toggle('disabled', !enabled); + } + document.querySelectorAll(`.array-override-control-${index} .array-override-label`).forEach(label => { + label.classList.toggle('disabled', !enabled); + }); + }; + // 初始化所有 block 的状态,并绑定 onchange window.addEventListener('load', function () { requestAnimationFrame(() => { @@ -1425,6 +1450,15 @@ $(function() { window.handleCpuEnableChange(this, index); }); }); + + document.querySelectorAll('select[id^="array-override-enable-"]').forEach(select => { + const index = select.id.replace('array-override-enable-', ''); + window.handleArrayOverrideChange(select, index); + + select.addEventListener('change', function () { + window.handleArrayOverrideChange(this, index); + }); + }); }); }); setTimeout(() => { diff --git a/include/FanBlockRender.php b/include/FanBlockRender.php index 5a6eaf0..bf780e3 100644 --- a/include/FanBlockRender.php +++ b/include/FanBlockRender.php @@ -24,6 +24,10 @@ function render_fan_block($cfg, $i, $pwms, $disks, $pwm_labels, $cpu_sensors) { $idle_abs = isset($cfg['idle']) && is_numeric($cfg['idle']) ? (int)$cfg['idle'] : 0; $idle_pct = round($idle_abs * 100 / 255) . '%'; + $array_override_enable = ($cfg['array_override_enable'] ?? '0') == '1'; + $array_override_pwm_raw = isset($cfg['array_override_pwm']) && is_numeric($cfg['array_override_pwm']) ? $cfg['array_override_pwm'] : 255; + $array_override_pct = round($array_override_pwm_raw * 100 / 255) . '%'; + ob_start(); ?>
@@ -193,6 +197,32 @@ class="interval-input fcp-interval-input" + + + Override on Mover/Parity Check: + + + + + + + Override Speed: + + > + + + Disk Temperature Settings diff --git a/include/FanctrlLogic.php b/include/FanctrlLogic.php index ab1e92d..362b660 100644 --- a/include/FanctrlLogic.php +++ b/include/FanctrlLogic.php @@ -152,6 +152,8 @@ function scan_dir($dir) { cpu_sensor="" cpu_min_temp="" cpu_max_temp="" + array_override_enable="0" + array_override_pwm="255" INI ); diff --git a/include/update.fanctrlplus.php b/include/update.fanctrlplus.php index 7e46595..18793ae 100644 --- a/include/update.fanctrlplus.php +++ b/include/update.fanctrlplus.php @@ -86,6 +86,13 @@ $cpu_max_temp = ''; } + // Mover/Parity Check Override + $array_override_enable = $_POST['array_override_enable'][$i] ?? '0'; + $array_override_percent_raw = $_POST['array_override_percent'][$i] ?? ''; + $array_override_percent = is_numeric($ao = preg_replace('/[^0-9]/', '', $array_override_percent_raw)) ? intval($ao) : 100; + $array_override_percent = max(0, min(100, $array_override_percent)); + $array_override_pwm = (int) round($array_override_percent * 255 / 100); + // Custom Name 不能为空 if ($custom === '') { ob_clean(); @@ -195,6 +202,8 @@ 'cpu_sensor' => $cpu_sensor, 'cpu_min_temp' => $cpu_min_temp, 'cpu_max_temp' => $cpu_max_temp, + 'array_override_enable' => $array_override_enable, + 'array_override_pwm' => $array_override_pwm, ]; $content = ''; diff --git a/scripts/fanctrlplus_loop.sh b/scripts/fanctrlplus_loop.sh index 20d2f21..072f0c9 100644 --- a/scripts/fanctrlplus_loop.sh +++ b/scripts/fanctrlplus_loop.sh @@ -31,6 +31,22 @@ plugin="fanctrlplus" custom="${custom:-$(basename "$cfg_file" .cfg)}" controller_enable="${controller}_enable" +# ===== Mover / Parity Check Override ===== +array_override_enable="${array_override_enable:-0}" +array_override_pwm="${array_override_pwm:-$max}" + +array_op_label() { + if pgrep -x mover >/dev/null 2>&1; then + echo "Mover" + return + fi + local resync + resync=$(/usr/local/sbin/mdcmd status 2>/dev/null | grep -Po '^mdResync=\K[0-9]+') + if [[ -n "$resync" && "$resync" -gt 0 ]]; then + echo "Parity" + fi +} + # 推导 RPM 读取路径 if [[ "$controller" =~ pwm([0-9]+)$ ]]; then fan_index="${BASH_REMATCH[1]}" @@ -138,6 +154,16 @@ while true; do temp_origin="(Idle)" fi + # Mover/Parity Check 覆盖:优先级最高,覆盖所有温控与 idle 结果 + if [[ "$array_override_enable" == "1" ]]; then + array_op="$(array_op_label)" + if [[ -n "$array_op" ]]; then + pwm_val="$array_override_pwm" + max_temp="*" + temp_origin="($array_op)" + fi + fi + # 每轮都写入 Dashboard 缓存 echo "${max_temp} ${temp_origin}" > "/var/tmp/fanctrlplus/temp_${plugin}_${custom}"