• System Management Controller (SMC) Interface and Background Timer

    Rob Grizzard02/22/2020 at 21:47 0 comments

    The SMC can be used to retrieve various sensor readings [1] and set fan speeds [2].  The fuzzer at [1] is very interesting, and I may use it in the future to dynamically populate a list of available sensors, but for now I am content to use the average of two SMC keys (TC0E and TC0F) that report the CPU die temperature as reported by a superuser forum post.  I am following Sébastien Lavoie's example and applying the GNU General Public License v2.0 to this project because it uses devnull's smc library [3].

    A background timer was used to request the CPU temperature at regular intervals.  The steps followed for its implementation were:

    1. Create a new Swift file within the macos-temperature-monitor group and call it "Background_Timer.swift".  The class, "Background_Timer", should conform to the ObservableObject protocol for interaction with the UI. 
    2. Daniel Galasko's excellent post on Medium about background timers in Swift [4] was followed to include the class "RepeatingTimer".
    3. An instance of RepeatingTimer was added as a property of instances of Background_Timer, and its event handler was modified during initialization to update its counter property:
      init() { myRT = RepeatingTimer(timeInterval: 10) myRT.eventHandler = { print("Timer Fired") self.incrementCounter() print("Counter value: \(self.counter)") } myRT.resume() }
    4. The UI was updated to display the counter from the timer [5].

    Next, the SMC logic was added.

    1. The file "XPC_Tester.swift" was renamed to "CPU_Temp_Handler.swift" and its contents were replaced with an empty class called "CPU_Temp_Handler" that conforms to the ObservedObject protocol.
    2. A new group was added to CPU_Temp_XPC target, and it was called "SMC"
    3. A C file was added to the SMC group, and it was named "smc.c".  A header was also created automatically by selecting "Also create a header file" from the dialog. 
      smc.c was added to the SMC group and applied to the CPU_Temp_XPC target.
      An Objective-C bridging header was created by selecting that option from the dialog.
    4. The contents for these files came from the examples at [3], with the addition of of the function void getCpuTemp(char* to_write, size_t size) that can be used to get the average of the two CPU temperature sensors and write their average in Fahrenheit to an input buffer.
      void getCpuTemp(char* to_write, size_t size) { double temperature_A = SMCGetTemperature(SMC_KEY_CPU_0_DIE_TEMP_A); double temperature_B = SMCGetTemperature(SMC_KEY_CPU_0_DIE_TEMP_B); double avg_temp = temperature_A; if (temperature_B != 0.0) { avg_temp = (temperature_A + temperature_B) / 2.0; } avg_temp = convertToFahrenheit(avg_temp); //printf("size: %lu", size); snprintf(to_write, size, "%fF", avg_temp); }
    5. The XPC interface was updated to interact with this library:
      1. The function prototypefunc getCPUTemp(withReply reply: @escaping (String) -> Void) was added to the protocol
      2. Its definition was added to the CPU_Temp_XPC class:
        func getCPUTemp(withReply reply: @escaping (String) -> Void){ _ = SMCOpen() var toReturn = "" let sizeToReturn: CUnsignedLong = 10 var addressBuffer = [Int8](repeating:0, count:Int(sizeToReturn)) getCpuTemp(&addressBuffer, Int(sizeToReturn)) let data = Data(bytes: addressBuffer as [Int8], count: Int(CUnsignedLong(sizeToReturn))); toReturn = String(data: data, encoding: .utf8) ?? "" SMCClose() reply(toReturn) <br>
    6. There likely exists a better way to update the UI with the value retrieved from the XPC Service passing data from the SMC than what follows, and if you have improvements then please let me know:
      1. The class that communicates with the XPC Service, CPU_Temp_Handler, has an instance property to store the String returned from the XPC Service, and a function to assign the returned value to this property
        import Foundation import CPU_Temp_XPC class CPU_Temp_Handler { var CPU_Temp = "" func setCPUTemp() { let connection = NSXPCConnection(serviceName: "com.grizz.CPU-Temp-XPC") connection.remoteObjectInterface...
    Read more »

  • Add XPC Service

    Rob Grizzard02/22/2020 at 18:02 0 comments

    I started adding an XPC Service [1] by using XCode's built-in functionality.

    At this point, I consulted the excellent blog post [2] by Matthew Miner, in which the process of converting the boilerplate [3] Objective-C code into Swift is described.  Interested readers should consult the original source, but the steps are repeated here.

    1. Rename files:
      main.m -> main.swift
      CPU_Temp_XPC.h -> CPU_Temp_XPC.swift
      CPU_Temp_XPC.m -> CPU_Temp_XPC_Delegate.swift
      CPU_Temp_XPCProtocol.h -> CPU_Temp_XPCProtocol.swift
    2. Add the files to the target's "Compile Sources" build phase.
    3. Replace the Objective-C contents with Swift translations and edit build settings:

      1. Install Objective-C Compatibility Header: NO
      2. Objective-C Generated Interface Header Name: “”
      3. Runpath Search Paths: @loader_path/../../../../Frameworks
      4. Swift Language Version: Swift 5
    4. Add testing logic [4]
      1. NOTE - the NSXPCConnection used for testing must use the target's bundle identifier
      2. This commit adds a class, XPC_Tester, and some front-end logic to test that the XPC Service is configured properly.  The test class conforms to the ObservableObject protocol and publishes a variable for tracking by the front-end.  The front-end in turn calls the tester to convert an input String to uppercase using the XPC Service.

    [1] https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html#//apple_ref/doc/uid/10000172i-SW6-SW1
    [2] https://matthewminer.com/2018/08/25/creating-an-xpc-service-in-swift.html
    [3] https://github.com/grizzardrl/macOS-temperature-monitor/commit/b7936595c879250a03e8c1f551d98bf62c6d2908
    [4] https://github.com/grizzardrl/macOS-temperature-monitor/commit/35193d6eda99437689615a212d99ad6ceec94906

  • Initial commit

    Rob Grizzard02/22/2020 at 17:20 0 comments

    I am using XCode's version control with GitHub.  I followed Apple's documentation [1] here.  The resulting commit can be found at [2].

    [1] https://help.apple.com/xcode/mac/current/#/dev3fd7ccc7a
    [2] https://github.com/grizzardrl/macOS-temperature-monitor/commit/97542ffe98522a879234f5c29ad6a6dc03e28a15