#!/usr/bin/php
<?php
##############################
######  DEAMON SECTION  ######
##############################
require_once '/usr/local/emhttp/plugins/ipmi/include/ipmi_options.php';
require_once '/usr/local/emhttp/plugins/ipmi/include/ipmi_settings_fan.php';
require_once '/usr/local/emhttp/plugins/ipmi/include/ipmi_drives.php';
require_once '/usr/local/emhttp/plugins/dynamix/include/Helpers.php';

extract(parse_plugin_cfg('dynamix',true));
$debug = FALSE;

set_time_limit(0);
$prog     = pathinfo(__FILE__, PATHINFO_FILENAME);
$lockfile = "/var/run/{$prog}.pid";
$log      = '/var/log/ipmifan';
$service  = __FILE__;
openlog($prog, LOG_PID | LOG_PERROR, LOG_LOCAL0);

$usage = <<<EOF

Process settings in ipmi plugin fan config.
Control fans based on config values and [options].

Usage: $prog [options]

  -a, --auto       set fans to auto
      --full       set fans to full speed
  -q, --quiet      suppress all messages
      --debug      turn on debugging
      --daemon     run in the background
      --help       display this help and exit
      --quit       stop daemon if running
      --version    output version information and exit


EOF;

$shortopts = 'adfq';
$longopts = [
    'auto',
    'daemon',
    'debug',
    'full',
    'help',
    'quit',
    'quiet',
    'version'
];
$args = getopt($shortopts, $longopts);

if (array_key_exists('help', $args)) {
    echo $usage.PHP_EOL;
    exit(0);
}

if (array_key_exists('version', $args)) {
    echo 'IPMI Fan Control: 1.0'.PHP_EOL;
    exit(0);
}

$arga   = (array_key_exists('a', $args) || array_key_exists('auto', $args));
$argf   = (array_key_exists('full', $args));
$argq   = (array_key_exists('q', $args) || array_key_exists('quiet', $args));
$debug  = (array_key_exists('debug', $args));
$daemon = (array_key_exists('d', $args) || array_key_exists('daemon', $args));
$quit   = (array_key_exists('quit', $args));

$raw  = (empty($board_json)) ? '' : $board_json[$board]['raw'];

if(file_exists($lockfile)){
    $lock_pid = file($lockfile, FILE_IGNORE_NEW_LINES)[0];
    $pid_running = preg_replace("/\s+/", "", shell_exec("ps -p $lock_pid | grep $lock_pid"));
    if($pid_running){
        if($quit){
            fanlog('Stopping Fan Control');
            syslog(LOG_INFO, "killing daemon with PID [$lock_pid]");
            exec("kill $lock_pid");
            unlink($lockfile);
            autofan();
            exit(0);
        }else{
            echo "$prog is already running [$lock_pid]".PHP_EOL;
            exit(0);
        }
    }else{
        if($quit){
            echo "$lock_pid is not currently running".PHP_EOL;
            unlink($lockfile);
            exit(0);
        }else
            file_put_contents($lockfile, getmypid());
    }
}else{
    if($quit){
        echo "$prog not currently running".PHP_EOL;
        exit(0);
    }else
        file_put_contents($lockfile, getmypid());
}

if($arga){
        autofan();
        exit(0);
}

if($argf){
        fullfan();
        exit(0);
}

if (!$board_file_status) {
    $msg = "Your $board motherboard is not supported or setup yet";
    logger($msg, $argq);
    exit(1);
}

if($daemon){
    exec("php $service 1>/dev/null ".($debug ? "":"2>&1 ")."&");
    syslog(LOG_INFO, "process started. To terminate it, type: $prog --quit");
    exit(0);
}

#############################################
# ASRock
# ipmi-raw 00 3a 01 00 00 00 00 00 00 00 00
# ipmi-raw 00 3a 01 AA BB CC DD EE FF GG HH
# 00 = smartfan mode
# 01 - 64  = 1% - 100%
#############################################

#############################################
# ASRock Dual Socket
# ipmi-raw 00 3a 01 CPU_1_OVERRIDE CPU_1 REAR_1 FRONT_1 FRONT_2 FRONT_3
# ipmi-raw 00 3a 11 CPU_2_OVERRIDE CPU_2 REAR_2 FRONT_4
# ipmi-raw 00 3a 01 00 AA BB CC DD EE
# ipmi-raw 00 3a 11 00 AA BB CC
#############################################

