Skip to main content

Fan problem on ASUS NAS M-25

Posted in

Intro

Those of you who own ASUS NAS M-25 might have noticed the weird behavior of its fan. The web interface is said to have a control for it, but I have searched it thoroughly and didn't find anything that has to do with fan.

Leaving it permanently on or off is not an option. By the way, when in terminal (via telnet) one can issue the following command
to turn the fan on at low rate:

# cpu_ctl fan_low

Similarly, use fan_stop and fan_high, for disabling and running the fan at its maximum correspondingly. Using these commands manually when intensive writing/reading is expected is not an option either. Yes, this is better than nothing, but you have to have telnetd enabled all the time, you have to log in and run the command.

Let us take a look at what can be done. Log in to your NAS. Remember that once NAS is started the telnet daemon is kept alive for 3 minutes, so we might want make sure it is alive longer, for at least this session. This is done by:

# ps | grep prod
# kill <pid of product_test_daemon>

What other processes are running there?

# ps | grep fan
  496 root      3528 S    fancontrold

Hm, the daemon is running but it seems it is not doing what it is supposed to do. I would like to know what options does it have.

# fancontrold -h
fancontrold: invalid option -- h
[1] + Stopped                    fancontrold -h
# 

It looks like no way to find out. No need to have 2 instances running, so I just kill the last one.

# ps | grep fan
  496 root      3528 S    fancontrold 
 3227 root      1680 T    fancontrold -h 
# kill -9 3227
#

Next idea is to try to find its config. I suppose it may be in /etc.

# ls -l /etc
drwxr-xr-x    2 root     root         1024 Oct 28 06:00 hotplug
-rwxrwxrwx    1 root     root         3572 Oct 27 13:43 iTunescript
-rwxr-xr-x    1 root     root          248 Oct 27 13:43 mt-daapd.playlist
-rw-r--r--    1 root     root          188 Oct 27 13:43 nsswitch.conf
drwxr-xr-x    2 root     root         1024 Oct 28 06:00 pam.d
drwxr-xr-x    2 root     root         1024 Oct 28 06:00 plugins
-rw-r--r--    1 root     root         1925 Mar  3 22:15 pure-ftpd.pem
-rwxr-xr-x    1 root     root         6500 Oct 27 13:43 rc.sh
lrwxrwxrwx    1 root     root           20 Oct 28 06:00 rtc.conf -> /system/cfg/rtc.conf
-rwxr-xr-x    1 root     root           75 Oct 27 13:43 start_services.sh
-rwxr-xr-x    1 root     root           73 Oct 27 13:43 stop_services.sh
-rwxr-xr-x    1 root     root           73 Oct 27 13:43 stop_services_firmwareupgrade.sh
-rwxr-xr-x    1 root     root          836 Oct 27 13:43 twonky.sh
-rwxr-xr-x    1 root     root          430 Oct 27 13:43 udhcpd.conf
-rwxr-xr-x    1 root     root          348 Oct 27 13:43 udhcpd.conf.def
lrwxrwxrwx    1 root     root           32 Oct 28 06:00 upnp_all_format.conf -> /system/cfg/upnp_all_format.conf
lrwxrwxrwx    1 root     root           33 Oct 28 06:00 upnp_open_format.conf -> /system/cfg/upnp_open_format.conf
-rwxrwxrwx    1 root     root           53 Oct 27 13:43 upnpc_fail.sh
-rwxrwxrwx    1 root     root         1679 Oct 27 13:43 upnpc_start.sh
-rwxrwxrwx    1 root     root         1318 Oct 27 13:43 upnpc_stop.sh
-rwxrwxrwx    1 root     root          325 Oct 27 13:43 upnpc_success.sh
-rwxrwxr-x    1 root     root          222 Oct 27 13:43 usbplug.sh
-rw-r--r--    1 root     root           24 Oct 28 06:00 version.txt

Nothing here (I remove standard configs from the output). But notice that rc.sh script. We will have to look inside it some time later.

As a workaround solution we can write a script to be executed by cron, that will look at current temperature and when it goes high, it just turns the fan on, otherwise, turn it off.

