fix(fan): mode 或目标转速读取失败时沿用起步窗口

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ysicingandClaude authored and 缘生 committed 2026-10-04 21:52:14 +08:00
1 parent 6782efa805
commit 8919dabb6c
2 files changed
+27 -7

No files matched your search

@@ -50,13 +50,17 @@ struct FanStartupTracker {
/// 曾读到状态键的风扇;之后读不到视为偶发失败,沿用原窗口而不是改用手动目标推断。
private var reportsStatus: Set<Int> = []
mutating func update(id: Int, current: Double, target: Double, isManual: Bool,
/// target、isManual 为 nil 表示该键读取失败。
mutating func update(id: Int, current: Double, target: Double?, isManual: Bool?,
status: Double?, now: TimeInterval) -> Bool {
if status != nil { reportsStatus.insert(id) }
if current == 0, status == nil, reportsStatus.contains(id) {
// 缺少判断依据时视为偶发失败,沿用原窗口,避免恢复读数后重新开始提示。
let unknown = status == nil && (reportsStatus.contains(id) || target == nil || isManual == nil)
if current == 0, unknown {
return startedAt[id].map { now - $0 < 10 } ?? false
}
let starting = status.map { $0 == 1 } ?? (isManual && target.isFinite && target > 0)
let manualTarget = isManual == true && target.map { $0.isFinite && $0 > 0 } == true
let starting = status.map { $0 == 1 } ?? manualTarget
guard current == 0, starting else {
startedAt[id] = nil
return false
@@ -96,12 +100,14 @@ public final class FanControl {
guard let current = smc.double(SMCKey("F\(id)Ac")) else { return nil }
let minimum = smc.double(SMCKey("F\(id)Mn")) ?? 0
let maximum = smc.double(SMCKey("F\(id)Mx")) ?? 0
let target = smc.double(SMCKey("F\(id)Tg")) ?? current
let mode = smc.double(modeKey(id)) ?? 0
let targetReading = smc.double(SMCKey("F\(id)Tg"))
let target = targetReading ?? current
let modeReading = smc.double(modeKey(id))
let mode = modeReading ?? 0
// 仅在读数为零时多读一个状态键,运行中的风扇沿用原采样开销。
let status = current == 0 ? smc.double(SMCKey("F\(id)St")) : nil
let isStarting = startup.update(id: id, current: current, target: target,
isManual: mode == 1, status: status, now: now)
let isStarting = startup.update(id: id, current: current, target: targetReading,
isManual: modeReading.map { $0 == 1 }, status: status, now: now)
return FanState(id: id, current: current, minimum: minimum, maximum: maximum,
target: target, isManual: mode == 1, isStarting: isStarting)
}
@@ -72,6 +72,20 @@ struct FanStartupTests {
#expect(!failureWithManualTarget)
}
@Test func transientModeOrTargetFailureKeepsTheFallbackWindow() {
var tracker = FanStartupTracker()
let initiallyStarting = tracker.update(id: 0, current: 0, target: 2000, isManual: true, status: nil, now: 0)
#expect(initiallyStarting)
let modeFailure = tracker.update(id: 0, current: 0, target: 2000, isManual: nil, status: nil, now: 5)
#expect(modeFailure)
let targetFailure = tracker.update(id: 0, current: 0, target: nil, isManual: true, status: nil, now: 11)
#expect(!targetFailure)
let afterRecovery = tracker.update(id: 0, current: 0, target: 2000, isManual: true, status: nil, now: 12)
#expect(!afterRecovery)
let neverStarted = tracker.update(id: 1, current: 0, target: nil, isManual: nil, status: nil, now: 12)
#expect(!neverStarted)
}
@Test func failedFanCountDoesNotRestartAnExpiredWindow() {
var tracker = FanStartupTracker()
let initiallyStarting = tracker.update(id: 0, current: 0, target: 2000, isManual: true, status: nil, now: 0)