#############################################
# ASRock 570
# ipmi-raw 00 3a d6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
# ipmi-raw 00 3a d6 AA BB CC DD EE FF GG HH II JJ KK LL MM NN OO PP
# 00 = smartfan mode
# 01 - 64  = 1% - 100%
#############################################

#############################################
# Supermicro X10/X11
# ipmi-raw 00 30 70 66 01 00 64
# ipmi-raw 00 30 70 66 AA BB CC
#
# AA
# 00 - Get value
# 01 - Set value
#
# BB
# 00 - FAN 1/2/3/4 or CPU_FAN1/2
# 01 - FAN A or SYS_FAN1/2/3
#
# CC
# 00 to 64 - Set Speed (0-64)
#############################################

#############################################
# Supermicro X9
# ipmi-raw 00 30 91 5A 3 00 FF
# ipmi-raw 00 30 91 5A 3 BB CC
#
# BB
# 10 - FAN 1/2/3/4
# 11 - FAN A
#
# CC
# 00 to FF - Set Speed (0-255)
#############################################

#############################################
# Dell
# raw'    => '30 30 02 FF', # + value 01-64 for % FF is all or ID
#'auto'   => '30 30 01 00',
#'manual' => '30 30 01 01',
#'full'   => '30 30 02 FF 64',
# 01 - 64  = 1% - 100%
#############################################

##############################
###### FUNCTION SECTION ######
##############################

/* logger*/
function logger($msg, $quiet = false) {
    syslog(LOG_INFO, $msg);
    if (!$quiet)
        echo PHP_EOL.$msg.PHP_EOL;
}

/* logfile */
function fanlog($msg) {
    global $log;
    $msg = date('Y-m-d H:i:s').' '.$msg.PHP_EOL;
    file_put_contents($log,$msg,FILE_APPEND);
}

/* debug */
function debug($m){
  global $prog, $debug;
  if($debug){
    $STDERR = fopen('php://stderr', 'w+');
    fwrite($STDERR, $m.PHP_EOL);
    fclose($STDERR);
  }
}

/* auto fan */
function autofan() {
    global $board_json, $board, $board_model, $cmd_count, $fanopts;
    fanlog('Setting fans to auto');
    $cmd = '';
    switch($board) {
        case "ASRock":
        case "ASRockRack":
            //if board is ASRock or AsRockRack
            for($i = 0; $i <= $cmd_count; $i++){
                $board0 = ($i == 0) ? $board : $board.$i;
                $raw    = $board_json[$board0]['raw'];
                $auto   = $board_json[$board0]['auto'];
                $cmd   .= "ipmi-raw $raw $auto $fanopts 2>&1 >/dev/null &";
            }
            break;
        case "Supermicro":
            //if board is Supermicro
            $raw    = $board_json[$board]['raw'];
            $auto = $board_json[$board]['auto'];
            $mode = trim(shell_exec("ipmi-raw 00 30 45 00 | awk -F '45 00 ' '{print $2}'"));
            $cmd  = "ipmi-raw $auto $mode $fanopts 2>&1 >/dev/null &";
            break;
        case "Dell":
            //if board is Dell
            $raw = $board_json[$board]['raw'];
            $auto = $board_json[$board]['auto'];
            $cmd   = "ipmi-raw $auto $fanopts 2>&1 >/dev/null &";
            break;
    }
    shell_exec($cmd);
}

/* manual fan */
function manualfan() {
    global $board_json, $board, $board_model, $cmd_count, $fanopts;
    fanlog('Setting fans to manual');
    $cmd = '';
    switch($board) {
        case "ASRock":
        case "ASRockRack":
            //if board is ASRock or AsRockRack
            for($i = 0; $i <= $cmd_count; $i++){
                $board0 = ($i == 0) ? $board : $board.$i;
                $raw    = $board_json[$board0]['raw'];
                $auto   = $board_json[$board0]['auto'];
                $cmd   .= "ipmi-raw $raw $auto $fanopts 2>&1 >/dev/null &";
            }
            break;
        case "Supermicro":
            //if board is Supermicro
            $raw    = $board_json[$board]['raw'];
            $auto = $board_json[$board]['auto'];
            $mode = trim(shell_exec("ipmi-raw 00 30 45 00 | awk -F '45 00 ' '{print $2}'"));
            $cmd  = "ipmi-raw $auto $mode $fanopts 2>&1 >/dev/null &";
            break;
        case "Dell":
            //if board is Dell
            $raw    = $board_json[$board]['raw'];
            $manual = $board_json[$board]['manual'];
            $cmd   = "ipmi-raw $manual $fanopts 2>&1 >/dev/null &";
            break;
    }
    shell_exec($cmd);
    sleep(10) ;
}

