ホームページ>開発ツール>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
- Mac mini 2018に特化したものとする。(他機種の場合は、適宜対策して下さい。)
- CPU温度にはTC0Pを用いるのが作法のようなので、これを使う。
- CPU温度は、最低を0度、最高を150度としてグラフ化する。
- ファンスピードは、最低を0RPM、最高を5,000RPMとしてグラフ化する。
- 情報の取得のみで、セットは行わない。
dyllibのビルド(Xcode)
- Xcodeで新規プロジェクトを作成(テンプレートダイアログで「Library」を選択。ファイル名はここでは「smc」)
- smcフォルダに「new file...」でファイル作成(テンプレートダイアログで「C File」を選択。ファイル名はここでは「smc」)
- smc.cに、smcFanControl-2.6.1-1の「smc.c」の内容をコピー&ペースト(全文置き換え)
- smc.hに、smcFanControl-2.6.1-1の「smc.h」の内容をコピー&ペースト(全文置き換え)
- smc.cの、int main(int argc, char *argv[]) {...}を削除
- 以下を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; }
- SMCPrintTemps/SMCPrintFansに引数と変数を追加(以下はSMCPrintTempsの場合)
kern_return_t SMCPrintTemps(char *retStr) { char str[100]; …
- SMCPrintTemps/SMCPrintFansのprintf()を、sprintf()とstrcat()に置き換え(以下は一例)
注)printSP78(val);は一箇所しか呼ばれていないので、展開して同様の対策を施したほうが楽。// 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);
- ビルド
- 出来上がったsmc.dylibをXojoのプロジェクトと同じフォルダ内に置く
- リリース時にはアプリケーションパッケージ内のFrameworksフォルダ内のsmc.dylibを使うようにしてあるため、何らかの手段でコピーする
Xojoでの実装
【ソースコードのコピー&ペーストについて】
・ソースコード(グレー背景部分の全文)をコピーし、指定のオブジェクトにペーストすると、(新規作成して名前等を個別にコピー&ペーストしなくても)復元されます。
・ペーストはオブジェクトに行って下さい。オブジェクト内のEvent Handlers/Methods/Properties等にペーストしても、うまくいかない場合があります。
・それでもペーストできない場合は、各項目のカッコ内を適用して下さい。
実行してみたところ、CPU温度とファンスピードが取得できることを確認しました。
- Xojoで新規プロジェクトを作成(64bitビルド)
- 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)を追加(配置はスクリーンショットを参照)
- 以下を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
- 以下を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
- 以下をPushButton1にペースト(できなければ、Sub - Endの間をActionイベントに記述)
Sub Action() Handles Action // SMC情報を取得して表示 ExecSMC() End Sub
- 以下をWindow1にペースト(できなければ、Sub - Endの間をOpenイベントに記述)
Sub Open() Handles Open // SMC情報を取得して表示 ExecSMC() End Sub
- 以下を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
- 以下をWindow1にペースト(できなければプロパティに、名前:pRPMact、データ型:Integer、を追加)
Private Property pRPMact as Integer
- 以下をWindow1にペースト(できなければプロパティに、名前:pRPMmax、データ型:Integer、を追加)
Private Property pRPMmax as Integer
- 以下をWindow1にペースト(できなければプロパティに、名前:pRPMmin、データ型:Integer、を追加)
Private Property pRPMmin as Integer
- 以下をWindow1にペースト(できなければプロパティに、名前:pTempe、データ型:Single、を追加)
Private Property pTempe as Single
おわりに
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]