Files
MacMonitor/Sources/Telemetry/Fan/MMFanTelemetryProvider.m
gronod f9093b1d99
MacMonitor CI/CD Pipeline / Build & Test (Intel x86_64) (push) Canceled after 0s
feat(fan): implement MMFanTelemetryProvider for multi-fan RPM and utilization (Issue #4)
2026-09-08 12:22:17 +01:00

148 lines
4.4 KiB
Objective-C

#import "MMFanTelemetryProvider.h"
@interface MMFanTelemetryProvider () {
MMAppleSMCClient *_smcClient;
NSInteger _probedFanCount;
}
@end
@implementation MMFanTelemetryProvider
- (instancetype)init {
return [self initWithSMCClient:[MMAppleSMCClient sharedClient]];
}
- (instancetype)initWithSMCClient:(MMAppleSMCClient *)client {
self = [super init];
if (self) {
_smcClient = client;
_probedFanCount = -1;
[self probeFans];
}
return self;
}
- (void)probeFans {
if (!_smcClient.isAvailable) return;
NSNumber *num = [_smcClient readNumericValueForKey:@"FNum" error:nil];
if (num) {
_probedFanCount = [num integerValue];
} else {
// Fallback probe F0Ac
NSNumber *f0 = [_smcClient readNumericValueForKey:@"F0Ac" error:nil];
if (f0 && [f0 doubleValue] >= 0.0) {
_probedFanCount = 1;
NSNumber *f1 = [_smcClient readNumericValueForKey:@"F1Ac" error:nil];
if (f1 && [f1 doubleValue] >= 0.0) {
_probedFanCount = 2;
}
} else {
_probedFanCount = 0;
}
}
}
- (MMTelemetryDomain)domain {
return MMTelemetryDomainFan;
}
- (NSString *)providerIdentifier {
return @"com.i3omb.macmonitor.telemetry.fan";
}
- (BOOL)isAvailable {
return _smcClient.isAvailable;
}
- (MMAppleSMCClient *)smcClient {
return _smcClient;
}
- (NSInteger)fanCount {
return (_probedFanCount >= 0) ? _probedFanCount : 0;
}
+ (NSDictionary<NSString *, id> *)calculateFanMetricsFromFanList:(NSArray<NSDictionary<NSString *, NSNumber *> *> *)fanList {
if (!fanList || fanList.count == 0) {
return @{
@"fanCount": @(0),
@"fans": @[],
@"averageRPM": @(0.0),
@"peakRPM": @(0.0)
};
}
NSMutableArray<NSDictionary<NSString *, id> *> *resultList = [NSMutableArray arrayWithCapacity:fanList.count];
double sumRPM = 0.0;
double peakRPM = 0.0;
for (NSUInteger i = 0; i < fanList.count; i++) {
NSDictionary<NSString *, NSNumber *> *raw = fanList[i];
double current = [raw[@"currentRPM"] doubleValue];
double min = [raw[@"minRPM"] doubleValue];
double max = [raw[@"maxRPM"] doubleValue];
double target = [raw[@"targetRPM"] doubleValue];
double util = 0.0;
if (max > min && current >= min) {
util = ((current - min) / (max - min)) * 100.0;
if (util < 0.0) util = 0.0;
if (util > 100.0) util = 100.0;
}
if (current > peakRPM) peakRPM = current;
sumRPM += current;
[resultList addObject:@{
@"fanIndex": @(i),
@"currentRPM": @(current),
@"minRPM": @(min),
@"maxRPM": @(max),
@"targetRPM": @(target),
@"utilizationPercent": @(util)
}];
}
double avgRPM = fanList.count > 0 ? (sumRPM / fanList.count) : 0.0;
return @{
@"fanCount": @(fanList.count),
@"fans": [resultList copy],
@"averageRPM": @(avgRPM),
@"peakRPM": @(peakRPM)
};
}
- (nullable NSDictionary<NSString *, id> *)sampleTelemetryWithError:(NSError **)error {
if (_probedFanCount < 0 && _smcClient.isAvailable) {
[self probeFans];
}
NSInteger count = [self fanCount];
NSMutableArray<NSDictionary<NSString *, NSNumber *> *> *rawList = [NSMutableArray arrayWithCapacity:count];
for (NSInteger i = 0; i < count; i++) {
NSString *acKey = [NSString stringWithFormat:@"F%ldAc", (long)i];
NSString *mnKey = [NSString stringWithFormat:@"F%ldMn", (long)i];
NSString *mxKey = [NSString stringWithFormat:@"F%ldMx", (long)i];
NSString *tgKey = [NSString stringWithFormat:@"F%ldTg", (long)i];
NSNumber *ac = [_smcClient readNumericValueForKey:acKey error:nil] ?: @(0.0);
NSNumber *mn = [_smcClient readNumericValueForKey:mnKey error:nil] ?: @(0.0);
NSNumber *mx = [_smcClient readNumericValueForKey:mxKey error:nil] ?: @(6000.0);
NSNumber *tg = [_smcClient readNumericValueForKey:tgKey error:nil] ?: ac;
[rawList addObject:@{
@"currentRPM": ac,
@"minRPM": mn,
@"maxRPM": mx,
@"targetRPM": tg
}];
}
return [MMFanTelemetryProvider calculateFanMetricsFromFanList:rawList];
}
@end