🍁

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

2021/06/23に公開

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/IOHIDServiceClient.h>
#include <IOKit/hidsystem/IOHIDEventSystemClient.h>

// IOHIDPageAppleVendor 0xFF00
// IOHIDUsageAppleVendorTemperatureSensor 0x05
// IOHIDEventTypeTemperature 0x0F

IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef);
void IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef, CFDictionaryRef);
CFTypeRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef, int64_t, int64_t, int64_t);
CFDictionaryRef IOHIDServiceClientCopyProperties(IOHIDServiceClientRef, CFArrayRef);
double IOHIDEventGetFloatValue(CFTypeRef, int64_t);

NSDictionary *thermalDictionary(void) {
    int32_t page = 0xFF00;
    int32_t usage = 0x05;
    NSDictionary* filter = @{
        @(kIOHIDPrimaryUsagePageKey): @(page),
        @(kIOHIDPrimaryUsageKey): @(usage)
    };
    IOHIDEventSystemClientRef client = IOHIDEventSystemClientCreate(kCFAllocatorDefault);
    IOHIDEventSystemClientSetMatching(client, (__bridge CFDictionaryRef)filter);
    NSArray *services = CFBridgingRelease(IOHIDEventSystemClientCopyServices(client));
    NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
    for (id obj in services) {
        IOHIDServiceClientRef service = (__bridge IOHIDServiceClientRef)obj;
        CFTypeRef event = IOHIDServiceClientCopyEvent(service, 0x0F, 0, 0);
        NSString *name = CFBridgingRelease(IOHIDServiceClientCopyProperty(service, CFSTR("Product")));
        if (event != nil && event != nil) {
            double value = IOHIDEventGetFloatValue(event, 0x0F << 0x10);
            mutableDict[name] = @(value);
        }
        if (event != nil) {
            CFRelease(event);
        }
    }
    CFRelease(client);
    return mutableDict;
}

最後に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) }
        let M1Temp = temperatures.filter { sensors.contains($0.key) }
        if M1Temp.isEmpty {
            let M1ProTemp = dict.filter { $0.key.lowercased().hasSuffix("tcal") == false }
            if M1ProTemp.isEmpty {
                return 0
            }
            return M1ProTemp.reduce(0) { $0 + $1.value } / Double(M1ProTemp.count)
        } else {
            return M1Temp.reduce(0) { $0 + $1.value } / Double(M1Temp.count)
        }
    }
    
}
let value = CPUTemperatureARM().get()
print(String(format: "CPU Temperature: %4.1f°C", value))

参考

Discussion