Files
macos-stats/Modules/Sensors/readers.swift
2021-12-22 17:52:41 +01:00

304 lines
9.4 KiB
Swift

//
// readers.swift
// Sensors
//
// Created by Serhiy Mytrovtsiy on 17/06/2020.
// Using Swift 5.0.
// Running on macOS 10.15.
//
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Kit
import IOKit.hid
internal class SensorsReader: Reader<[Sensor_p]> {
internal var list: [Sensor_p] = []
}
internal class x86_SensorsReader: SensorsReader {
init() {
super.init()
var available: [String] = SMC.shared.getAllKeys()
var list: [Sensor] = []
if let count = SMC.shared.getValue("FNum") {
debug("Found \(Int(count)) fans", log: self.log)
for i in 0..<Int(count) {
self.list.append(Fan(
id: i,
key: "F\(i)Ac",
name: SMC.shared.getStringValue("F\(i)ID") ?? "\(localizedString("Fan")) #\(i)",
minSpeed: SMC.shared.getValue("F\(i)Mn") ?? 1,
maxSpeed: SMC.shared.getValue("F\(i)Mx") ?? 1,
value: SMC.shared.getValue("F\(i)Ac") ?? 0,
mode: self.getFanMode(i)
))
}
}
available = available.filter({ (key: String) -> Bool in
switch key.prefix(1) {
case "T", "V", "P": return true
default: return false
}
})
SensorsList.forEach { (s: Sensor) in
if let idx = available.firstIndex(where: { $0 == s.key }) {
list.append(s)
available.remove(at: idx)
}
}
SensorsList.filter{ $0.key.contains("%") }.forEach { (s: Sensor) in
var index = 1
for i in 0..<10 {
let key = s.key.replacingOccurrences(of: "%", with: "\(i)")
if available.firstIndex(where: { $0 == key }) != nil {
var sensor = s.copy()
sensor.key = key
sensor.name = s.name.replacingOccurrences(of: "%", with: "\(index)")
list.append(sensor)
index += 1
}
}
}
for sensor in list {
if let newValue = SMC.shared.getValue(sensor.key) {
if let idx = list.firstIndex(where: { $0.key == sensor.key }) {
list[idx].value = newValue
}
}
}
self.list += list.filter({ (s: Sensor) -> Bool in
if s.type == .temperature && s.value > 110 {
return false
}
return true
})
}
public override func read() {
for i in 0..<self.list.count {
self.list[i].value = SMC.shared.getValue(self.list[i].key) ?? 0
}
self.callback(self.list)
}
private func getFanMode(_ id: Int) -> FanMode {
let fansMode: Int = Int(SMC.shared.getValue("FS! ") ?? 0)
var mode: FanMode = .automatic
if fansMode == 0 {
mode = .automatic
} else if fansMode == 3 {
mode = .forced
} else if fansMode == 1 && id == 0 {
mode = .forced
} else if fansMode == 2 && id == 1 {
mode = .forced
}
return mode
}
}
internal class AppleSilicon_SensorsReader: SensorsReader {
private let types: [SensorType] = [.temperature, .current, .voltage]
init() {
super.init()
for type in types {
self.fetch(type: type)
}
self.calculateAverageAndHottest()
self.sort()
}
public override func read() {
for type in types {
self.fetch(type: type)
}
self.calculateAverageAndHottest()
self.sort()
self.callback(self.list)
}
private func fetch(type: SensorType) {
var page: Int32 = 0
var usage: Int32 = 0
var eventType: Int32 = kIOHIDEventTypeTemperature
// usagePage:
// kHIDPage_AppleVendor = 0xff00,
// kHIDPage_AppleVendorTemperatureSensor = 0xff05,
// kHIDPage_AppleVendorPowerSensor = 0xff08,
// kHIDPage_GenericDesktop
//
// usage:
// kHIDUsage_AppleVendor_TemperatureSensor = 0x0005,
// kHIDUsage_AppleVendorPowerSensor_Current = 0x0002,
// kHIDUsage_AppleVendorPowerSensor_Voltage = 0x0003,
// kHIDUsage_GD_Keyboard
//
switch type {
case .temperature:
page = 0xff00
usage = 0x0005
eventType = kIOHIDEventTypeTemperature
case .current:
page = 0xff08
usage = 0x0002
eventType = kIOHIDEventTypePower
case .voltage:
page = 0xff08
usage = 0x0003
eventType = kIOHIDEventTypePower
case .power: break
case .fan: break
}
if let list = AppleSiliconSensors(page, usage, eventType) {
list.forEach { (key, value) in
if let name = key as? String, let value = value as? Double {
self.upsert(key: name, value: value, type: type)
}
}
}
return
}
private func createDeviceMatchingDictionary(usagePage: Int, usage: Int) -> CFMutableDictionary {
let dict = [
kIOHIDPrimaryUsageKey: usage,
kIOHIDPrimaryUsagePageKey: usagePage
] as NSDictionary
return dict.mutableCopy() as! NSMutableDictionary
}
private func upsert(key: String, value: Double, type: SensorType, group: SensorGroup = .system, prepend: Bool = false) {
if let idx = self.list.firstIndex(where: { $0.key == key }) {
self.list[idx].value = value
} else {
var name: String = key
var g: SensorGroup = group
AppleSiliconSensorsList.forEach { (s: Sensor) in
if s.key.contains("%") {
var index = 1
for i in 0..<64 {
if s.key.replacingOccurrences(of: "%", with: "\(i)") == key {
name = s.name.replacingOccurrences(of: "%", with: "\(index)")
}
index += 1
}
} else if s.key == key {
name = s.name
}
g = s.group
}
let s = Sensor(
key: key,
name: name,
value: value,
group: g,
type: type
)
if prepend {
self.list.insert(s, at: 0)
} else {
self.list.append(s)
}
}
}
private func calculateAverageAndHottest() {
let cpuSensors = self.list.filter({ $0.key.hasPrefix("pACC MTR Temp") || $0.key.hasPrefix("eACC MTR Temp") }).map{ $0.value }
let gpuSensors = self.list.filter({ $0.key.hasPrefix("GPU MTR Temp") }).map{ $0.value }
let socSensors = self.list.filter({ $0.key.hasPrefix("SOC MTR Temp") }).map{ $0.value }
if !socSensors.isEmpty {
self.upsert(
key: "Average SOC",
value: socSensors.reduce(0, +) / Double(socSensors.count),
type: .temperature,
group: .system,
prepend: true
)
if let max = socSensors.max() {
self.upsert(
key: "Hottest SOC",
value: max,
type: .temperature,
group: .system,
prepend: true
)
}
}
if !gpuSensors.isEmpty {
self.upsert(
key: "Average GPU",
value: gpuSensors.reduce(0, +) / Double(gpuSensors.count),
type: .temperature,
group: .GPU,
prepend: true
)
if let max = gpuSensors.max() {
self.upsert(
key: "Hottest GPU",
value: max,
type: .temperature,
group: .system,
prepend: true
)
}
}
if !cpuSensors.isEmpty {
self.upsert(
key: "Average CPU",
value: cpuSensors.reduce(0, +) / Double(cpuSensors.count),
type: .temperature,
group: .CPU,
prepend: true
)
if let max = cpuSensors.max() {
self.upsert(
key: "Hottest CPU",
value: max,
type: .temperature,
group: .system,
prepend: true
)
}
}
}
private func sort() {
self.list = self.list.filter({ (s: Sensor_p) -> Bool in
switch s.type {
case .temperature:
return s.value < 110 && s.value >= 0
case .voltage:
return s.value < 300 && s.value >= 0
case .current:
return s.value < 100 && s.value >= 0
default: return true
}
}).sorted { $0.key.lowercased() < $1.key.lowercased() }
}
}