Problem 1: How to find out the current temperature?

The web interface for NAS has a "System status" page under "Status" on the left side. It shows us the temperature of the whole system and for each hard drive.

HERE GOES THE IMAGE

It is time to find out how it is accomplished, so that we can use that approach in our script.

# cd /web/www/data
... ! web interface pages are most probably somewhere here...
# ls
CfgBackup                      function                       index.php                      server
GetValue.php                   home                           language                       status
JS                             iepngfix.htc                   loading.html                   style
account                        images                         login_error.php
disk                           imagesbutton-next-disable.gif  logout.php
empty.html                     index.html                     maintenance

Those directories look like the Web interface's tabs on the left. We need Status.

# cd status
# ls
GetValue.php        hd_status.php       printer_status.php  syslog.php          system_status.php

I think system_status.php is what we are looking for. There must be several occurrences of word "temperature" in this page.

# cat system_status.php | grep -in temp
19:$SYSTEM_TEMPERATURE_C=query("/system/system_information/temperature_c");
20:$SYSTEM_TEMPERATURE_F=query("/system/system_information/temperature_f");
21:$BATTERY=query("/temp/ups/percent");
22:$STATUS=query("/temp/ups/state");
28:var hdd_temp_info = new Array();
29:var hdd_temp_str = ""
32:var usb1 = "<? echo query("/temp/usbport1/type"); ?>";
33:var usb2 = "<? echo query("/temp/usbport2/type"); ?>";
34:var usb3 = "<? echo query("/temp/usbport3/type"); ?>";
35:var man1 = "<? echo query("/temp/usbport1/manufacturer"); ?>";
36:var man2 = "<? echo query("/temp/usbport2/manufacturer"); ?>";
37:var man3 = "<? echo query("/temp/usbport3/manufacturer"); ?>";
38:var pro1 = "<? echo query("/temp/usbport1/product"); ?>";
39:var pro2 = "<? echo query("/temp/usbport2/product"); ?>";
40:var pro3 = "<? echo query("/temp/usbport3/product"); ?>";
47:     if (hdd_temp_info.length != 0)
49:             hdd_temp_str = "";
50:             for (var i = 0; i < hdd_temp_info.length; i++)
52:                     if (hdd_temp_info[i]=="")
55:                     if (hdd_temp_str != "")
56:                             hdd_temp_str += ", ";
57:                     hdd_temp_str += "<?=$T_Slot ?> " + (i+1) + ": " + hdd_temp_info[i];
59:             getEle("hdd_temperature").innerHTML = hdd_temp_str;
60:             getEle("hdd_temperature_div").style.display = "";
63:     if (hdd_temp_str != "")
64:             getEle("hdd_temperature_div").style.display = "";
66:             getEle("hdd_temperature_div").style.display = "none";
78:     getEle("hdd_temperature_div").style.display = "none";
168:                                            <?=$T_System_Temperature ?>:
171:                                            <span class="blackcolor" id="sys_temperature"><?=$SYSTEM_TEMPERATURE_C ?>б╟C/<?=$SYSTEM_TEMPERATURE_F ?>б╟F</span>
174:                            <tr id="hdd_temperature_div">
176:                                            <?=$T_HD_TEMP ?>:
178:                                    <td id="hdd_temperature">

Lines 19 and 20 tell us how to read the system temperature.
Line 57 tells us how the "Hard Drive Temperature:" string is built.

Proceed with determining hard drive temperature.

# grep -rIn hdd_temp_info *
GetValue.php:62:                hdd_temp_info.length = 0;
GetValue.php:86:                                        echo "hdd_temp_info[".$idx."] = '".$temp_c."б╟C/".$temp_f."б╟F';";
GetValue.php:92:                        if ($temp_c=="") { echo "hdd_temp_info[".$idx."] = '';"; }
system_status.php:28:var hdd_temp_info = new Array();
system_status.php:47:   if (hdd_temp_info.length != 0)
system_status.php:50:           for (var i = 0; i < hdd_temp_info.length; i++)
system_status.php:52:                   if (hdd_temp_info[i]=="")
system_status.php:57:                   hdd_temp_str += "<?=$T_Slot ?> " + (i+1) + ": " + hdd_temp_info[i];

