ホームページ開発ツール>Xojo / Real Studio Trial and Error・XcodeとCocoaのDeclareでSMCにアクセスする

 Xojo / Real Studio Trial and Error

XcodeとCocoaのDeclareでSMCにアクセスする

目次
 はじめに

 以下は、Xojo Cocoaビルドについての話題です。

 SMCへのアクセスについて、調べてみました。

 なお検証には、Xojo 2017 Release 1.1とXcode 11.3.1を用いています。(Mac mini 2018 + macOS 10.14.6 Mojave)


 方針

 夏の盛りともなると、Mac miniでは排熱が気になるところでもあるので、内部温度について調べてみることにしました。
 当初はアプリケーションを念頭に置いていたのですが、進めるうちに、内部温度(とファンスピード)は、SMC(System Management Controller)から取得できることが分かってきました。
 ググってみると、思いの外SMCにアクセスするサンプルコードが公開されていましたので、先ずはそれらを使わせて頂くこととしました。

 参考サイト(1):GitHub - hholtmann/smcFanControl: Control the fans of every Intel Mac to make it run cooler
 参考サイト(2):GitHub - lavoiesl/osx-cpu-temp: Outputs current CPU temperature for OSX
 参考サイト(3):GitHub - FergusInLondon/SMCWrapper: A clean and documented OOP wrapper around the AppleSMC IOService on Mac OS X.
 参考サイト(4):GitHub - beltex/SMCKit: Apple SMC library & tool

 一通り実行してみましたが、mini 2018でファンスピードが正常に表示されたのは「smcFanControl 2.6.1 Beta 1」だけだったので、こちら一本に絞り込みました。
 勿論、このアプリをそのまま利用させて頂いて何の問題もないのですが、折角なので(笑)、カスタマイズしてみることにしました。
注意:smcFanControlはGPLライセンスが適用されていますので、ソースコードを利用したものを再配布する場合は、規約に従う必要があります。
 ソースを見ると分かりますが、SMCアクセスとユーザーインターフェイスは分離していて、SMCアクセスはコマンドランツールとしてもビルドされるようになっています。
 このツールを実行すると必要な情報が返ってくるので、これをラップ(つまり、GUIをXcodeからXojoベースに変更)するだけでアプリとして成立はします。
 ただし、シェルを起動してツールを実行し、標準出力から得られる文字列をパースするのは、大した手間ではないとは云え、やや冗長にはなります。
 そこで今回は、こちらでもやったdylib化の手法を使い、Declareから直接呼び出すことで、呼び出し部分の簡素化を試みます。

 前述の通り、SMCアクセスは一つのファイル(smc.c)に纏められているので、先ずは新規プロジェクトに(ヘッダーと共に)コピーします。
 次に、printf()sprintf()strcat()に置き換えて、内部で文字列を保持するようにします。
 最後に、main関数の代替となる、Declareで呼び出すメソッドを追加します。
 保持した文字列は戻り値ではなく、引数で受け渡すこととします。(戻り値はエラーコード用とするためで、これはオリジナルから踏襲。)

 取得する情報は、CPU温度とファンスピードとします。
