796 lines
31 KiB
Swift
796 lines
31 KiB
Swift
|
//
|
||
|
// SMC.swift
|
||
|
// SMCKit
|
||
|
//
|
||
|
// The MIT License
|
||
|
//
|
||
|
// Copyright (C) 2014-2017 beltex <https://beltex.github.io>
|
||
|
//
|
||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
// of this software and associated documentation files (the "Software"), to deal
|
||
|
// in the Software without restriction, including without limitation the rights
|
||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
// furnished to do so, subject to the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be included in
|
||
|
// all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
// THE SOFTWARE.
|
||
|
|
||
|
import IOKit
|
||
|
import Foundation
|
||
|
import os
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Type Aliases
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
// http://stackoverflow.com/a/22383661
|
||
|
|
||
|
/// Floating point, unsigned, 14 bits exponent, 2 bits fraction
|
||
|
public typealias FPE2 = (UInt8, UInt8)
|
||
|
|
||
|
/// Floating point, signed, 7 bits exponent, 8 bits fraction
|
||
|
public typealias SP78 = (UInt8, UInt8)
|
||
|
|
||
|
public typealias SMCBytes = (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||
|
UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8,
|
||
|
UInt8, UInt8, UInt8, UInt8)
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Standard Library Extensions
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
extension UInt32 {
|
||
|
|
||
|
init(fromBytes bytes: (UInt8, UInt8, UInt8, UInt8)) {
|
||
|
// TODO: Broken up due to "Expression was too complex" error as of
|
||
|
// Swift 4.
|
||
|
|
||
|
let byte0 = UInt32(bytes.0) << 24
|
||
|
let byte1 = UInt32(bytes.1) << 16
|
||
|
let byte2 = UInt32(bytes.2) << 8
|
||
|
let byte3 = UInt32(bytes.3)
|
||
|
|
||
|
self = byte0 | byte1 | byte2 | byte3
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension Bool {
|
||
|
|
||
|
init(fromByte byte: UInt8) {
|
||
|
self = byte == 1 ? true : false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public extension Int {
|
||
|
|
||
|
init(fromFPE2 bytes: FPE2) {
|
||
|
self = (Int(bytes.0) << 6) + (Int(bytes.1) >> 2)
|
||
|
}
|
||
|
|
||
|
func toFPE2() -> FPE2 {
|
||
|
return (UInt8(self >> 6), UInt8((self << 2) ^ ((self >> 6) << 8)))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension Double {
|
||
|
|
||
|
init(fromSP78 bytes: SP78) {
|
||
|
// FIXME: Handle second byte
|
||
|
let sign = bytes.0 & 0x80 == 0 ? 1.0 : -1.0
|
||
|
self = sign * Double(bytes.0 & 0x7F) // AND to mask sign bit
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Thanks to Airspeed Velocity for the great idea!
|
||
|
// http://airspeedvelocity.net/2015/05/22/my-talk-at-swift-summit/
|
||
|
public extension FourCharCode {
|
||
|
|
||
|
init(fromString str: String) {
|
||
|
precondition(str.count == 4)
|
||
|
|
||
|
self = str.utf8.reduce(0) { sum, character in
|
||
|
return sum << 8 | UInt32(character)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
init(fromStaticString str: StaticString) {
|
||
|
precondition(str.utf8CodeUnitCount == 4)
|
||
|
|
||
|
self = str.withUTF8Buffer { buffer in
|
||
|
// TODO: Broken up due to "Expression was too complex" error as of
|
||
|
// Swift 4.
|
||
|
|
||
|
let byte0 = UInt32(buffer[0]) << 24
|
||
|
let byte1 = UInt32(buffer[1]) << 16
|
||
|
let byte2 = UInt32(buffer[2]) << 8
|
||
|
let byte3 = UInt32(buffer[3])
|
||
|
|
||
|
return byte0 | byte1 | byte2 | byte3
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func toString() -> String {
|
||
|
return String(describing: UnicodeScalar(self >> 24 & 0xff)!) +
|
||
|
String(describing: UnicodeScalar(self >> 16 & 0xff)!) +
|
||
|
String(describing: UnicodeScalar(self >> 8 & 0xff)!) +
|
||
|
String(describing: UnicodeScalar(self & 0xff)!)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Defined by AppleSMC.kext
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/// Defined by AppleSMC.kext
|
||
|
///
|
||
|
/// This is the predefined struct that must be passed to communicate with the
|
||
|
/// AppleSMC driver. While the driver is closed source, the definition of this
|
||
|
/// struct happened to appear in the Apple PowerManagement project at around
|
||
|
/// version 211, and soon after disappeared. It can be seen in the PrivateLib.c
|
||
|
/// file under pmconfigd. Given that it is C code, this is the closest
|
||
|
/// translation to Swift from a type perspective.
|
||
|
///
|
||
|
/// ### Issues
|
||
|
///
|
||
|
/// * Padding for struct alignment when passed over to C side
|
||
|
/// * Size of struct must be 80 bytes
|
||
|
/// * C array's are bridged as tuples
|
||
|
///
|
||
|
/// http://www.opensource.apple.com/source/PowerManagement/PowerManagement-211/
|
||
|
public struct SMCParamStruct {
|
||
|
|
||
|
/// I/O Kit function selector
|
||
|
public enum Selector: UInt8 {
|
||
|
case kSMCHandleYPCEvent = 2
|
||
|
case kSMCReadKey = 5
|
||
|
case kSMCWriteKey = 6
|
||
|
case kSMCGetKeyFromIndex = 8
|
||
|
case kSMCGetKeyInfo = 9
|
||
|
}
|
||
|
|
||
|
/// Return codes for SMCParamStruct.result property
|
||
|
public enum Result: UInt8 {
|
||
|
case kSMCSuccess = 0
|
||
|
case kSMCError = 1
|
||
|
case kSMCKeyNotFound = 132
|
||
|
}
|
||
|
|
||
|
public struct SMCVersion {
|
||
|
var major: CUnsignedChar = 0
|
||
|
var minor: CUnsignedChar = 0
|
||
|
var build: CUnsignedChar = 0
|
||
|
var reserved: CUnsignedChar = 0
|
||
|
var release: CUnsignedShort = 0
|
||
|
}
|
||
|
|
||
|
public struct SMCPLimitData {
|
||
|
var version: UInt16 = 0
|
||
|
var length: UInt16 = 0
|
||
|
var cpuPLimit: UInt32 = 0
|
||
|
var gpuPLimit: UInt32 = 0
|
||
|
var memPLimit: UInt32 = 0
|
||
|
}
|
||
|
|
||
|
public struct SMCKeyInfoData {
|
||
|
/// How many bytes written to SMCParamStruct.bytes
|
||
|
//var dataSize: IOByteCount = 0
|
||
|
var dataSize:UInt32 = 0
|
||
|
|
||
|
/// Type of data written to SMCParamStruct.bytes. This lets us know how
|
||
|
/// to interpret it (translate it to human readable)
|
||
|
var dataType: UInt32 = 0
|
||
|
|
||
|
var dataAttributes: UInt8 = 0
|
||
|
}
|
||
|
|
||
|
/// FourCharCode telling the SMC what we want
|
||
|
var key: UInt32 = 0
|
||
|
|
||
|
var vers = SMCVersion()
|
||
|
|
||
|
var pLimitData = SMCPLimitData()
|
||
|
|
||
|
var keyInfo = SMCKeyInfoData()
|
||
|
|
||
|
/// Padding for struct alignment when passed over to C side
|
||
|
var padding: UInt16 = 0
|
||
|
|
||
|
/// Result of an operation
|
||
|
var result: UInt8 = 0
|
||
|
|
||
|
var status: UInt8 = 0
|
||
|
|
||
|
/// Method selector
|
||
|
var data8: UInt8 = 0
|
||
|
|
||
|
var data32: UInt32 = 0
|
||
|
|
||
|
/// Data returned from the SMC
|
||
|
var bytes: SMCBytes = (UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0))
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: SMC Client
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/// SMC data type information
|
||
|
public struct DataTypes {
|
||
|
|
||
|
/// Fan information struct
|
||
|
public static let FDS =
|
||
|
DataType(type: FourCharCode(fromStaticString: "{fds"), size: 16)
|
||
|
public static let Flag =
|
||
|
DataType(type: FourCharCode(fromStaticString: "flag"), size: 1)
|
||
|
/// See type aliases
|
||
|
public static let FPE2 =
|
||
|
DataType(type: FourCharCode(fromStaticString: "fpe2"), size: 2)
|
||
|
/// See type aliases
|
||
|
public static let SP78 =
|
||
|
DataType(type: FourCharCode(fromStaticString: "sp78"), size: 2)
|
||
|
public static let UInt8 =
|
||
|
DataType(type: FourCharCode(fromStaticString: "ui8 "), size: 1)
|
||
|
public static let UInt32 =
|
||
|
DataType(type: FourCharCode(fromStaticString: "ui32"), size: 4)
|
||
|
}
|
||
|
|
||
|
public struct SMCKey {
|
||
|
let code: FourCharCode
|
||
|
let info: DataType
|
||
|
}
|
||
|
|
||
|
public struct DataType: Equatable {
|
||
|
let type: FourCharCode
|
||
|
let size: IOByteCount
|
||
|
}
|
||
|
|
||
|
public func ==(lhs: DataType, rhs: DataType) -> Bool {
|
||
|
return lhs.type == rhs.type && lhs.size == rhs.size
|
||
|
}
|
||
|
|
||
|
/// Apple System Management Controller (SMC) user-space client for Intel-based
|
||
|
/// Macs. Works by talking to the AppleSMC.kext (kernel extension), the closed
|
||
|
/// source driver for the SMC.
|
||
|
public struct SMCKit {
|
||
|
|
||
|
public enum SMCError: Error {
|
||
|
|
||
|
/// AppleSMC driver not found
|
||
|
case driverNotFound
|
||
|
|
||
|
/// Failed to open a connection to the AppleSMC driver
|
||
|
case failedToOpen
|
||
|
|
||
|
/// This SMC key is not valid on this machine
|
||
|
case keyNotFound(code: String)
|
||
|
|
||
|
/// Requires root privileges
|
||
|
case notPrivileged
|
||
|
|
||
|
/// Fan speed must be > 0 && <= fanMaxSpeed
|
||
|
case unsafeFanSpeed
|
||
|
|
||
|
/// https://developer.apple.com/library/mac/qa/qa1075/_index.html
|
||
|
///
|
||
|
/// - parameter kIOReturn: I/O Kit error code
|
||
|
/// - parameter SMCResult: SMC specific return code
|
||
|
case unknown(kIOReturn: kern_return_t, SMCResult: UInt8)
|
||
|
}
|
||
|
|
||
|
/// Connection to the SMC driver
|
||
|
fileprivate static var connection: io_connect_t = 0
|
||
|
|
||
|
/// Open connection to the SMC driver. This must be done first before any
|
||
|
/// other calls
|
||
|
public static func open() throws {
|
||
|
let service = IOServiceGetMatchingService(kIOMasterPortDefault,
|
||
|
IOServiceMatching("AppleSMC"))
|
||
|
|
||
|
if service == 0 { throw SMCError.driverNotFound }
|
||
|
|
||
|
let result = IOServiceOpen(service, mach_task_self_, 0,
|
||
|
&SMCKit.connection)
|
||
|
IOObjectRelease(service)
|
||
|
|
||
|
if result != kIOReturnSuccess { throw SMCError.failedToOpen }
|
||
|
}
|
||
|
|
||
|
/// Close connection to the SMC driver
|
||
|
@discardableResult
|
||
|
public static func close() -> Bool {
|
||
|
let result = IOServiceClose(SMCKit.connection)
|
||
|
return result == kIOReturnSuccess ? true : false
|
||
|
}
|
||
|
|
||
|
/// Get information about a key
|
||
|
public static func keyInformation(_ key: FourCharCode) throws -> DataType {
|
||
|
var inputStruct = SMCParamStruct()
|
||
|
|
||
|
inputStruct.key = key
|
||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCGetKeyInfo.rawValue
|
||
|
|
||
|
let outputStruct = try callDriver(&inputStruct)
|
||
|
|
||
|
return DataType(type: outputStruct.keyInfo.dataType,
|
||
|
size: IOByteCount(outputStruct.keyInfo.dataSize))
|
||
|
}
|
||
|
|
||
|
/// Get information about the key at index
|
||
|
public static func keyInformationAtIndex(_ index: Int) throws ->
|
||
|
FourCharCode {
|
||
|
var inputStruct = SMCParamStruct()
|
||
|
|
||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCGetKeyFromIndex.rawValue
|
||
|
inputStruct.data32 = UInt32(index)
|
||
|
|
||
|
let outputStruct = try callDriver(&inputStruct)
|
||
|
|
||
|
return outputStruct.key
|
||
|
}
|
||
|
|
||
|
public static func getKey(_ code: String, type: DataType) -> SMCKey {
|
||
|
let key = SMCKey(code: FourCharCode(fromString: code), info: type)
|
||
|
return key
|
||
|
}
|
||
|
|
||
|
/// Read data of a key
|
||
|
public static func readData(_ key: SMCKey) throws -> SMCBytes {
|
||
|
var inputStruct = SMCParamStruct()
|
||
|
|
||
|
inputStruct.key = key.code
|
||
|
inputStruct.keyInfo.dataSize = UInt32(IOByteCount(key.info.size))
|
||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCReadKey.rawValue
|
||
|
|
||
|
let outputStruct = try callDriver(&inputStruct)
|
||
|
|
||
|
return outputStruct.bytes
|
||
|
}
|
||
|
|
||
|
/// Write data for a key
|
||
|
public static func writeData(_ key: SMCKey, data: SMCBytes) throws {
|
||
|
var inputStruct = SMCParamStruct()
|
||
|
|
||
|
inputStruct.key = key.code
|
||
|
inputStruct.bytes = data
|
||
|
inputStruct.keyInfo.dataSize = UInt32(IOByteCount(key.info.size))
|
||
|
inputStruct.data8 = SMCParamStruct.Selector.kSMCWriteKey.rawValue
|
||
|
|
||
|
_ = try callDriver(&inputStruct)
|
||
|
}
|
||
|
|
||
|
/// Make an actual call to the SMC driver
|
||
|
public static func callDriver(_ inputStruct: inout SMCParamStruct,
|
||
|
selector: SMCParamStruct.Selector = .kSMCHandleYPCEvent)
|
||
|
throws -> SMCParamStruct {
|
||
|
os_log("SMCPARAMSTRUCT SIZE: %d", MemoryLayout<SMCParamStruct>.stride)
|
||
|
assert(MemoryLayout<SMCParamStruct>.stride == 80, "SMCParamStruct size is != 80")
|
||
|
|
||
|
var outputStruct = SMCParamStruct()
|
||
|
let inputStructSize = MemoryLayout<SMCParamStruct>.stride
|
||
|
var outputStructSize = MemoryLayout<SMCParamStruct>.stride
|
||
|
|
||
|
let result = IOConnectCallStructMethod(SMCKit.connection,
|
||
|
UInt32(selector.rawValue),
|
||
|
&inputStruct,
|
||
|
inputStructSize,
|
||
|
&outputStruct,
|
||
|
&outputStructSize)
|
||
|
|
||
|
switch (result, outputStruct.result) {
|
||
|
case (kIOReturnSuccess, SMCParamStruct.Result.kSMCSuccess.rawValue):
|
||
|
return outputStruct
|
||
|
case (kIOReturnSuccess, SMCParamStruct.Result.kSMCKeyNotFound.rawValue):
|
||
|
throw SMCError.keyNotFound(code: inputStruct.key.toString())
|
||
|
case (kIOReturnNotPrivileged, _):
|
||
|
throw SMCError.notPrivileged
|
||
|
default:
|
||
|
throw SMCError.unknown(kIOReturn: result,
|
||
|
SMCResult: outputStruct.result)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: General
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
extension SMCKit {
|
||
|
|
||
|
/// Get all valid SMC keys for this machine
|
||
|
public static func allKeys() throws -> [SMCKey] {
|
||
|
let count = try keyCount()
|
||
|
var keys = [SMCKey]()
|
||
|
|
||
|
for i in 0 ..< count {
|
||
|
let key = try keyInformationAtIndex(i)
|
||
|
let info = try keyInformation(key)
|
||
|
keys.append(SMCKey(code: key, info: info))
|
||
|
}
|
||
|
|
||
|
return keys
|
||
|
}
|
||
|
|
||
|
/// Get the number of valid SMC keys for this machine
|
||
|
public static func keyCount() throws -> Int {
|
||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "#KEY"),
|
||
|
info: DataTypes.UInt32)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Int(UInt32(fromBytes: (data.0, data.1, data.2, data.3)))
|
||
|
}
|
||
|
|
||
|
/// Is this key valid on this machine?
|
||
|
public static func isKeyFound(_ code: FourCharCode) throws -> Bool {
|
||
|
do {
|
||
|
_ = try keyInformation(code)
|
||
|
} catch SMCError.keyNotFound { return false }
|
||
|
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Temperature
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/// The list is NOT exhaustive. In addition, the names of the sensors may not be
|
||
|
/// mapped to the correct hardware component.
|
||
|
///
|
||
|
/// ### Sources
|
||
|
///
|
||
|
/// * powermetrics(1)
|
||
|
/// * https://www.apple.com/downloads/dashboard/status/istatpro.html
|
||
|
/// * https://github.com/hholtmann/smcFanControl
|
||
|
/// * https://github.com/jedda/OSX-Monitoring-Tools
|
||
|
/// * http://www.opensource.apple.com/source/net_snmp/
|
||
|
/// * http://www.parhelia.ch/blog/statics/k3_keys.html
|
||
|
public struct TemperatureSensors {
|
||
|
|
||
|
public static let AMBIENT_AIR_0 = TemperatureSensor(name: "AMBIENT_AIR_0",
|
||
|
code: FourCharCode(fromStaticString: "TA0P"))
|
||
|
public static let AMBIENT_AIR_1 = TemperatureSensor(name: "AMBIENT_AIR_1",
|
||
|
code: FourCharCode(fromStaticString: "TA1P"))
|
||
|
// Via powermetrics(1)
|
||
|
public static let CPU_0_DIE = TemperatureSensor(name: "CPU_0_DIE",
|
||
|
code: FourCharCode(fromStaticString: "TC0F"))
|
||
|
public static let CPU_0_DIODE = TemperatureSensor(name: "CPU_0_DIODE",
|
||
|
code: FourCharCode(fromStaticString: "TC0D"))
|
||
|
public static let CPU_0_HEATSINK = TemperatureSensor(name: "CPU_0_HEATSINK",
|
||
|
code: FourCharCode(fromStaticString: "TC0H"))
|
||
|
public static let CPU_0_PROXIMITY =
|
||
|
TemperatureSensor(name: "CPU_0_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TC0P"))
|
||
|
public static let ENCLOSURE_BASE_0 =
|
||
|
TemperatureSensor(name: "ENCLOSURE_BASE_0",
|
||
|
code: FourCharCode(fromStaticString: "TB0T"))
|
||
|
public static let ENCLOSURE_BASE_1 =
|
||
|
TemperatureSensor(name: "ENCLOSURE_BASE_1",
|
||
|
code: FourCharCode(fromStaticString: "TB1T"))
|
||
|
public static let ENCLOSURE_BASE_2 =
|
||
|
TemperatureSensor(name: "ENCLOSURE_BASE_2",
|
||
|
code: FourCharCode(fromStaticString: "TB2T"))
|
||
|
public static let ENCLOSURE_BASE_3 =
|
||
|
TemperatureSensor(name: "ENCLOSURE_BASE_3",
|
||
|
code: FourCharCode(fromStaticString: "TB3T"))
|
||
|
public static let GPU_0_DIODE = TemperatureSensor(name: "GPU_0_DIODE",
|
||
|
code: FourCharCode(fromStaticString: "TG0D"))
|
||
|
public static let GPU_0_HEATSINK = TemperatureSensor(name: "GPU_0_HEATSINK",
|
||
|
code: FourCharCode(fromStaticString: "TG0H"))
|
||
|
public static let GPU_0_PROXIMITY =
|
||
|
TemperatureSensor(name: "GPU_0_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TG0P"))
|
||
|
public static let HDD_PROXIMITY = TemperatureSensor(name: "HDD_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TH0P"))
|
||
|
public static let HEATSINK_0 = TemperatureSensor(name: "HEATSINK_0",
|
||
|
code: FourCharCode(fromStaticString: "Th0H"))
|
||
|
public static let HEATSINK_1 = TemperatureSensor(name: "HEATSINK_1",
|
||
|
code: FourCharCode(fromStaticString: "Th1H"))
|
||
|
public static let HEATSINK_2 = TemperatureSensor(name: "HEATSINK_2",
|
||
|
code: FourCharCode(fromStaticString: "Th2H"))
|
||
|
public static let LCD_PROXIMITY = TemperatureSensor(name: "LCD_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TL0P"))
|
||
|
public static let MEM_SLOT_0 = TemperatureSensor(name: "MEM_SLOT_0",
|
||
|
code: FourCharCode(fromStaticString: "TM0S"))
|
||
|
public static let MEM_SLOTS_PROXIMITY =
|
||
|
TemperatureSensor(name: "MEM_SLOTS_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TM0P"))
|
||
|
public static let MISC_PROXIMITY = TemperatureSensor(name: "MISC_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "Tm0P"))
|
||
|
public static let NORTHBRIDGE = TemperatureSensor(name: "NORTHBRIDGE",
|
||
|
code: FourCharCode(fromStaticString: "TN0H"))
|
||
|
public static let NORTHBRIDGE_DIODE =
|
||
|
TemperatureSensor(name: "NORTHBRIDGE_DIODE",
|
||
|
code: FourCharCode(fromStaticString: "TN0D"))
|
||
|
public static let NORTHBRIDGE_PROXIMITY =
|
||
|
TemperatureSensor(name: "NORTHBRIDGE_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TN0P"))
|
||
|
public static let ODD_PROXIMITY = TemperatureSensor(name: "ODD_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "TO0P"))
|
||
|
public static let PALM_REST = TemperatureSensor(name: "PALM_REST",
|
||
|
code: FourCharCode(fromStaticString: "Ts0P"))
|
||
|
public static let PWR_SUPPLY_PROXIMITY =
|
||
|
TemperatureSensor(name: "PWR_SUPPLY_PROXIMITY",
|
||
|
code: FourCharCode(fromStaticString: "Tp0P"))
|
||
|
public static let THUNDERBOLT_0 = TemperatureSensor(name: "THUNDERBOLT_0",
|
||
|
code: FourCharCode(fromStaticString: "TI0P"))
|
||
|
public static let THUNDERBOLT_1 = TemperatureSensor(name: "THUNDERBOLT_1",
|
||
|
code: FourCharCode(fromStaticString: "TI1P"))
|
||
|
|
||
|
public static let all = [AMBIENT_AIR_0.code: AMBIENT_AIR_0,
|
||
|
AMBIENT_AIR_1.code: AMBIENT_AIR_1,
|
||
|
CPU_0_DIE.code: CPU_0_DIE,
|
||
|
CPU_0_DIODE.code: CPU_0_DIODE,
|
||
|
CPU_0_HEATSINK.code: CPU_0_HEATSINK,
|
||
|
CPU_0_PROXIMITY.code: CPU_0_PROXIMITY,
|
||
|
ENCLOSURE_BASE_0.code: ENCLOSURE_BASE_0,
|
||
|
ENCLOSURE_BASE_1.code: ENCLOSURE_BASE_1,
|
||
|
ENCLOSURE_BASE_2.code: ENCLOSURE_BASE_2,
|
||
|
ENCLOSURE_BASE_3.code: ENCLOSURE_BASE_3,
|
||
|
GPU_0_DIODE.code: GPU_0_DIODE,
|
||
|
GPU_0_HEATSINK.code: GPU_0_HEATSINK,
|
||
|
GPU_0_PROXIMITY.code: GPU_0_PROXIMITY,
|
||
|
HDD_PROXIMITY.code: HDD_PROXIMITY,
|
||
|
HEATSINK_0.code: HEATSINK_0,
|
||
|
HEATSINK_1.code: HEATSINK_1,
|
||
|
HEATSINK_2.code: HEATSINK_2,
|
||
|
MEM_SLOT_0.code: MEM_SLOT_0,
|
||
|
MEM_SLOTS_PROXIMITY.code: MEM_SLOTS_PROXIMITY,
|
||
|
PALM_REST.code: PALM_REST,
|
||
|
LCD_PROXIMITY.code: LCD_PROXIMITY,
|
||
|
MISC_PROXIMITY.code: MISC_PROXIMITY,
|
||
|
NORTHBRIDGE.code: NORTHBRIDGE,
|
||
|
NORTHBRIDGE_DIODE.code: NORTHBRIDGE_DIODE,
|
||
|
NORTHBRIDGE_PROXIMITY.code: NORTHBRIDGE_PROXIMITY,
|
||
|
ODD_PROXIMITY.code: ODD_PROXIMITY,
|
||
|
PWR_SUPPLY_PROXIMITY.code: PWR_SUPPLY_PROXIMITY,
|
||
|
THUNDERBOLT_0.code: THUNDERBOLT_0,
|
||
|
THUNDERBOLT_1.code: THUNDERBOLT_1]
|
||
|
}
|
||
|
|
||
|
public struct TemperatureSensor {
|
||
|
public let name: String
|
||
|
public let code: FourCharCode
|
||
|
}
|
||
|
|
||
|
public enum TemperatureUnit {
|
||
|
case celius
|
||
|
case fahrenheit
|
||
|
case kelvin
|
||
|
|
||
|
public static func toFahrenheit(_ celius: Double) -> Double {
|
||
|
// https://en.wikipedia.org/wiki/Fahrenheit#Definition_and_conversions
|
||
|
return (celius * 1.8) + 32
|
||
|
}
|
||
|
|
||
|
public static func toKelvin(_ celius: Double) -> Double {
|
||
|
// https://en.wikipedia.org/wiki/Kelvin
|
||
|
return celius + 273.15
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension SMCKit {
|
||
|
|
||
|
public static func allKnownTemperatureSensors() throws ->
|
||
|
[TemperatureSensor] {
|
||
|
var sensors = [TemperatureSensor]()
|
||
|
|
||
|
for sensor in TemperatureSensors.all.values {
|
||
|
if try isKeyFound(sensor.code) { sensors.append(sensor) }
|
||
|
}
|
||
|
|
||
|
return sensors
|
||
|
}
|
||
|
|
||
|
public static func allUnknownTemperatureSensors() throws -> [TemperatureSensor] {
|
||
|
let keys = try allKeys()
|
||
|
|
||
|
return keys.filter { $0.code.toString().hasPrefix("T") &&
|
||
|
$0.info == DataTypes.SP78 &&
|
||
|
TemperatureSensors.all[$0.code] == nil }
|
||
|
.map { TemperatureSensor(name: "Unknown", code: $0.code) }
|
||
|
}
|
||
|
|
||
|
/// Get current temperature of a sensor
|
||
|
public static func temperature(_ sensorCode: FourCharCode,
|
||
|
unit: TemperatureUnit = .celius) throws -> Double {
|
||
|
let data = try readData(SMCKey(code: sensorCode, info: DataTypes.SP78))
|
||
|
|
||
|
let temperatureInCelius = Double(fromSP78: (data.0, data.1))
|
||
|
|
||
|
switch unit {
|
||
|
case .celius:
|
||
|
return temperatureInCelius
|
||
|
case .fahrenheit:
|
||
|
return TemperatureUnit.toFahrenheit(temperatureInCelius)
|
||
|
case .kelvin:
|
||
|
return TemperatureUnit.toKelvin(temperatureInCelius)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Fan
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
public struct Fan {
|
||
|
// TODO: Should we start the fan id from 1 instead of 0?
|
||
|
public let id: Int
|
||
|
public let name: String
|
||
|
public let minSpeed: Int
|
||
|
public let maxSpeed: Int
|
||
|
}
|
||
|
|
||
|
extension SMCKit {
|
||
|
|
||
|
public static func allFans() throws -> [Fan] {
|
||
|
let count = try fanCount()
|
||
|
var fans = [Fan]()
|
||
|
|
||
|
for i in 0 ..< count {
|
||
|
fans.append(try SMCKit.fan(i))
|
||
|
}
|
||
|
|
||
|
return fans
|
||
|
}
|
||
|
|
||
|
public static func fan(_ id: Int) throws -> Fan {
|
||
|
let name = try fanName(id)
|
||
|
let minSpeed = try fanMinSpeed(id)
|
||
|
let maxSpeed = try fanMaxSpeed(id)
|
||
|
return Fan(id: id, name: name, minSpeed: minSpeed, maxSpeed: maxSpeed)
|
||
|
}
|
||
|
|
||
|
/// Number of fans this machine has. All Intel based Macs, except for the
|
||
|
/// 2015 MacBook (8,1), have at least 1
|
||
|
public static func fanCount() throws -> Int {
|
||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "FNum"),
|
||
|
info: DataTypes.UInt8)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Int(data.0)
|
||
|
}
|
||
|
|
||
|
public static func fanName(_ id: Int) throws -> String {
|
||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)ID"),
|
||
|
info: DataTypes.FDS)
|
||
|
let data = try readData(key)
|
||
|
|
||
|
// The last 12 bytes of '{fds' data type, a custom struct defined by the
|
||
|
// AppleSMC.kext that is 16 bytes, contains the fan name
|
||
|
let c1 = String(UnicodeScalar(data.4))
|
||
|
let c2 = String(UnicodeScalar(data.5))
|
||
|
let c3 = String(UnicodeScalar(data.6))
|
||
|
let c4 = String(UnicodeScalar(data.7))
|
||
|
let c5 = String(UnicodeScalar(data.8))
|
||
|
let c6 = String(UnicodeScalar(data.9))
|
||
|
let c7 = String(UnicodeScalar(data.10))
|
||
|
let c8 = String(UnicodeScalar(data.11))
|
||
|
let c9 = String(UnicodeScalar(data.12))
|
||
|
let c10 = String(UnicodeScalar(data.13))
|
||
|
let c11 = String(UnicodeScalar(data.14))
|
||
|
let c12 = String(UnicodeScalar(data.15))
|
||
|
|
||
|
let name = c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + c11 + c12
|
||
|
|
||
|
let characterSet = CharacterSet.whitespaces
|
||
|
return name.trimmingCharacters(in: characterSet)
|
||
|
}
|
||
|
|
||
|
public static func fanCurrentSpeed(_ id: Int) throws -> Int {
|
||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Ac"),
|
||
|
info: DataTypes.FPE2)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Int(fromFPE2: (data.0, data.1))
|
||
|
}
|
||
|
|
||
|
public static func fanMinSpeed(_ id: Int) throws -> Int {
|
||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mn"),
|
||
|
info: DataTypes.FPE2)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Int(fromFPE2: (data.0, data.1))
|
||
|
}
|
||
|
|
||
|
public static func fanMaxSpeed(_ id: Int) throws -> Int {
|
||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mx"),
|
||
|
info: DataTypes.FPE2)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Int(fromFPE2: (data.0, data.1))
|
||
|
}
|
||
|
|
||
|
/// Requires root privileges. By minimum we mean that OS X can interject and
|
||
|
/// raise the fan speed if needed, however it will not go below this.
|
||
|
///
|
||
|
/// WARNING: You are playing with hardware here, BE CAREFUL.
|
||
|
///
|
||
|
/// - Throws: Of note, `SMCKit.SMCError`'s `UnsafeFanSpeed` and `NotPrivileged`
|
||
|
public static func fanSetMinSpeed(_ id: Int, speed: Int) throws {
|
||
|
let maxSpeed = try fanMaxSpeed(id)
|
||
|
if speed <= 0 || speed > maxSpeed { throw SMCError.unsafeFanSpeed }
|
||
|
|
||
|
let data = speed.toFPE2()
|
||
|
let bytes: SMCBytes = (data.0, data.1, UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0), UInt8(0),
|
||
|
UInt8(0), UInt8(0))
|
||
|
|
||
|
let key = SMCKey(code: FourCharCode(fromString: "F\(id)Mn"),
|
||
|
info: DataTypes.FPE2)
|
||
|
|
||
|
try writeData(key, data: bytes)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// MARK: Miscellaneous
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
public struct batteryInfo {
|
||
|
public let batteryCount: Int
|
||
|
public let isACPresent: Bool
|
||
|
public let isBatteryPowered: Bool
|
||
|
public let isBatteryOk: Bool
|
||
|
public let isCharging: Bool
|
||
|
}
|
||
|
|
||
|
extension SMCKit {
|
||
|
|
||
|
public static func isOpticalDiskDriveFull() throws -> Bool {
|
||
|
// TODO: Should we catch key not found? That just means the machine
|
||
|
// doesn't have an ODD. Returning false though is not fully correct.
|
||
|
// Maybe we could throw a no ODD error instead?
|
||
|
let key = SMCKey(code: FourCharCode(fromStaticString: "MSDI"),
|
||
|
info: DataTypes.Flag)
|
||
|
|
||
|
let data = try readData(key)
|
||
|
return Bool(fromByte: data.0)
|
||
|
}
|
||
|
|
||
|
public static func batteryInformation() throws -> batteryInfo {
|
||
|
let batteryCountKey =
|
||
|
SMCKey(code: FourCharCode(fromStaticString: "BNum"),
|
||
|
info: DataTypes.UInt8)
|
||
|
let batteryPoweredKey =
|
||
|
SMCKey(code: FourCharCode(fromStaticString: "BATP"),
|
||
|
info: DataTypes.Flag)
|
||
|
let batteryInfoKey =
|
||
|
SMCKey(code: FourCharCode(fromStaticString: "BSIn"),
|
||
|
info: DataTypes.UInt8)
|
||
|
|
||
|
let batteryCountData = try readData(batteryCountKey)
|
||
|
let batteryCount = Int(batteryCountData.0)
|
||
|
|
||
|
let isBatteryPoweredData = try readData(batteryPoweredKey)
|
||
|
let isBatteryPowered = Bool(fromByte: isBatteryPoweredData.0)
|
||
|
|
||
|
let batteryInfoData = try readData(batteryInfoKey)
|
||
|
let isCharging = batteryInfoData.0 & 1 == 1 ? true : false
|
||
|
let isACPresent = (batteryInfoData.0 >> 1) & 1 == 1 ? true : false
|
||
|
let isBatteryOk = (batteryInfoData.0 >> 6) & 1 == 1 ? true : false
|
||
|
|
||
|
return batteryInfo(batteryCount: batteryCount, isACPresent: isACPresent,
|
||
|
isBatteryPowered: isBatteryPowered,
|
||
|
isBatteryOk: isBatteryOk,
|
||
|
isCharging: isCharging)
|
||
|
}
|
||
|
}
|