From 536c3cb39667ffdd75b20ecfd2021f361927495c Mon Sep 17 00:00:00 2001 From: Alfred Date: Mon, 25 May 2026 15:52:26 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=B8=A9=E5=BA=A6=E5=A4=B1=E6=95=88=E9=99=8D?= =?UTF-8?q?=E9=80=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 20 +++++++++++-- README.md | 4 +-- components/ts_drivers/src/ts_fan.c | 17 ++++++++--- components/ts_webui/web/css/style.css | 37 +++++++++++++++++------- components/ts_webui/web/js/app.js | 32 +++++++++++--------- components/ts_webui/web/js/lang/en-US.js | 14 +++++---- components/ts_webui/web/js/lang/zh-CN.js | 14 +++++---- version.txt | 2 +- 8 files changed, 95 insertions(+), 45 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 574cc09..9b3ab7b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -138,11 +138,25 @@ jobs: pattern: tianshanos-firmware-* path: firmware + - name: Resolve release tag + id: release_tag + run: | + if [ "${{ github.event_name }}" = "release" ]; then + TAG="${{ github.event.release.tag_name }}" + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + TAG="${GITHUB_REF_NAME}" + else + VERSION="${{ needs.build.outputs.version }}" + TAG="v${VERSION%%+*}" + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "Release tag: $TAG" + - name: Check if release already exists (main branch) if: github.ref == 'refs/heads/main' id: check run: | - TAG="v${{ needs.build.outputs.version }}" + TAG="${{ steps.release_tag.outputs.tag }}" if gh release view "$TAG" 2>/dev/null; then echo "skip=true" >> $GITHUB_OUTPUT echo "⏭️ Release $TAG already exists, skipping" @@ -154,8 +168,8 @@ jobs: if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && steps.check.outputs.skip != 'true') uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.event_name == 'release' && github.event.release.tag_name || startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', needs.build.outputs.version) }} - name: ${{ github.event_name == 'release' && github.event.release.tag_name || startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', needs.build.outputs.version) }} + tag_name: ${{ steps.release_tag.outputs.tag }} + name: ${{ steps.release_tag.outputs.tag }} files: | firmware/**/*.bin firmware/**/flasher_args.json diff --git a/README.md b/README.md index 7d57ded..14a4c6b 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,8 @@ esptool.py --chip esp32s3 -p /dev/ttyACM0 write_flash \ ## 当前状态 -**版本**: 0.4.6 -**阶段**: Phase 39 完成 - LPMU 接入上层网络 +**版本**: 0.4.7 +**阶段**: Phase 40 完成 - 自动风扇温度失效降速优化 ### 已完成功能 diff --git a/components/ts_drivers/src/ts_fan.c b/components/ts_drivers/src/ts_fan.c index e127e98..8c3cf39 100644 --- a/components/ts_drivers/src/ts_fan.c +++ b/components/ts_drivers/src/ts_fan.c @@ -39,6 +39,7 @@ #define FAN_AUTO_GAIN_MAX 2.00f #define FAN_AUTO_RISE_RATE_PER_SEC 12.0f #define FAN_AUTO_FALL_RATE_PER_SEC 0.30f +#define FAN_AUTO_STALE_DUTY 25 #define FAN_AUTO_HISTORY_SIZE 32 /*===========================================================================*/ @@ -405,16 +406,19 @@ static esp_err_t apply_adaptive_auto(fan_instance_t *fan, const ts_temp_data_t * int16_t control_temp = TS_TEMP_DEFAULT_VALUE; int16_t guard_temp = TS_TEMP_DEFAULT_VALUE; uint8_t old_duty = fan->current_duty; + bool recovered_from_stale = fan->temp_stale; if (!get_adaptive_temperatures(temp_data, &control_temp, &guard_temp)) { fan->temp_stale = true; fan->auto_state = TS_FAN_AUTO_STATE_STALE; fan->response_observing = false; fan->auto_fall_credit = 0.0f; - fan->target_duty = 100; + fan->guard_active = false; + fan->guard_release_since_ms = 0; + fan->target_duty = FAN_AUTO_STALE_DUTY; fan->predicted_temperature = fan->control_temperature; fan->last_auto_update_ms = now_ms; - return update_pwm(fan, 100); + return update_pwm(fan, FAN_AUTO_STALE_DUTY); } fan->temp_stale = false; @@ -494,11 +498,16 @@ static esp_err_t apply_adaptive_auto(fan_instance_t *fan, const ts_temp_data_t * bool allow_down = (guard_temp <= FAN_AUTO_GUARD_RELEASE_TEMP) && (!slope_valid || fan->slope_c_per_min <= 0.0f); - if (target < fan->current_duty && !allow_down) { + if (target < fan->current_duty && !allow_down && !recovered_from_stale) { target = fan->current_duty; } - target = apply_auto_rate_limit(fan, target, allow_down, now_ms); + if (recovered_from_stale && target < fan->current_duty) { + fan->auto_fall_credit = 0.0f; + fan->last_auto_update_ms = now_ms; + } else { + target = apply_auto_rate_limit(fan, target, allow_down, now_ms); + } fan->target_duty = target; esp_err_t ret = update_pwm(fan, target); diff --git a/components/ts_webui/web/css/style.css b/components/ts_webui/web/css/style.css index 1f2150f..863c703 100644 --- a/components/ts_webui/web/css/style.css +++ b/components/ts_webui/web/css/style.css @@ -3001,13 +3001,32 @@ button.btn-gray:hover, .fan-header { display: flex; justify-content: space-between; - align-items: center; + align-items: flex-start; + gap: 12px; margin-bottom: 16px; } +.fan-header-main { + display: flex; + align-items: center; + flex: 1; + flex-wrap: wrap; + gap: 8px 10px; + min-width: 0; +} + +.fan-header-actions { + display: flex; + align-items: center; + flex: 0 0 auto; + gap: 8px; +} + .fan-title { font-size: 1.1rem; font-weight: 600; + line-height: 1.5; + white-space: nowrap; } .fan-status-badge { @@ -3038,26 +3057,23 @@ button.btn-gray:hover, } .fan-auto-info-btn { - width: 24px; - height: 24px; + width: 22px; + height: 22px; padding: 0; - margin-left: 6px; - border: 1px solid var(--border-color); + border: none; border-radius: 50%; - background: var(--bg-elevated); + background: transparent; color: var(--text-muted); display: inline-flex; align-items: center; justify-content: center; - vertical-align: 16px; cursor: pointer; transition: all var(--t-fast); } .fan-auto-info-btn:hover { color: var(--text-primary); - background: var(--bg-card); - border-color: var(--text-muted); + background: transparent; } .fan-auto-info-btn i { @@ -3094,9 +3110,10 @@ button.btn-gray:hover, flex-wrap: wrap; gap: 6px; align-items: center; - margin: -4px 0 14px; + margin: 0; font-size: 0.75rem; color: var(--text-light); + min-width: 0; } .fan-auto-meta span { diff --git a/components/ts_webui/web/js/app.js b/components/ts_webui/web/js/app.js index 82e4560..5bf7863 100644 --- a/components/ts_webui/web/js/app.js +++ b/components/ts_webui/web/js/app.js @@ -1484,40 +1484,46 @@ function updateFanInfo(data) { const _autoHelpTitleSafe = typeof escapeHtml === 'function' ? escapeHtml(_autoHelpTitle) : _autoHelpTitle; const autoStateLabels = { idle: typeof t === 'function' ? t('fanPage.autoStateIdle') : '待机', - baseline: typeof t === 'function' ? t('fanPage.autoStateBaseline') : '基线', - active: typeof t === 'function' ? t('fanPage.autoStateActive') : '自适应', - guard: typeof t === 'function' ? t('fanPage.autoStateGuard') : '保护', - stale: typeof t === 'function' ? t('fanPage.autoStateStale') : '失效', + baseline: typeof t === 'function' ? t('fanPage.autoStateBaseline') : '按曲线运行', + active: typeof t === 'function' ? t('fanPage.autoStateActive') : '自适应调速', + guard: typeof t === 'function' ? t('fanPage.autoStateGuard') : '保护介入', + stale: typeof t === 'function' ? t('fanPage.autoStateStale') : '温度失效', unknown: typeof t === 'function' ? t('fanPage.autoStateUnknown') : '未知' }; const autoState = fan.auto_state || 'unknown'; const autoStateText = autoStateLabels[autoState] || autoState; - const guardLabel = typeof t === 'function' ? t('fanPage.guardTempShort') : '保护'; - const predictedLabel = typeof t === 'function' ? t('fanPage.predictedTempShort') : '预测'; + const guardLabel = typeof t === 'function' ? t('fanPage.guardTempShort') : '安全参考温度'; + const predictedLabel = typeof t === 'function' ? t('fanPage.predictedTempShort') : '45秒预测'; + const slopeLabel = typeof t === 'function' ? t('fanPage.slopeTempShort') : '升温速度'; + const slopeUnit = typeof t === 'function' ? t('fanPage.slopeTempUnit') : '°C/分钟'; const autoMeta = hasAutoTelemetry ? `
${autoStateText} ${typeof fan.guard_temperature === 'number' ? `${guardLabel} ${fan.guard_temperature.toFixed(1)}°C` : ''} ${typeof fan.predicted_temperature === 'number' ? `${predictedLabel} ${fan.predicted_temperature.toFixed(1)}°C` : ''} - ${typeof fan.slope_c_per_min === 'number' ? `${fan.slope_c_per_min.toFixed(2)}°C/min` : ''} + ${typeof fan.slope_c_per_min === 'number' ? `${slopeLabel} ${fan.slope_c_per_min.toFixed(2)}${slopeUnit}` : ''}
` : ''; return `
- ${_fanTitle} - - - +
+ ${_fanTitle} + ${autoMeta} +
+
+ ${mode === 'auto' ? `` : ''} + + + +
${displayDuty} % - ${mode === 'auto' ? `` : ''} ${rpm > 0 ? `
${rpm} RPM
` : ''}
- ${autoMeta}
diff --git a/components/ts_webui/web/js/lang/en-US.js b/components/ts_webui/web/js/lang/en-US.js index dd6a32e..908c3e0 100644 --- a/components/ts_webui/web/js/lang/en-US.js +++ b/components/ts_webui/web/js/lang/en-US.js @@ -2735,13 +2735,15 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('en-US', { editTempCurve: 'Edit Temperature Curve', manualModeHint: 'Switch to manual mode to adjust', autoStateIdle: 'Idle', - autoStateBaseline: 'Baseline', - autoStateActive: 'Adaptive', - autoStateGuard: 'Guard', - autoStateStale: 'Stale', + autoStateBaseline: 'Following curve', + autoStateActive: 'Adaptive speed', + autoStateGuard: 'Protection active', + autoStateStale: 'Temperature stale', autoStateUnknown: 'Unknown', - guardTempShort: 'Guard', - predictedTempShort: 'Predicted', + guardTempShort: 'Safety reference', + predictedTempShort: '45s forecast', + slopeTempShort: 'Rise rate', + slopeTempUnit: '°C/min', autoHelpTitle: 'What makes Auto mode different?', autoHelpIntro: 'Auto mode adjusts fan speed based on the current cooling condition of the device, instead of only following a fixed curve.', autoHelpCurveDiff: 'Curve mode works like a fixed rule table: when temperature reaches a point, the fan uses the matching speed. It is stable and predictable when you want to define the cooling behavior yourself.', diff --git a/components/ts_webui/web/js/lang/zh-CN.js b/components/ts_webui/web/js/lang/zh-CN.js index 4256320..ac87c54 100644 --- a/components/ts_webui/web/js/lang/zh-CN.js +++ b/components/ts_webui/web/js/lang/zh-CN.js @@ -2720,13 +2720,15 @@ if (typeof i18n !== 'undefined') i18n.registerLanguage('zh-CN', { editTempCurve: '编辑温度曲线', manualModeHint: '切换到手动模式后可调节', autoStateIdle: '待机', - autoStateBaseline: '基线', - autoStateActive: '自适应', - autoStateGuard: '保护', - autoStateStale: '失效', + autoStateBaseline: '按曲线运行', + autoStateActive: '自适应调速', + autoStateGuard: '保护介入', + autoStateStale: '温度失效', autoStateUnknown: '未知', - guardTempShort: '保护', - predictedTempShort: '预测', + guardTempShort: '安全参考温度', + predictedTempShort: '45秒预测', + slopeTempShort: '升温速度', + slopeTempUnit: '°C/分钟', autoHelpTitle: '自动模式有什么不同?', autoHelpIntro: '自动模式会根据设备当前的散热状态实时调节风扇,而不是只按一条固定曲线运行。', autoHelpCurveDiff: '曲线模式更像一张固定规则表:温度到多少,风扇就转到多少。它稳定、可预期,适合你想手动定义散热策略的场景。', diff --git a/version.txt b/version.txt index ef52a64..f905682 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.4.6 +0.4.7