温度センサーは内部の至る所にあるようで、Mac mini 2018では31箇所分が取得された。
箇所名はFourCCで表されるが、それが何処なのかの公式情報はないようで、非公式には例えば以下のサイト等が参考になるが、それでも半分くらい。
参考サイト(5):macos - Description for Apple's SMC Keys - Stack Overflow
 以上を踏まえ、(残りの)仕様は以下の通りとしました。

 dyllibのビルド(Xcode)
  1. Xcodeで新規プロジェクトを作成(テンプレートダイアログで「Library」を選択。ファイル名はここでは「smc」)
  2. smcフォルダに「new file...」でファイル作成(テンプレートダイアログで「C File」を選択。ファイル名はここでは「smc」)
  3. smc.cに、smcFanControl-2.6.1-1の「smc.c」の内容をコピー&ペースト(全文置き換え)
  4. smc.hに、smcFanControl-2.6.1-1の「smc.h」の内容をコピー&ペースト(全文置き換え)
  5. smc.cの、int main(int argc, char *argv[]) {...}を削除
  6. 以下をsmc.cにペースト
    int getSMCinfo(char *retStr) {
        kern_return_t result;
        int ret1 = 0, ret2 = 0;
    
        smc_init();
    
        result = SMCPrintTemps(retStr);
        if (result != kIOReturnSuccess) ret1 = 1;
    	
        strcat(retStr, "separater\n");  // 温度とFan Speedを分離し易くするために挿入
    
        result = SMCPrintFans(retStr);
        if (result != kIOReturnSuccess) ret2 = 2;
    	
        smc_close();
        
        return ret1+ret2;
    }
    
  7. SMCPrintTemps/SMCPrintFansに引数と変数を追加(以下はSMCPrintTempsの場合)
    kern_return_t SMCPrintTemps(char *retStr)
    {
        char str[100];
    …
    
  8. SMCPrintTemps/SMCPrintFansのprintf()を、sprintf()strcat()に置き換え(以下は一例)
        // printf("%-4s ", val.key);
        sprintf(str, "%-4s ", val.key);
        strcat(retStr, str);
    	
        // printf("    Fan ID       : %s\n", val.bytes+4);  // SMCPrintFans()では、ラベルに相当する文字列は削除しておく。
        sprintf(str, "%s\n", val.bytes+4);
        strcat(retStr, str);
    
    注)printSP78(val);は一箇所しか呼ばれていないので、展開して同様の対策を施したほうが楽。
  9. ビルド
  10. 出来上がったsmc.dylibをXojoのプロジェクトと同じフォルダ内に置く
  11. リリース時にはアプリケーションパッケージ内のFrameworksフォルダ内のsmc.dylibを使うようにしてあるため、何らかの手段でコピーする

 Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
  1. Xojoで新規プロジェクトを作成(64bitビルド)
  2. Window1に、Canvas2個(Name:Canvas1、Name:Canvas2)、GroupBox2個(Name:GroupBox1、Name:GroupBox2)、Label18個(Name:Label1,Text:0.0、Name:Label2,Text:0、Name:Label3,Text:0、Name:Label4,Text:50、Name:Label5,Text:100、Name:Label6,Text:150、Name:Label7,Text:min、Name:Label8,Text:max)、PushButton(Name:PushButton1)を追加(配置はスクリーンショットを参照)
  3. 以下をCanvas1にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
    Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
      // 背景を白で塗り潰し
      g.ForeColor=RGB(255,255,255)
      g.FillRect 0,0,me.Width,me.Height
      
      // 温度に対応する領域を赤で塗り潰し
      Dim w As Integer = Round(me.Width*(pTempe/150))  // 150は表示の上限
      if w>0 then
        g.ForeColor=RGB(222,64,64)
        g.FillRect 0,0,w,me.Height
      end if
    End Sub
    
  4. 以下をCanvas2にペースト(できなければ、Sub - Endの間をPaintイベントに記述)
    Sub Paint(g As Graphics, areas() As REALbasic.Rect) Handles Paint
      // 背景を白で塗り潰し
      g.ForeColor=RGB(255,255,255)
      g.FillRect 0,0,me.Width,me.Height
      
      // 回転数を表示幅に変換
      Dim wc As Integer = Round(me.Width*(pRPMact/5000))  // 5,000は表示の上限
      Dim wi As Integer = Round(me.Width*(pRPMmin/5000))
      Dim wa As Integer = Round(me.Width*(pRPMmax/5000))
      
      // Fan Speed現在値に対応する領域を緑で塗り潰し
      if wc>0 then
        g.ForeColor=RGB(0,192,0)
        g.FillRect 0,0,wc,me.Height
      end if
      
      // Fan Speed最小値の位置に線を描いてラベルを移動
      if wi>0 then
        g.ForeColor=RGB(0,0,192)
        g.DrawLine wi,0,wi,me.Height
        Label7.Left=me.Left+wi-10
      end if
      
      // Fan Speed最大値の位置に線を描いてラベルを移動
      if wa>0 then
        g.ForeColor=RGB(192,0,0)
        g.DrawLine wa,0,wa,me.Height
        Label8.Left=me.Left+wa-10
      end if
    End Sub
    
  5. 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
    Sub Action() Handles Action
      // SMC情報を取得して表示
      ExecSMC()
    End Sub
    
  6. 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
    Sub Open() Handles Open
      // SMC情報を取得して表示
      ExecSMC()
    End Sub
    
  7. 以下をWindow1にペースト
    Private Sub ExecSMC()
      // SMC情報の取得
      #if DebugBuild  // デバッグ時にはプロジェクトと同じフォルダ内にあるものを使う。
        Declare Function getSMCinfo Lib "@executable_path/../../../libsmc.dylib" (str As Ptr) As Integer
      #else  // リリース時にはアプリケーションパッケージ内のFrameworksフォルダ内にあるものを使う。ビルド後に何らかの手段でコピーしておく。
        Declare Function getSMCinfo Lib "@executable_path/../Frameworks/libsmc.dylib" (str As Ptr) As Integer
      #endif
      Dim stm As New MemoryBlock(1000)  // 情報格納領域(Xojo側でallocしておく)
      Dim ret As Integer = getSMCinfo(stm)
      Dim stc As CString = stm.CString(0)  // MemoryBlockをCStringとして取得
      Dim st As String = stc  // CStringをStringとして取得(必要ないかも)
      
      Dim i As Integer
      Dim vv As Single
      Dim ss, ary1(-1), ary2(-1) as String
      
      // 温度情報とFan Speed情報を分離
      ary1=Split(st,"separater")
      
      // CPU温度の取得
      ary2=Split(ary1(0),Chr(10))
      for i=0 to ary2.Ubound
        if InStrB(ary2(i),"TC0P")>0 then  // CPU
          ss=NthField(ary2(i)," ",2)
          vv=Val(ss)  // 数値化
          pTempe=vv  // 描画用に保持
          vv=vv*10  // 10倍
          vv=Round(vv)  // 小数点以下を四捨五入して整数化
          vv=vv/10  // 10倍を戻す
          Label1.Text=Format(vv, "#.0")+" °C"  // 小数点以下1位が0でも表示する
        end if
      next
      
      // Fan Speedの取得
      ary2=Split(ary1(1),Chr(10))
      pRPMact=Val(ary2(4))  // Actual speed
      pRPMmin=Val(ary2(5))  // Minimum speed
      pRPMmax=Val(ary2(6))  // Maximum speed
      Label2.Text=Format(pRPMact, "#,###")+" RPM"  // 千の位の後にカンマを挟む
    
      // 結果をグラフに反映
      Canvas1.Refresh
      Canvas2.Refresh
    End Sub
    
  8. 以下をWindow1にペースト(できなければプロパティに、名前:pRPMact、データ型:Integer、を追加)
    Private Property pRPMact as Integer
    
  9. 以下をWindow1にペースト(できなければプロパティに、名前:pRPMmax、データ型:Integer、を追加)
    Private Property pRPMmax as Integer
    
  10. 以下をWindow1にペースト(できなければプロパティに、名前:pRPMmin、データ型:Integer、を追加)
    Private Property pRPMmin as Integer
    
  11. 以下をWindow1にペースト(できなければプロパティに、名前:pTempe、データ型:Single、を追加)
    Private Property pTempe as Single
    
 実行してみたところ、CPU温度とファンスピードが取得できることを確認しました。