/* full speed fan */
function fullfan() {
    global $board_json, $board, $board_model, $cmd_count, $fanopts;
    fanlog('Setting fans to full speed');
    switch($board) {
        case "ASRock":
        case "ASRockRack":
            //if board is ASRock
            $cmd = '';
            for($i = 0; $i <= $cmd_count; $i++){
                $board0 = ($i == 0) ? $board : $board.$i;
                $raw    = $board_json[$board0]['raw'];
                $full   = $board_json[$board0]['full'];
                $cmd   .= "ipmi-raw $raw $full $fanopts 2>&1 >/dev/null &";
            }
            break;
        case "Supermicro":
            //if board is Supermicro
            $full = $board_json[$board]['full'];
            $cmd  = "ipmi-raw $full $fanopts 2>&1 >/dev/null &";
            break;
        case "Dell":
            //if board is Dell
            $raw    = $board_json[$board]['raw'];
            $full = $board_json[$board]['full'];
            $cmd   = "ipmi-raw $full $fanopts 2>&1 >/dev/null &";
            break;  
    }
    echo shell_exec($cmd);
    sleep(10);
}

/* get highest temp of hard drives */
function get_highest_temp(){
    global $hddignore;
    $ignore = array_flip(explode(',', $hddignore));

    //get UA devices
    $ua_json = '/var/state/unassigned.devices/hdd_temp.json';
    $ua_devs = file_exists($ua_json) ? json_decode(file_get_contents($ua_json), true) : [];

    //get all hard drives
    $disksini = parse_ini_file('/var/local/emhttp/disks.ini',true);
    $devsini = parse_ini_file('/var/local/emhttp/devs.ini',true);
    if ($devsini) $hdds = array_merge($disksini, $devsini); else $hdds = $disksini ;
    unset($hdds['flash']);

    $highest_temp = 0;
    foreach ($hdds as $hdd) {

        if (!array_key_exists($hdd['id'], $ignore)) {
            $temp = 0;
            if(array_key_exists('temp', $hdd))
                $temp = $hdd['temp'];
            elseif(!empty($ua_devs)){
                $ua_key = "/dev/".$hdd['device'];
                $temp = (array_key_exists($ua_key, $ua_devs)) ? $ua_devs[$ua_key]['temp'] : 0;
            }else
                $temp = preg_replace("/\s+/", "", shell_exec("smartctl -A -n standby /dev/{$hdd} 2>/dev/null| grep -m 1 -i Temperature_Cel | awk '{print $10}'"));

            $highest_temp = ($temp > $highest_temp) ? $temp : $highest_temp;
        }
    }
    debug("Highest temp is {$highest_temp}ºC");
    return $highest_temp;
}

/* get fan and temp sensors */
function ipmi_fan_sensors() {
    global $ipmi, $fanopts, $hdd_temp;

    // return empty array if no ipmi detected or network options
    if(!$ipmi && empty($fanopts))
        return [];

    $cmd = "/usr/sbin/ipmi-sensors --comma-separated-output --no-header-output --interpret-oem-data $fanopts 2>/dev/null";
    $return_var=null ;
    exec($cmd, $output, $return_var);

    // return empty array if error
    if($return_var)
        return [];

    // add hard drive temp as a sensor
    $output[] = "99,HDD Temperature,Temperature, $hdd_temp,C,Ok";

    // key names for ipmi sensors output
    $keys = ['ID', 'Name', 'Type', 'Reading', 'Units', 'Event'];
    $sensors = [];

    foreach($output as $line){

    /// add sensor keys as keys to ipmi sensor output
        $sensor_raw = explode(",", $line);
        $size_raw = sizeof($sensor_raw);
        $sensor = ($size_raw < 6) ? []: array_combine($keys, array_slice($sensor_raw,0,6,true));

        if ($sensor['Type'] === 'Temperature' || $sensor['Type'] === 'Fan')
            $sensors[$sensor['ID']] = $sensor;
    }
    return $sensors; // sensor readings
    unset($sensors);
}

##############################
#####  PROGRAM SECTION  ######
##############################

$MD5          = md5_file($fancfg_file);
$hdd_temp     = get_highest_temp();
$sensors      = ipmi_fan_sensors();
$ipmipoll     = ($fanpoll <= $hddpoll) ? $fanpoll : $hddpoll;
$fan_count    = $fanpoll * 10;
$hdd_count    = $hddpoll * 10;
$curent_hex   = '';
$current_hex2 = '';

