iMac Fan Control付属のツールを使ってiMac内部の温度を読み取る

先日、iMacを修正した。症状は表示の異常で、ディスプレイ一面、ドット柄になってしまった。

修理内容はビデオカードの交換であった。修理内容を記した書類に、この部品は熱を持ちやすいのでうんぬんとのコメント。何かで覆って使っているわけでもないし、部品を変えるわけにもいかない。でも熱が原因ならまた起こるかもしれない。

なんとかならないかなと少し検索したところiMac Fan Controlというのがよく使われているようだ。さっそくインストールし、ファンの回転数をやや高めに設定した。ビデオカードの温度はインストール前と比べて10度近く下がったところで安定しているようである。

だが、温度は使用状況によって大きく変わる。継続的なモニタリングと調整が必要になる。(次の春から夏にかけて、忘れたころに、という心配がある。)

そういったツールがないものかと探してみたのだが……どうやら有償のものばかり。有償でもいいのだが、どうもオーバースペックというかなんというか。裏でそっと動いていてくれれば十分なのに、という印象。

どうしたものかなと考えていたところ、iMac Fan Controlにsmcというコマンドが付属しているのに気付いた。

$ ./smc -h
Apple System Management Control (SMC) tool 0.01
Usage:
./smc [options]
    -f         : fan info decoded
    -h         : help
    -k <key>   : key to manipulate
    -l         : list all keys and values
    -r         : read the value of a key
    -w <value> : write the specified value to a key
    -v         : version

実行すると、ファンの回転数を調べられるのが分かった。

$ ./smc -f
Total fans in system: 3

Fan #0:
    Actual speed : 2801
    Minimum speed: 2800
    Maximum speed: 3800
    Safe speed   : 0
    Target speed : 2800
    Mode         : auto

[...]

また、その他のセンサーから値を取ることもできる。たとえばCPU温度は「TC0H」というキーで読み取れるらしい。そこでそのように実行してみる。

$ ./smc -r -k TC0H
TC0H  [sp78]  (bytes 2a 40)

このときのCPU温度はシステム設定上の表示によれば42.1℃であった。上のコマンド実行を繰り返すと「bytes」の部分が変わることから、何かしら読み取れているのはたしか。そして、システム設定の表示は、このようにして読み取られた内容に基いているはずだ。

ところでiMac Fan Controlの配布物(iMacFanControl1.2.dmg)には、ソースコードが含まれている。それならシステム設定の表示をしているところで何をしているか調べればよさそうだ。「sp78」や「temperature」をキーにして検索する。「2a 40」のような生の読み取り値を「温度」に変換しているのはどうやらsmc.cにあるSMCGetTemperatureらしい。具体的には以下の部分である。

        // read succeeded - check returned value
        if (val.dataSize > 0) {
            if (strcmp(val.dataType, DATATYPE_SP78) == 0) {
                // convert fp78 value to temperature
                int intValue = (val.bytes[0] * 256 + val.bytes[1]) >> 2;
                return intValue / 64.0;
            }
        }

val.bytes[0]val.bytes[1]には読み取った値、つまり上の実行例なら「2a」と「40」が入っている。それら二つの読み取り値を、一つめ→二つめの順にくっつける。くっつけたものを2ビット右にシフトする。最後に64で割る。これが温度に変換した結果となる、はずだ。実はナナメ読みしかしていないが、おそらくそうだ。(val.bytesあたりのことはsmcのソースコードにもあたるとだいたいそんな感じだろうと分かる。)

実際、どういう変換結果になるかやってみる。ここではRubyを使う。

$ ruby -e 'p ((0x2a*256 + 0x40)>>2)/64.0'
42.25

システム設定で表示されていた「42.1度」とは少しズレがあるが、それらしい結果のように思える。

読み取りのタイミングにズレがあるはずなので、そのあたりを含めて、システム設定の表示と見比べながら何度か繰り返し実行してみたい。だが上のように毎度数式を書いていたのでは間に合わない。そこで次のようにしてみる。

$ ./smc -r -k TC0H | ruby -pe '
  $_.sub!(/(\[sp78\]  \(bytes (..) (..)\))/) {
    $1 + " %4.1f"%(([$2, $3].pack("H2H2").unpack("n").first>>2)/64.0)
  }
'
  TC0H  [sp78]  (bytes 2a 40) 42.2

ちょっと長くなってしまったが、smcの出力に温度を付け加えている。smc.hによれば、CPU温度はTC0H、HDD温度はTm0P、DVD温度はTO0P、GPU温度はTG0Hとなっている。次のようにすれば他の温度もまとめて変換して表示させられる。

$ (for k in TC0H Tm0P TO0P TG0H; do ./smc -r -k $k; done) | \
  ruby -pe '$_.sub!(/(\[sp78\]  \(bytes (..) (..)\))/) { $1 + " %4.1f"%(([$2, $3].pack("H2H2").unpack("n").first>>2)/64.0) }'     
  TC0H  [sp78]  (bytes 2a a0) 42.6
  Tm0P  [sp78]  (bytes 2f e0) 47.9
  TO0P  [sp78]  (bytes 2d e0) 45.9
  TG0H  [sp78]  (bytes 34 80) 52.5

なお、HDD温度のTm0Pについて以下を参照のこと。Tm0Pはメモリコントローラの温度ではないかという話もあるようで、正確なところはよく分からない。