🍪

Swift: macのCPU温度を取得する(Apple Silicon編)

4 min read

M1チップのMacでCPUの温度を取得する方法。(妥当っぽさは検証してみているが、本当にこれで正しい値なのかは謎)

まず、Objective-Cを使っていくのでBridging-Header.hを用意

Bridging-Header.h
#include <Foundation/Foundation.h>
extern NSDictionary* thermalDictionary();

次にセンサー名と値のディクショナリを取得するためのObjective-Cのコードを用意

sensors.m
#include <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/hidsystem/IOHIDEventSystemClient.h>

#define kIOHIDEventTypeTemperature  15

typedef struct __IOHIDEvent *IOHIDEventRef;
typedef struct __IOHIDServiceClient *IOHIDServiceClientRef;

IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef allocator);
int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client, CFDictionaryRef match);
int IOHIDEventSystemClientSetMatchingMultiple(IOHIDEventSystemClientRef client, CFArrayRef match);
CFStringRef IOHIDServiceClientCopyProperty(IOHIDServiceClientRef service, CFStringRef property);
IOHIDEventRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t , int32_t, int64_t);
double IOHIDEventGetFloatValue(IOHIDEventRef event, int32_t field);

CFDictionaryRef matching(int page, int usage) {
    const void *keys[2] = {
        CFStringCreateWithCString(0, kIOHIDPrimaryUsagePageKey, 0),
        CFStringCreateWithCString(0, kIOHIDPrimaryUsageKey, 0)
    };
    const void *nums[2] = {
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &page),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage)
    };
    return CFDictionaryCreate(0, keys, nums, 2,
                              &kCFTypeDictionaryKeyCallBacks,
                              &kCFTypeDictionaryValueCallBacks);
}

NSDictionary* getThermals(CFDictionaryRef sensors) {
    IOHIDEventSystemClientRef system = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
    IOHIDEventSystemClientSetMatching(system, sensors);
    CFArrayRef matchingsrvs = IOHIDEventSystemClientCopyServices(system);

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    long count = CFArrayGetCount(matchingsrvs);

    for (int i = 0; i < count; i++) {
        IOHIDServiceClientRef sc = (IOHIDServiceClientRef)CFArrayGetValueAtIndex(matchingsrvs, i);

        CFStringRef tempKey = IOHIDServiceClientCopyProperty(sc, CFSTR("Product"));
        NSString *key = (tempKey == NULL) ? @"unknown" : (__bridge NSString *)tempKey;

        IOHIDEventRef event = IOHIDServiceClientCopyEvent(sc, kIOHIDEventTypeTemperature, 0, 0);
        double tempValue = (event == 0) ? 0 : IOHIDEventGetFloatValue(event, (kIOHIDEventTypeTemperature << 16));
        NSNumber *value = [NSNumber numberWithDouble:tempValue];

        [dict setObject:value forKey:key];
    }
    return dict;
}

NSDictionary* thermalDictionary(void) {
    CFDictionaryRef sensors = matching(0xff00, 5);
    return getThermals(sensors);
}

最後にSwiftの方でCPUの平均値を取得してみる。

class CPUTemperatureARM {
    
    let sensors = [
        "eACC MTR Temp Sensor0",
        "eACC MTR Temp Sensor3",
        "pACC MTR Temp Sensor2",
        "pACC MTR Temp Sensor3",
        "pACC MTR Temp Sensor4",
        "pACC MTR Temp Sensor5",
        "pACC MTR Temp Sensor7",
        "pACC MTR Temp Sensor8",
        "pACC MTR Temp Sensor9"
    ]
    
    func get() -> Double {
        guard let dict = thermalDictionary() as? [String: Double] else { return 0 }
        let temperatures = dict
            .sorted { $0.key < $1.key }
            .map { (key: $0.key, value: $0.value) }
            .filter { sensors.contains($0.key) }
        // temperatures.forEach {
        //     print($0.key, String(format: "%0.1f", $0.value))
        // }
        print()
        let average = temperatures.reduce(0) { $0 + $1.value } / Double(temperatures.count)
        return average
    }
    
}
let value = CPUTemperatureARM().get()
print(String(format: "CPU Temperature: %4.1f°C", value))

参考

Discussion

ログインするとコメントできます