fanlog('Starting Fan Control');
fanlog("Board: $board Board Model: $board_model");

if($board == 'Supermicro'){
    fanlog("SM Board selection: $smboard_model");
    fullfan();
}
if($board == 'Dell'){
    manualfan();
}

while(TRUE){ while(TRUE){
####  DO YOUR STUFF HERE  ####

    /* Refresh hdds and hdd temp if hdd poll time is expired */
    if ($hdd_count <= 0) {
        $hdd_count = $hddpoll * 10;
        $hdd_temp  = get_highest_temp();
        $sensors['99']['Reading'] = $hdd_temp;
    }
    /* Get sensor info if fan sensor poll time is expired */
    if ($fan_count <= 0) {
        $fan_count = $fanpoll * 10;
        $sensors = ipmi_fan_sensors();
    }

    for($i = 0; $i <= $cmd_count; $i++){
        $board0 = ($i == 0) ? $board : $board.$i;
        $raw    = $board_json[$board0]['raw'];
        $fans   = $board_json[$board0]['fans'];
        $hex    = '';
        $msg    = 'Fan:Temp';
        $cmd    = '';
        $exec   = false;
        $fan_count  = 0;
       # var_dump($fans,$raw) ;

        foreach($fans as $fan => $value){

            $temp = isset($fancfg["TEMP_$fan"]) ? $fancfg["TEMP_$fan"] : '';
         #   echo "temp:".$temp;
            if(!empty($temp)) {
                $templo  = (isset($fancfg["TEMPLO_$fan"])) ? intval($fancfg["TEMPLO_$fan"])  : 30;
                $temphi  = (isset($fancfg["TEMPHI_$fan"])) ? intval($fancfg["TEMPHI_$fan"])  : 45;
                $fanmin  = (isset($fancfg["FANMIN_$fan"])) ? intval($fancfg["FANMIN_$fan"])  : 16;
                $fanmax  = (isset($fancfg["FANMAX_$fan"])) ? intval($fancfg["FANMAX_$fan"])  : $range;
                $temploo  = (isset($fancfg["TEMPLOO_$fan"])) ? intval($fancfg["TEMPLOO_$fan"])  : 30;
                $temphio  = (isset($fancfg["TEMPHIO_$fan"])) ? intval($fancfg["TEMPHIO_$fan"])  : 45;
                $fanmino  = (isset($fancfg["FANMINO_$fan"])) ? intval($fancfg["FANMINO_$fan"])  : 16;
                $fanmaxo  = (isset($fancfg["FANMAXO_$fan"])) ? intval($fancfg["FANMAXO_$fan"])  : $range;
                $reading = floatval($sensors[$temp]['Reading']);
                $name    = htmlspecialchars($sensors[$temp]['Name']);
                $use_override = false;
                if ($temp = "99" && $reading <= 0) {
                    if (isset($fancfg["TEMPHDD_$fan"]) &&  $fancfg["TEMPHDD_$fan"] != 0) {
                    $temp=    $fancfg["TEMPHDD_$fan"] ;
                    $reading = floatval($sensors[$temp]['Reading']);
                    $name    = "HDD Spundown using ".htmlspecialchars($sensors[$temp]['Name']);
                    $use_override = true;
                    }  
                }
                if ($use_override) {
                    if ($reading <= $temploo){
                        $pct = 0;
                        $pwm = 1;
                    }elseif ($reading >= $temphio){
                        $pct = 1;
                        $pwm = $range;
                    }else{
                        $pct = ($reading-$temploo)/($temphio-$temploo)*($fanmaxo-$fanmino)/$range + $fanmino/$range;
                        $pwm = round(($pct)*($range/4))*4; //round
                    }
                    //impose fan lower limit
                    if ($pwm <= $fanmino){
                        $pwm  = $fanmino;
                        $pct  = $fanmino/$range;
                    }

                    //impose fan upper limit
                    if ($pwm >= $fanmaxo){
                        $pwm  = $fanmaxo;
                        $pct  = $fanmaxo/$range;
                    }
                } else {
                    if ($reading <= $templo){
                        $pct = 0;
                        $pwm = 1;
                    }elseif ($reading >= $temphi){
                        $pct = 1;
                        $pwm = $range;
                    }else{
                        $pct = ($reading-$templo)/($temphi-$templo)*($fanmax-$fanmin)/$range + $fanmin/$range;
                        $pwm = round(($pct)*($range/4))*4; //round
                    }
                    //impose fan lower limit
                    if ($pwm <= $fanmin){
                        $pwm  = $fanmin;
                        $pct  = $fanmin/$range;
                    }

                    //impose fan upper limit
                    if ($pwm >= $fanmax){
                        $pwm  = $fanmax;
                        $pct  = $fanmax/$range;
                    }
                }
                //convert pwm to hexadecimal
                if($range == 255){
                    $pwm = dechex($pwm);
                }

                //pad pwm to 2 places
                $pwm = str_pad($pwm, 2, '0', STR_PAD_LEFT);

                //add fan, value, temp sensor name and reading
                $pct  = str_pad(round($pct*100), 2, ' ', STR_PAD_LEFT);
                if ($reading <= 0) $mytemp = "Spundown" ; else $mytemp = str_replace([" ","&#8201;&#176;"],["",""],my_temp($reading));
                $msg .= ", {$fan}({$pct}%):".str_replace('Temperature','Temp',$name)."($mytemp)";

           }else{
                $pwm ='00';
           }

           switch($board) {
            case "ASRock":
            case "ASRockRack":
                switch($board_model) {
                    case 'EP2C612 WS':
                        $hex .= " $pwm";
                        break;
                    default:
                        //add value to hex for Asrock boards
                        if($cmd_count !== 0 && $fan_count == 0){
                            // set value for CPU fans for dual sockets
                            if($pwm == '00'){
                                $hex .= ' 00 00';
                            }else{
                                $hex .= " 01 $pwm";
                            }

                        }else{
                            $hex .= " $pwm";
                        }
                        break;
                }
                break;
            case "Supermicro":
                //create command for Supermicro
                $cmd_str = "ipmi-raw $raw $value $pwm $fanopts 2>&1; ";
                //check if new pwm for FAN1234 then add to cmd string
                if(($value == '00' || $value == '10') && $current_pwm !== $pwm && $pwm !== '00'){
                    $cmd .= $cmd_str;

                    //set current value for next loop
                    $current_pwm  = $pwm;
                    $exec = true;
                }
                // check if new pwm for FANA then add to cmd string
                if(($value == '01' || $value == '11') && $current_pwm2 !== $pwm && $pwm !== '00'){
                    $cmd .= $cmd_str;

                    //set current value for next loop
                    $current_pwm2 = $pwm;
                    $exec = true;
                }
                break;
            case "Dell":
               # echo $pwm;
               # $pwm = "05" ;
                #fanlog($msg);
                if(($value == '00' || $value == '10') && $current_pwm !== $pwm && $pwm !== '00'){
                    $cmd = "ipmi-raw $raw $pwm $fanopts 2>&1; ";
                    $current_pwm  = $pwm;
                    
                    $exec = true;
                }
                break;
            }
            $fan_count++;
        }
        switch($board) {
            case "ASRock":
            case "ASRockRack":
            //compare last value to new value for Asrock boards
            $cmd_str = "ipmi-raw $raw$hex $fanopts 2>&1; ";

            if($i == 0){

                if($current_hex !== $hex){
                    $cmd = $cmd_str;

                    //set current value for next loop
                    $current_hex = $hex;
                    $exec = true;
                }
            }else{

                if($current_hex2 !== $hex){
                    $cmd = $cmd_str;

                    //set 2nd current value for next loop
                    $current_hex2 = $hex;
                    $exec = true;
                }
            }
            break;
        }

        // execute command if exec set
        if($exec){
            shell_exec($cmd);
            #fanlog($cmd);
            if($debug)
                fanlog($cmd);

            //log changes
            fanlog($msg);
        }
    }
    /* print variable */
    $defined_vars = get_defined_vars();

    foreach (array('_GET','_POST','_COOKIE','_FILES','argv','argc','_SERVER') as $i)
        unset($defined_vars[$i]);

    if($debug)
        debug("\nDECLARED VARIABLES:\n".print_r($defined_vars, true));

    unset($defined_vars);

    $time1 = time();

    for ($i=0; $i < $ipmipoll ; $i++) {
        sleep(10);
        $fan_count = $fan_count - 10;
        $hdd_count = $hdd_count - 10;

        $MD5_new = md5_file($fancfg_file);
        if((file_exists($fancfg_file)) && ($MD5_new != $MD5)){
            $msg = 'fan control config file updated, reloading settings';
            $fancfg = parse_ini_file($fancfg_file);
            logger($msg, $argq);
            $MD5 = $MD5_new;
            fanlog($msg);
            break;
        }
    }
    debug("Sleep ".(time()-$time1)." seconds.");

######  END OF SECTION  ######
    };
};
?>