GetValue.php knows something, and we are going to read it. Here is the most important part:

  1. /* HDD temp */
  2.     hdd_temp_info.length = 0;
  3.     <?          
  4.     $idx=0;     
  5.     $t_idx=1;   
  6.     $hdd_temp_str="";
  7.     for("/RAID/disks/disk")
  8.     {           
  9.         $temp_c="";
  10.         if(query("size") > 0)
  11.         {   
  12.             $f_idx=1;
  13.             while($f_idx < 30) /*NOTE: 30 should be a safe figure, cyber*/
  14.             {
  15.                 $f_name=query("/RAID/disks/disk:".$t_idx."/s_f:".$f_idx."/name");
  16.                 if($f_name == "Temperature_Celsius")
  17.                 {
  18.                     $temp_c=query("/RAID/disks/disk:".$t_idx."/s_f:".$f_idx."/raw");
  19.                     $f_idx = 30;
  20.                 }
  21.                 $f_idx++;
  22.             }
  23.             if($temp_c!="")
  24.             {
  25.                 $temp_f = $temp_c*9/5+32;
  26.                 echo "hdd_temp_info[".$idx."] = '".$temp_c."б╟C/".$temp_f."б╟F';";
  27.             }
  28.             //echo "hdd_temp_str[".$idx."] = '".$temp_c."б╟C/".$temp_f."б╟F'";
  29.             //$hdd_temp_str=$hdd_temp_str.$t_idx.": "$temp_c."б╟C/".$temp_f."б╟F";
  30.     }   
  31.  
  32.     if ($temp_c=="") { echo "hdd_temp_info[".$idx."] = '';"; }
  33.     $t_idx++;
  34.     $idx++;
  35.     }           
  36.     ?>          
  37.     break;

Lines 75 and 78 are reading temperature. In fact, they iterate over all available parameters and find the one that is called Temperature_Celsius. As soon as it is found, its identifier is used to read the actual value.

Available parameters are "/RAID/disks/disk:/s_f:/name", where <t_id> is the number of a hard drive (starting from 1) and <f_id> from 1 to 29 inclusively (according to source code). Here is the list:

  1. Raw_Read_Error_Rate
  2. Throughput_Performance
  3. Spin_Up_Time
  4. Start_Stop_Count
  5. Reallocated_Sector_Ct
  6. Seek_Error_Rate
  7. Seek_Time_Performance
  8. Power_On_Hours
  9. Spin_Retry_Count
  10. Power_Cycle_Count
  11. Power-Off_Retract_Count
  12. Load_Cycle_Count
  13. Temperature_Celsius
  14. Reallocated_Event_Count
  15. Current_Pending_Sector
  16. Offline_Uncorrectable
  17. UDMA_CRC_Error_Count

You will learn the way I got it a little bit later. So, at this point I know that if I query the parameter with f_id equal to 13 I will get the temperature of my hard drives. As stated above the temperature of the system is determined by

query("/system/system_information/temperature_c");

and the temperature of hard drives:

query("/RAID/disks/disk:1/s_f:13/raw");
query("/RAID/disks/disk:2/s_f:13/raw");

Those string values are not paths. You can easily check it by searching these files. You won't find anything. At least, I didn't find anything :). Those strings look like a path to a parameter within some database. And this is where /etc/rc.sh will help us.

  1. # head -n40 /etc/rc.sh | tail -n10
  2. #-------------------------------------------------------------------------------
  3. /web/templates/initxmldb.sh
  4.  
  5. #LOG_MODULES=`xmldbc -g /system/log/memory_log_setting/status`
  6. #if [ "$LOG_MODULES" = "1" ] ; then
  7. #       insmod /usr/lib/modules/logkk.ko
  8. #       logdb &
  9. #fi
  10.  
  11. AUTO_PWR_RECOVERY=`xmldbc -g /system/apr`

