モチベーション
-
Jetson Nano A02を手に入れました
-
しかしファンがないのでよく落ちる
-
4ピンのPWMファンを買ってみた
-
しかしJetson Nanoには自動でPWM制御する機能は付いていない
これを書いた理由
様々な方が実際にファンの自動制御を行ってます。Pythonで温度を取得し、pwm制御を行うスクリプトがGitHubに公開されていたり...
しかし割と簡略化されてはいますが、ブラックボックス化されていて何やってるかイマイチわからん
Linux内でシステムの自動化をするならPythonよりbash(必ずとは限らない)
あと純粋このシステムだとbashの方がスマート
なにより、自分で理解して自分で作った方がそれっぽい
この記事を見るよりGitHubに公開されてるものをそのまま使ったほうが早いので、こちらは自分が処理内容を理解するための、そして自分が作ったという優越感に浸るための内容となっています。
やっていく流れ
- Jetson Nanoの温度情報を取得する
- Jetson NanoでPWM制御を行う
- 取得した温度情報からPWM信号に変換(設定)する
- bashで1→3→2の流れをスクリプトに起こす
- systemdに組み込んでデーモン化する
Jetson Nanoの温度情報を取得する
まず、Jetson Nanoの温度情報を取得します。参考はこちらです。
/sys/devices/virtual/thermalに、それぞれの温度を管理するzoneが格納されています。
$ cd /sys/devices/virtual/thermal/
$ ls
cooling_device0 cooling_device10 cooling_device12 cooling_device14 cooling_device16
cooling_device3 cooling_device5 cooling_device7 cooling_device9 thermal_zone1
thermal_zone3 thermal_zone5 cooling_device1 cooling_device11 cooling_device13
cooling_device15 cooling_device2 cooling_device4 cooling_device6 cooling_device8
thermal_zone0 thermal_zone2 thermal_zone4 thermal_zone6
こんな感じでthermal_zoneが複数あります。参考記事には3つあり、他の記事には8つだったり、私は7つと様々です。
次にこれらの温度情報と、それの属性(何の温度情報なのか)を確認します。
$ cat thermal_zone[0-6]/type
AO-therm
CPU-therm
GPU-therm
PLL-therm
PMIC-Die
thermal-fan-est
iwlwifi_1
$ cat thermal_zone[0-6]/temp
40000
30000
31000
29000
50000
29750
35000
このように、それぞれのthermal_zoneに格納されている温度情報とその種別を確認できます。
例えば、thermal_zone1はCPU-thermとあるのでCPU温度を指し、これが30000となっていますが、これは30℃です。
Jetson NanoのPWMファンを制御する
参考記事はこちらです。
pwmは0から255までの値を取り、/sys/devices/pwm-fanに含まれるterget_pwmの値によって回転数を制御できます。
$ sudo sh -c 'echo 255 > /sys/devices/pwm-fan/target_pwm'
おそらく最大火力なのでものすごい音になるかと思いますが、このコマンドの255を違う値にしてもう一度ターミナルに送信すると回転数が変化します。所感ですが、少しの値だと変化がわからないので、50、100単位で値をずらしてみるといいかもしれません。
bashスクリプトで5秒おきにpwmを温度情報に基づいて制御する
まず全体のスクリプトを示します。
#!/bin/bash
#daemon script path: /usr/local/bin/get_temp.sh
#daemon system name: autofan_daemon
#logfile
LOGFILE="/var/log/fan_thermal.log"
#温度を取得するzone
zones=(1 2 5)
# 温度を取得する関数
read_thermal_zone() {
local zone=$1
local temp_file="/sys/class/thermal/thermal_zone${zone}/temp"
if [[ -f $temp_file ]]; then
temp_millidegree=$(cat $temp_file)
temp_degree=$(echo "scale=3; $temp_millidegree / 1000" | bc)
else
temp_degree="Not available"
fi
echo "${temp_degree}"
}
# 温度の平均値を算出する関数
calculate_average_temperature() {
local sum=0
local count=0
for temp in "$@"; do
if [[ "$temp" != "Not available" ]]; then
sum=$(echo "$sum + $temp" | bc)
((count++))
fi
done
if (( count > 0 )); then
average=$(echo "scale=3; $sum / $count" | bc)
echo "$average"
else
echo "Not available"
fi
}
# 温度の平均値からPWM値を決定する関数
determine_pwm_value() {
local average_temp=$1
local pwm_value=0
# ここで段階的に設定
if [[ "$average_temp" != "Not available" ]]; then
if (( $(echo "$average_temp < 30" | bc -l) )); then
pwm_value=0
elif (( $(echo "$average_temp < 40" | bc -l) )); then
pwm_value=50
elif (( $(echo "$average_temp < 50" | bc -l) )); then
pwm_value=100
elif (( $(echo "$average_temp < 60" | bc -l) )); then
pwm_value=150
else
pwm_value=255
fi
else
pwm_value=0
fi
echo "$pwm_value"
}
# メインスクリプト
while true; do
temperatures=()
for zone in "${zones[@]}"; do
temp=$(read_thermal_zone $zone)
temperatures+=("$temp")
done
average_temp=$(calculate_average_temperature "${temperatures[@]}")
pwm_value=$(determine_pwm_value "$average_temp")
echo "$(date): Temp:$average_temp℃ PWM:$pwm_value" >>$LOGFILE
# Write PWM value to the system
if [[ -w /sys/devices/pwm-fan/target_pwm ]]; then
sudo sh -c "echo $pwm_value > /sys/devices/pwm-fan/target_pwm"
else
echo "Error: Cannot write to /sys/devices/pwm-fan/target_pwm"
fi
sleep 5 # 5秒待機
done
このスクリプトは温度を取得する関数、得た温度から代表値(今回は平均値)を算出する関数、代表値をpwm信号に変換する関数を用意し、これを5秒ごとに更新、そのたびにpwm信号を送信するというスクリプトになります。
温度を取得する関数
# 温度を取得する関数
read_thermal_zone() {
local zone=$1
local temp_file="/sys/class/thermal/thermal_zone${zone}/temp"
if [[ -f $temp_file ]]; then
temp_millidegree=$(cat $temp_file)
temp_degree=$(echo "scale=3; $temp_millidegree / 1000" | bc)
else
temp_degree="Not available"
fi
echo "${temp_degree}"
}
先ほど確認した温度情報の種別を見ると
- CPUの温度情報
- GPUの温度情報
- ファンの信号から取得しているヒートシンクの温度情報
が利用する温度情報に該当するので温度の取得するzoneを1,2,5に絞り、℃で扱う温度情報に変換します。
取得できなかった場合は"Not available"という文字列を代入します。
温度から代表値を算出する関数
# 温度の平均値を算出する関数
calculate_average_temperature() {
local sum=0
local count=0
for temp in "$@"; do
if [[ "$temp" != "Not available" ]]; then
sum=$(echo "$sum + $temp" | bc)
((count++))
fi
done
if (( count > 0 )); then
average=$(echo "scale=3; $sum / $count" | bc)
echo "$average"
else
echo "Not available"
fi
}
私は簡単に平均値を利用しました。
得られた温度情報が"Not availavble"ではない場合加算し、カウントします。そして加算された値とカウント数で割ることにより得られた温度情報のみ(1つや2つの場合もある)から平均値を算出できます。
代表値からpwm信号に変換する関数
# 温度の平均値からPWM値を決定する関数
determine_pwm_value() {
local average_temp=$1
local pwm_value=0
# ここで段階的に設定
if [[ "$average_temp" != "Not available" ]]; then
if (( $(echo "$average_temp < 30" | bc -l) )); then
pwm_value=0
elif (( $(echo "$average_temp < 40" | bc -l) )); then
pwm_value=50
elif (( $(echo "$average_temp < 50" | bc -l) )); then
pwm_value=100
elif (( $(echo "$average_temp < 60" | bc -l) )); then
pwm_value=150
else
pwm_value=255
fi
else
pwm_value=0
fi
echo "$pwm_value"
}
ここら辺は割と好みになるのではないかなと思います。
私は温度の代表値が特定の範囲でpwmを段階的に変化させていますが、例えば最大設定温度から規格化する直線的な変化や、温度の閾値を変化させるなど様々かと思います。
メインスクリプト
#!/bin/bash
#daemon script path: /usr/local/bin/get_temp.sh
#daemon system name: autofan_daemon
#logfile
LOGFILE="/var/log/fan_thermal.log"
#温度を取得するzone
zones=(1 2 5)
# メインスクリプト
while true; do
temperatures=()
for zone in "${zones[@]}"; do
temp=$(read_thermal_zone $zone)
temperatures+=("$temp")
done
average_temp=$(calculate_average_temperature "${temperatures[@]}")
pwm_value=$(determine_pwm_value "$average_temp")
echo "$(date): Temp:$average_temp℃ PWM:$pwm_value" >>$LOGFILE
# Write PWM value to the system
if [[ -w /sys/devices/pwm-fan/target_pwm ]]; then
sudo sh -c "echo $pwm_value > /sys/devices/pwm-fan/target_pwm"
else
echo "Error: Cannot write to /sys/devices/pwm-fan/target_pwm"
fi
sleep 5 # 5秒待機
done
ここでは、上で定義した3つの関数を呼び出し、pwm値を更新します。これを5秒毎に行います。
ログファイルfan_thermal.logを用意し、書き込むことで常時pwm値と平均温度の監視ができます。
また、書き込むpwm値が存在しない場合は書き込まないようにしています。
このスクリプトは、ターミナルで
$ sudo bash get_temp.sh
このように入力することで実行できます。
また、ログファイルの書き込みを許可するために
$ sudo chmod 666 /var/log/fan_thermal.log
とすることでログが書き込まれるようになります。
お願いダえもん!常時起動にして!
このbashスクリプトを/usr/local/binに移動し、systemdでデーモン名autofan_daemonを作成します。
$ sudo cp get_temp.sh /usr/local/bin
$ cd /etc/systemd/system/
$ sudo vi autofan_daemon.service
autofan_daemon.serviceにこのように設定します。
[Unit]
Description=Get Thermal Zone Information and Set PWM
After=network.target
[Service]
ExecStart=/usr/local/bin/get_temp.sh
Restart=on-failure
RestartSec=10
User=root
StandardOutput=journal
StandardError=journal
StartLimitIntervalSec=50
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
Userはrootにしないと権限上書き込みができないので気を付けましょう。
そしたらこれを常時起動にして完成です。
$ sudo systemctl start autofan_daemon
$ sudo systemctl enable autofan_daemon
ちゃんと再起動してもpwm制御でファンが起動するかと思います。
よきJetsonライフを
補足:スクリプトを修正した場合
もしpwm値や回転条件などを変更した場合、/usr/local/binにあるget_temp.shを変更しただけでは何も変化しないので、ダえもんをリロードしてあげてください。
$ sudo systemctl daemon-reload
$ sudo systemctl restart autofan_daemon
参考