S Shot1


 おわりに

 CPUコアの温度は、TC0Pと比べると10〜20度程度高く表示されます。これは既知の情報のようですが、どちらを気にすればいいのかはよく分かりません。
 ひとまず取得した温度情報は全て表示する、というのも一つの手でしょう。

 参考サイト(6):Discrepancy between TC0P and actual CPU temperature · Issue #10 · MikaelStrom/macfanctld · GitHub

 またファンスピードは、温度がそれほど高くなってないせいもあってか、最低値近辺をウロウロしていますが、時々最低値を下回ります。誤差範囲ということかもしれませんが、こちらもよく分かりません。


 お世話になったサイト

 貴重な情報をご提供頂いている皆様に、お礼申し上げます。(以下、順不同)

 参考サイト(1):GitHub - hholtmann/smcFanControl: Control the fans of every Intel Mac to make it run cooler
 参考サイト(2):GitHub - lavoiesl/osx-cpu-temp: Outputs current CPU temperature for OSX
 参考サイト(3):GitHub - FergusInLondon/SMCWrapper: A clean and documented OOP wrapper around the AppleSMC IOService on Mac OS X.
 参考サイト(4):GitHub - beltex/SMCKit: Apple SMC library & tool
 参考サイト(5):macos - Description for Apple's SMC Keys - Stack Overflow
 参考サイト(6):Discrepancy between TC0P and actual CPU temperature · Issue #10 · MikaelStrom/macfanctld · GitHub


 更新履歴

 2020.09.07 新規作成


[Home]  [MacSoft]  [Donation]  [History]  [Privacy Policy]  [Affiliate Policy]