Macに外付けしたUSBファンの電源を自動でOn/Off

USBファンの電源を自動でOn/Off

夏になってきて暑くなってきました。そしてMacBook Proも負荷がかかるとCPU温度が上がり、サーマルスロットリングがかかって処理が遅くなる現象が起きるようになってきたため、外付けのUSB冷却ファンをつけてみました。


LiANGSTARノートパソコンスタンド 冷却ファンつき

確かにつけているとサーマルスロットリングは回避できるようになったのですが、手動でOnにするのをよく忘れるし、作業後にOffにするのも手間という課題が。
ちょっとした作業時は(多少とはいえ)音も気になるので止まっていて欲しいし、とはいえ熱くなってきたらスロットリングする前に稼働していて欲しい。

そんなわけで、熱やスリープ状態に応じてファンのOn/Offを自動化してみました。

制御に使ったのはUSBハブの、per-port power switchingというポートごとに電源をON/OFFできるという機能です。
これに対応しているものは結構少なく、情報もあまりありません。今のところ確実なのはSUGOI HUBという商品のようで、これはポート1, 2の2つみですが電源の制御が可能です。

システムトークス USB2-HUB4X-BK USB2.0ハブ

制御プログラム

制御に使うプログラムは以下です。Linux等でよく使われていますがlibusb(legacy)でMacでもちゃんと動作します。

hub-ctrl.c

以下のようにインストール。libusb-compatは brew で導入します。

$ brew install libusb-compat
$ curl -O http://www.gniibe.org/oitoite/ac-power-control-by-USB-hub/hub-ctrl.c
$ gcc -Os `pkg-config --cflags --libs libusb` hub-ctrl.c

これで、SUGOI HUBを接続して ./hub-ctrl を実行すると

Hub #0 at 020:064
 INFO: individual power switching.
Hub #1 at 020:052
 INFO: individual power switching.
Hub #2 at 000:024
 INFO: individual power switching.

などと表示されます。このうちどれがSUGOI HUBかは、アップルメニュー > このMacについて > システムレポート の USB を開き、製造元IDが「NEC Corporation」になっているハブを探して、その場所IDを見ると分かります。今回はこんな感じになっており、 場所IDの後ろの部分が 64 と 064 が一致しているので、 Hub #0 が該当するものと分かります。

あとは以下のように、ハブ番号 0 を -h オプションに、電源をON/OFFしたいポートを -P オプションに指定して、-p 1 で on, -p 0 で off にできます。

hub-ctrl -h 0 -P 1 -p 0  # ポート1の電源off
hub-ctrl -h 0 -P 1 -p 1  # ポート1の電源on

本体のファン回転数に連動させる

さて、このポート1にUSBファンを接続して、熱くなってきたら自動でONになるようにしてみます。
MacのCPU温度やファン回転数の情報は、以下のように powermetrics コマンドを実行すると5秒ごとに取得・レポートさせることができます。

$ sudo powermetrics --samplers smc
...
**** SMC sensors ****

CPU Thermal level: 94
GPU Thermal level: 44
IO Thermal level: 44
Fan: 5054.41 rpm
CPU die temperature: 88.95 C (fan)
GPU die temperature: 76.00 C
CPU Plimit: 0.00
GPU Plimit (Int): 0.00 
GPU2 Plimit (Ext1): 0.00 
Number of prochots: 0

なかなか熱々。CPU温度のところに出ている (fan) という表示はファンで温度制御しているよ、というような意味でしょうか。さらに熱くなってサーマルスロットリング中は (power) などと出てきて、CPU Plimitに0以上の数値が表示されます。冷えている時は何も出ません。

これを使って、ファンを制御してみましょう。いろんな方法が考えられますが、本体ファンに連動させてみることにしました。一定以上の回転数で回っていたらonにするようにrubyでプログラムを書いてみました。on/offに幅が設けてあるのは閾値付近で頻繁にon/offが切り替わるのを避けるためです。

#!/usr/bin/ruby

`sudo true`  # パスワード入力を促すため
io = IO.popen("sudo powermetrics --samplers smc", "r")
while (line = io.gets)
  print line
  if line.match(/Fan: ([\d.]+)/)
    fan = $1.to_f
    if fan > 4200  # ON閾値
      system('hub-ctrl -h 0 -P 1 -p 1')
    elsif fan < 3800  # OFF閾値
      system('hub-ctrl -h 0 -P 1 -p 0')
    end
  end
end

これを実行しておくと、そろそろ熱くなってきたぞ、という頃合いに、自動的にUSBファンが周り始め、かなりの場合サーマルスロットリングを避けることができるようになりました(流石に全コアずっと100%で回り続けているような状況では放熱が追いつかないこともありますが、それでも性能劣化の度合いは緩和されているようです)。
音も本体ファンが高速に回っているような状況ならどのみち気にならないし、なかなか便利に使えています。