117 lines
3.7 KiB
Swift
117 lines
3.7 KiB
Swift
import Foundation
|
|
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: "自动"
|
|
case .cooling: "降温"
|
|
case .maximum: "强冷"
|
|
case .custom: "自定义"
|
|
}
|
|
}
|
|
|
|
/// “降温”档位于最低与最高转速之间的 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("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 "未检测到可调节的风扇" }
|
|
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
|
|
}
|
|
}
|