Skip to content

Commit

Permalink
Merge branch 'feature/SMC'
Browse files Browse the repository at this point in the history
  • Loading branch information
dstorm-fl committed Jul 17, 2019
2 parents 16f6031 + 31f57f4 commit f1bf370
Show file tree
Hide file tree
Showing 18 changed files with 546 additions and 62 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# SMC
# SMC
### Wrapper for macOS's System Management Controller (SMC) written in Swift.

#### License
MIT license. See the LICENSE file for more information.

#### References
- [SMCWrapper](https://github.com/FergusInLondon/SMCWrapper)
- [SMCKit](https://github.com/beltex/SMCKit)
33 changes: 33 additions & 0 deletions smc/SMC/Device.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// Device.swift
// smc
//
// Created by Daniel Storm on 7/17/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

struct Device {

struct Controller {

static let isT2: Bool = {
let system_profiler = Process()
let pipe = Pipe()

system_profiler.launchPath = "/usr/sbin/system_profiler"
system_profiler.arguments = ["SPiBridgeDataType"]
system_profiler.standardOutput = pipe

system_profiler.launch()
system_profiler.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String? = String(data: data, encoding: .utf8)
return output?.contains("Apple T2 Security Chip") == true ? true : false
}()

}

}
24 changes: 24 additions & 0 deletions smc/SMC/SMC+CPU.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// SMC+CPU.swift
// smc
//
// Created by Daniel Storm on 7/17/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

extension SMC {

// MARK: - CPU
public func cpuTemperature(key: SensorKey = Sensor.CPU.proximity) -> Temperature? {
guard let bytes = bytes(key: key) else { return nil }

let celsius = Double(bytes.0 & 0x7F)

return Temperature(celsius: celsius,
fahrenheit: Temperature.fahrenheit(celsius: celsius),
kelvin: Temperature.kelvin(celsius: celsius))
}

}
23 changes: 23 additions & 0 deletions smc/SMC/SMC+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// SMC+Extensions.swift
// smc
//
// Created by Daniel Storm on 6/30/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

extension String {

func smcKey() -> UInt32? {
guard self.count == 4 else { return nil }

let value = self.utf8.reduce(0) { sumOfBits, character in
return sumOfBits << 8 | UInt32(character)
}

return value
}

}
75 changes: 75 additions & 0 deletions smc/SMC/SMC+Fans.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// SMC+Fans.swift
// smc
//
// Created by Daniel Storm on 7/17/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

extension SMC {

// MARK: - Fans
struct Fan {
let identifier: Int
let currentRPM: Int?
let minimumRPM: Int?
let maximumRPM: Int?
let targetRPM: Int?
}

public func fans() -> [Fan] {
guard let numberOfFans = numberOfFans() else { return [] }
var fans: [Fan] = []
for i in 0..<numberOfFans { fans.append(fan(at: i)) }
return fans
}

public func numberOfFans() -> Int? {
guard let bytes = bytes(key: "FNum") else { return nil }
return Int(bytes.0)
}

private func fan(at index: Int) -> Fan {
return Fan(identifier: index,
currentRPM: fanCurrentRPM(for: index),
minimumRPM: fanMinimumRPM(for: index),
maximumRPM: fanMaximumRPM(for: index),
targetRPM: fanTargetRPM(for: index))
}

private func fanCurrentRPM(for identifier: Int) -> Int? {
guard let bytes = bytes(key: "F\(identifier)Ac") else { return nil }
return integer(for: bytes)
}

private func fanMinimumRPM(for identifier: Int) -> Int? {
guard let bytes = bytes(key: "F\(identifier)Mn") else { return nil }
return integer(for: bytes)
}

private func fanMaximumRPM(for identifier: Int) -> Int? {
guard let bytes = bytes(key: "F\(identifier)Mx") else { return nil }
return integer(for: bytes)
}

private func fanTargetRPM(for identifier: Int) -> Int? {
guard let bytes = bytes(key: "F\(identifier)Tg") else { return nil }
return integer(for: bytes)
}

// MARK: - Helpers
private func integer(for bytes: SMCBytes) -> Int {
// Devices that have a T2 security chip use an FLT data type
if Device.Controller.isT2 {
var value: Float = 0.0
memcpy(&value, [bytes.0, bytes.1, bytes.2, bytes.3], 4)
return Int(value)
}
else {
return (Int(bytes.0) << 6) + (Int(bytes.1) >> 2)
}
}

}
24 changes: 24 additions & 0 deletions smc/SMC/SMC+GPU.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// SMC+GPU.swift
// smc
//
// Created by Daniel Storm on 7/17/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

extension SMC {

// MARK: - GPU
public func gpuTemperature(key: SensorKey = Sensor.GPU.proximity) -> Temperature? {
guard let bytes = bytes(key: key) else { return nil }

let celsius = Double(bytes.0 & 0x7F)

return Temperature(celsius: celsius,
fahrenheit: Temperature.fahrenheit(celsius: celsius),
kelvin: Temperature.kelvin(celsius: celsius))
}

}
30 changes: 30 additions & 0 deletions smc/SMC/SMC+Sensor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// SMC+Sensor.swift
// smc
//
// Created by Daniel Storm on 7/17/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation

typealias SensorKey = String

extension SMC {

// MARK: - Sensor
public struct Sensor {

// MARK: - CPU
public struct CPU {
public static let proximity: SensorKey = "TC0P"
}

// MARK: - GPU
public struct GPU {
public static let proximity: SensorKey = "TG0P"
}

}

}
124 changes: 124 additions & 0 deletions smc/SMC/SMC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// SMC.swift
// smc
//
// Created by Daniel Storm on 6/30/19.
// Copyright © 2019 Daniel Storm (github.com/DanielStormApps).
//

import Foundation
import IOKit

class SMC {

private static var connection: io_connect_t = 0

// MARK: Init
static let shared = SMC()
private init() {
openConnection()
}

// MARK: - Connection Lifecycle
private func openConnection() {
let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleSMC"))
assert(IOServiceOpen(service, mach_task_self_, 0, &SMC.connection) == kIOReturnSuccess, "Unable to start SMC")
IOObjectRelease(service)
}

private func closeConnection() {
IOServiceClose(SMC.connection)
}

// MARK: - SMC
public func bytes(key: String) -> SMCBytes? {
guard let smcKey = key.smcKey() else { return nil }
let outputDataSize = dataSize(smcKey: smcKey)
let outputBytes = bytes(smcKey: smcKey, dataSize: outputDataSize)
return outputBytes
}

// MARK: - Helpers
private func dataSize(smcKey: UInt32) -> IOByteCount {
var inputStructure = SMCStructure()
var outputStructure = SMCStructure()

let inputStructureSize = MemoryLayout<SMCStructure>.stride
var outputStructureSize = MemoryLayout<SMCStructure>.stride

inputStructure.key = smcKey
inputStructure.data8 = 9

let _ = IOConnectCallStructMethod(SMC.connection,
2,
&inputStructure,
inputStructureSize,
&outputStructure,
&outputStructureSize)

return outputStructure.keyInfo.dataSize
}

private func bytes(smcKey: UInt32, dataSize: UInt32) -> SMCBytes {
var inputStructure = SMCStructure()
var outputStructure = SMCStructure()

let inputStructureSize = MemoryLayout<SMCStructure>.stride
var outputStructureSize = MemoryLayout<SMCStructure>.stride

inputStructure.key = smcKey
inputStructure.keyInfo.dataSize = dataSize
inputStructure.data8 = 5

let _ = IOConnectCallStructMethod(SMC.connection,
2,
&inputStructure,
inputStructureSize,
&outputStructure,
&outputStructureSize)

return outputStructure.bytes
}

// MARK: - Deinit
deinit {
closeConnection()
}

}

extension SMC {

#if DEBUG
/// - Note: Only available in `DEBUG` environment.
func printSystemInformation() {
print("------------------")
print("System Information")
print("------------------")

// Fans
print()
let fans = SMC.shared.fans()
for fan in fans {
print("Fan: \(fan)")
}

// CPU
print()
let cpuTemperature = SMC.shared.cpuTemperature()
print("CPU C: \(String(describing: cpuTemperature?.celsius))")
print("CPU F: \(String(describing: cpuTemperature?.fahrenheit))")
print("CPU K: \(String(describing: cpuTemperature?.kelvin))")

// GPU
print()
let gpuTemperature = SMC.shared.gpuTemperature()
print("GPU C: \(String(describing: gpuTemperature?.celsius))")
print("GPU F: \(String(describing: gpuTemperature?.fahrenheit))")
print("GPU K: \(String(describing: gpuTemperature?.kelvin))")

print()
print("------------------")
}
#endif
}
Loading

0 comments on commit f1bf370

Please sign in to comment.