Files

118 lines
3.7 KiB
Swift

import Foundation
import Localization
import Metrics
import Observation
import SMC
@MainActor
@Observable
public final class FanController {
public enum Mode: String, CaseIterable, Identifiable {
case automatic, cooling, maximum, custom
public var id: String { rawValue }
var title: String {
switch self {
case .automatic: tr("自动")
case .cooling: tr("降温")
case .maximum: tr("强冷")
case .custom: tr("自定义")
}
}
/// “降温”档位于最低与最高转速之间的 60%
static let coolingLevel = 0.6
}
public private(set) var mode: Mode = .automatic
/// 自定义档位:0 为最低转速,1 为最高转速
public var customLevel: Double = 0.5
public private(set) var isApplying = false
public private(set) var notice: String?
public private(set) var noticeIsError = false
@ObservationIgnored private let helper: HelperClient
@ObservationIgnored private let store: MetricsStore
@ObservationIgnored private let settings: AppSettings
init(helper: HelperClient, store: MetricsStore, settings: AppSettings) {
self.helper = helper
self.store = store
self.settings = settings
}
func select(_ newMode: Mode) async {
guard newMode != mode || newMode == .custom else { return }
let previous = mode
mode = newMode
if let error = await apply() {
mode = previous
show(error, isError: true)
} else {
clearNotice()
}
}
func commitCustomLevel() async {
guard mode == .custom else { return }
if let error = await apply() { show(error, isError: true) }
}
/// 连接恢复或唤醒后重新下发
func reapply() async {
guard mode != .automatic else { return }
if let error = await apply() { show(error, isError: true) }
}
/// 每次收到新指标时检查温度,自定义档位过低且 CPU 过热时交还系统控制
func evaluateSafety() {
guard mode == .custom, customLevel < 1,
let hottest = store.sensors?.temperature(.cpu)?.maximum,
hottest >= Double(settings.fanSafetyTemperature) else { return }
Task {
mode = .automatic
_ = await apply()
show(tr("CPU 温度达到 \(Format.temperature(hottest, fahrenheit: settings.useFahrenheit)),已恢复系统自动控制"), isError: false)
}
}
func targetRPM(for fan: FanState) -> Double {
switch mode {
case .automatic: fan.target
case .cooling: fan.minimum + (fan.maximum - fan.minimum) * Mode.coolingLevel
case .maximum: fan.maximum
case .custom: fan.minimum + (fan.maximum - fan.minimum) * customLevel
}
}
private func apply() async -> String? {
let fans = store.sensors?.fans ?? []
guard !fans.isEmpty else { return tr("未检测到可调节的风扇") }
isApplying = true
defer { isApplying = false }
if mode == .automatic {
return await helper.resetAllFans()
}
for fan in fans {
if let error = await helper.setFanTarget(fan: fan.id, rpm: targetRPM(for: fan)) {
_ = await helper.resetAllFans()
return error
}
}
return nil
}
private func show(_ message: String, isError: Bool) {
if isError { Log.fans.error("\(message, privacy: .public)") } else { Log.fans.info("\(message, privacy: .public)") }
notice = message
noticeIsError = isError
}
private func clearNotice() {
notice = nil
noticeIsError = false
}
}