@@ -330,14 +330,16 @@ public struct FanCurve: Codable, Equatable, Sendable {
330330 public var points : [ FanCurvePoint ]
331331 public var hysteresisCelsius : Double
332332 public var slewRateRPMPerSecond : Double ?
333+ public var fallSlewRateRPMPerSecond : Double ?
333334
334335 public init (
335336 name: String ,
336337 sensors: [ String ] = [ ] ,
337338 sensorWeights: [ String : Double ] = [ : ] ,
338339 points: [ FanCurvePoint ] ,
339340 hysteresisCelsius: Double = 0 ,
340- slewRateRPMPerSecond: Double ? = nil
341+ slewRateRPMPerSecond: Double ? = nil ,
342+ fallSlewRateRPMPerSecond: Double ? = nil
341343 ) throws {
342344 let sortedPoints = points. sorted { $0. temperatureCelsius < $1. temperatureCelsius }
343345 guard sortedPoints. count >= 2 else {
@@ -352,6 +354,7 @@ public struct FanCurve: Codable, Equatable, Sendable {
352354 self . points = sortedPoints
353355 self . hysteresisCelsius = max ( 0 , hysteresisCelsius)
354356 self . slewRateRPMPerSecond = slewRateRPMPerSecond. map { max ( 0 , $0) }
357+ self . fallSlewRateRPMPerSecond = fallSlewRateRPMPerSecond. map { max ( 0 , $0) }
355358 }
356359
357360 public static func quiet( maxRPM: Double ) -> FanCurve {
@@ -368,7 +371,8 @@ public struct FanCurve: Codable, Equatable, Sendable {
368371 FanCurvePoint ( 105 , maxRPM)
369372 ] ,
370373 hysteresisCelsius: 3 ,
371- slewRateRPMPerSecond: 400
374+ slewRateRPMPerSecond: 400 ,
375+ fallSlewRateRPMPerSecond: 180
372376 )
373377 }
374378
@@ -416,18 +420,19 @@ public struct FanCurveEngine: Sendable {
416420 now: Date
417421 ) throws -> FanCurveEvaluation {
418422 let aggregated = try Self . aggregate ( samples: samples, sensors: curve. sensors, weights: curve. sensorWeights)
419- let effectiveTemperature : Double
420- if
421- let previous = lastEffectiveTemperature,
422- abs ( aggregated - previous) <= curve. hysteresisCelsius
423- {
424- effectiveTemperature = previous
425- } else {
426- effectiveTemperature = aggregated
427- }
423+ let effectiveTemperature = Self . hysteresisAdjustedTemperature (
424+ aggregated: aggregated,
425+ previous: lastEffectiveTemperature,
426+ hysteresis: curve. hysteresisCelsius
427+ )
428428
429429 let interpolated = Self . interpolate ( points: curve. points, temperature: effectiveTemperature)
430- let target = slewLimitedTarget ( rawTarget: interpolated, slewRate: curve. slewRateRPMPerSecond, now: now)
430+ let target = slewLimitedTarget (
431+ rawTarget: interpolated,
432+ riseSlewRate: curve. slewRateRPMPerSecond,
433+ fallSlewRate: curve. fallSlewRateRPMPerSecond,
434+ now: now
435+ )
431436
432437 lastEffectiveTemperature = effectiveTemperature
433438 lastRPM = target
@@ -440,6 +445,19 @@ public struct FanCurveEngine: Sendable {
440445 )
441446 }
442447
448+ static func hysteresisAdjustedTemperature( aggregated: Double , previous: Double ? , hysteresis: Double ) -> Double {
449+ guard let previous, hysteresis > 0 else {
450+ return aggregated
451+ }
452+ if aggregated >= previous {
453+ return aggregated
454+ }
455+ if aggregated >= previous - hysteresis {
456+ return previous
457+ }
458+ return aggregated
459+ }
460+
443461 public static func interpolate( points: [ FanCurvePoint ] , temperature: Double ) -> Double {
444462 let sorted = points. sorted { $0. temperatureCelsius < $1. temperatureCelsius }
445463 guard let first = sorted. first, let last = sorted. last else {
@@ -497,19 +515,26 @@ public struct FanCurveEngine: Sendable {
497515 return selected. map ( \. celsius) . max ( ) ?? 0
498516 }
499517
500- private func slewLimitedTarget( rawTarget: Double , slewRate : Double ? , now: Date ) -> Double {
518+ private func slewLimitedTarget( rawTarget: Double , riseSlewRate : Double ? , fallSlewRate : Double ? , now: Date ) -> Double {
501519 guard
502- let slewRate,
503- slewRate > 0 ,
504520 let lastRPM,
505521 let lastEvaluationDate
506522 else {
507523 return rawTarget
508524 }
509- let maxDelta = slewRate * max( 0 , now. timeIntervalSince ( lastEvaluationDate) )
525+ let elapsed = max ( 0 , now. timeIntervalSince ( lastEvaluationDate) )
510526 if rawTarget > lastRPM {
527+ guard let slewRate = riseSlewRate, slewRate > 0 else {
528+ return rawTarget
529+ }
530+ let maxDelta = slewRate * elapsed
511531 return min ( rawTarget, lastRPM + maxDelta)
512532 }
533+ let slewRate = fallSlewRate ?? riseSlewRate
534+ guard let slewRate, slewRate > 0 else {
535+ return rawTarget
536+ }
537+ let maxDelta = slewRate * elapsed
513538 return max ( rawTarget, lastRPM - maxDelta)
514539 }
515540}
0 commit comments