The path in line 34 and in lint 40 look just like our strings. And xmldbc looks like a utility for managing some sort of xml database. I hope xmldbc has some usage help (unlike fancontrold).

# xmldbc -h
Usage: xmldbc version 2 [OPTIONS]
  -h                     show this help message.
  -H                     show version number.
  -v                     verbose mode.
  -i                     ignore external function (like runtime).
  -g {node path}         get value from {node path}.
  -s {node path} {value} set  {value} in {node path}.
  -d {node path}         delete {node path}.
  -l {XML file}          reload XML file to database.
  -D {XML file}          dump database to XML file.
  -S {unix socket}       specify unix socket name, default is /var/run/xmldb_sock
  -A {ephp file}         embeded php parse.
  -V {name=value}        variable for ephp.
  -x {command}           set extended get/set command.
  -t {tag:sec:command}   schedule a timer.
  -k {tag}               kill timers by tag.
  -X {node field}        sorting.
  -I {node field}        insert row.
  -p {node path} {file}  print node to file.

Great. That what we were looking for, -s and -g switch in particular. Let us make sure this does what we expect it to do.

# xmldbc -g /system/system_information/temperature_c
50
# xmldbc -g /RAID/disks/disk:1/s_f:13/name
Temperature_Celsius
# xmldbc -g /RAID/disks/disk:1/s_f:13/raw 
45
# xmldbc -g /RAID/disks/disk:1/s_f:13/val
133
# xmldbc -g /RAID/disks/disk:2/s_f:13/name
Temperature_Celsius
# xmldbc -g /RAID/disks/disk:2/s_f:13/raw 
47
# xmldbc -g /RAID/disks/disk:2/s_f:13/val
127

Hints:

  1. xmldbc config file: /system/cfg/config.xml
  2. xmldbc initialization: /web/templates/initxmldb.sh

Problem 2: Write the script.

Now that we know how to get the temperature and how to turn on the fan we can proceed to writing a script. It is up to you to decide when to change the fan speed but for this example I will just take some values of temperature.

# cd /usr/local
# touch my_fan_ctl.sh
# chmod 755 my_fan_ctl.sh
# cat << EOF > my_fan_ctl.sh
#!/bin/sh
 
STATE=off
 
T0=38
T1=40
T2=42
 
while [ 1 -eq 1 ]; do
    TEMPERATURE=`xmldbc -g /system/system_information/temperature_c`
 
    case $STATE in
        off)
                if [ $TEMPERATURE -gt $T0 ]; then
                    cpu_ctl fan_low
                    STATE="low"
                fi
                ;;
 
        low)
                if [ $TEMPERATURE -lt $T0 ]; then
                    cpu_ctl fan_stop
                    STATE="off"
                else
                    if [ $TEMPERATURE -gt $T2 ]; then
                        cpu_ctl fan_high
                        STATE="high"
                    fi
                fi
                ;;
 
        high)
                if [ $TEMPERATURE -lt $T1 ]; then
                    cpu_ctl fan_low
                    STATE="low"
                fi
                ;;
 
        *)
                ;;
    esac
 
    sleep 60
 
done
^D

Problem 3: Run it at NAS startup.

Add insert the following line:

/usr/local/my_fan_ctl.sh &

after the start of fancontrold.

Note: At this point this doesn't work. Because after reboot there will be no my_fan_ctl.sh script under /usr/local as well as all the modifications to rc.sh will be lost.

Problem 4: Get fancontrold working.

Needs source code investigation, fixing and cross compilation. This hasn't been done yet.

Helpful info. Well documented

Your blog was a fortuitous discovery! Thank you for documenting this. The same setup (xmldb) is used in a standard-issue VDSL2 router here in sunny England. Very useful :-)

cheers, asbokid

good job! thanks. ps. about

good job! thanks.
ps. about autostart

/mnt/md1/@optware 960392288 645232188 315160100 67% /opt

cd /etc/init.d
# cat S99setup
#!/bin/sh
cp -r /mnt/md1/tmp/.mc /
/opt/bin/my_fan_ctl.sh &

thats all.

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
ye_terda_: