Created
September 12, 2024 19:12
-
-
Save eatnumber1/0734f4e006c2cd1122df9e0ffc34507f to your computer and use it in GitHub Desktop.
jbod-fancontrol - Control fans on Supermicro JBODs via the out-of-band SMCIPMITool
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/zsh | |
# Author: Russell Harmon | |
setopt err_exit err_return | |
setopt function_argzero no_unset warn_create_global | |
declare -a flag_help flag_idle_percent_mode flag_user flag_password flag_fan_speed_percent flag_verbose | |
zparseopts -D -F -K -- \ | |
{h,-help}=flag_help \ | |
-idle_percent_mode=flag_idle_percent_mode \ | |
{u,-user}:=flag_user \ | |
{p,-password}:=flag_password \ | |
{s,-fan_speed_percent}:=flag_fan_speed_percent \ | |
{v,-verbose}=flag_verbose \ | |
if (( $#flag_help )) || [[ $# -ne 1 ]]; then | |
cat <<- EOF | |
Usage: $0 [--idle_percent_mode] [-u|--user=<username>] [-p|--password=<password>] [-s|--fan_speed_percent=<speed>] [-v|--verbose] <hostname> | |
Configure a JBOD for a given fan speed. | |
Args: | |
hostname - The hostname of the JBOD to connect to. | |
Options: | |
-h, --help - Print the help message | |
-v, --verbose - Log what's being done | |
-u, --user <username> - The username to log in as | |
-p, --password <password> - The password to log in as | |
-s, --fan_speed_percent <speed> | |
The fan speed to set, as measured in percent values 0-100%. | |
--idle_percent_mode - Inputs to the JBOD are measured in percent idle. | |
EOF | |
exit 0 | |
fi | |
host=$1 | |
user=${flag_user[-1]} | |
pass=${flag_password[-1]} | |
declare -i desired_speed | |
desired_speed=1 | |
if (( $#flag_fan_speed_percent )); then | |
desired_speed=${flag_fan_speed_percent[-1]} | |
fi | |
#ip=$(dig +short $host) | |
ip=$host | |
if [[ -z $ip ]]; then | |
echo "Unknown host $host" >&2 | |
exit 1 | |
fi | |
# This script is based on notes from | |
# | |
# https://forums.servethehome.com/index.php?resources/supermicro-x9-x10-x11-fan-speed-control.20/ | |
# | |
# It uses the SMCIPMITool subcommand: | |
# | |
# ipmi raw <netFn> <cmd> [data] | |
function SMCIPMITool { | |
setopt local_options | |
if (( $#flag_verbose )); then | |
set -x | |
fi | |
command SMCIPMITool "$@" | |
} | |
function smc { | |
local lines | |
local -i rc | |
lines="$(SMCIPMITool "$ip" "$user" "$pass" "$@")" || rc=$? | |
if [[ $rc -ne 0 ]]; then | |
echo "Failure: SMCIPMITool [...] $*" >&2 | |
echo "$lines" >&2 | |
return $rc | |
fi | |
echo "$lines" | |
} | |
function ipmi { | |
smc ipmi "$@" | |
} | |
# Valid values for cmd 45 | |
declare -rA fan_modes=( | |
'standard' 0 | |
'full' 1 | |
'optimal' 2 | |
'heavy_io' 4 | |
) | |
function get_fan_mode { | |
local -a lines | |
() { | |
local IFS=$'\n' | |
# netFn 30 is fan commands. | |
# cmd 45 controls the fan setting | |
# subcommand 00 returns the current fan setting | |
lines=( $(ipmi raw 30 45 00) ) | |
} | |
if [[ ${lines[1]} != "00" ]]; then | |
echo "Unknown response from ipmi tool when getting fan mode: '${(j:\n:)lines[*]}'" >&2 | |
return 1 | |
fi | |
# Convert to an int to remove leading zeros | |
local -i code | |
code=${lines[2]} | |
local mode | |
mode=${(k)fan_modes[(r)${code}]} | |
if [[ -z $mode ]]; then | |
echo "Unknown fan mode: '$code'" >&2 | |
return 1 | |
fi | |
echo $mode | |
} | |
# Usage: set_fan_mode <mode> | |
function set_fan_mode { | |
local mode | |
mode=${fan_modes[$1]} | |
if [[ -z $mode ]]; then | |
echo "Unknown fan mode $1" >&2 | |
return 1 | |
fi | |
local mode_hex | |
mode_hex="$(printf '%02x' $mode)" | |
local lines | |
# netFn 30 is fan commands. | |
# cmd 45 controls the fan setting | |
# subcommand 01 sets the current fan setting | |
# the last arg determins which fan setting to set to | |
lines="$(ipmi raw 30 45 01 $mode_hex)" | |
if [[ "$lines" != "00" ]]; then | |
echo "Unknown response from ipmi tool when setting fan mode: '$lines'" >&2 | |
return 1 | |
fi | |
} | |
# Map a speed profile as measured in percent time busy in the range 0-100 (bigger is faster) | |
# to a value in percent time idle (bigger is slower) in the range 0-100. | |
function speed_busy_to_idle_percent { | |
declare -i busy_percent="$1" | |
echo $(( 100 - busy_percent )) | |
} | |
function speed_idle_to_busy_percent { | |
declare -i idle_percent="$1" | |
echo $(( 100 - idle_percent )) | |
} | |
# We can set the percent duty cycle with Cmd 70 | |
# The usage is: | |
# ipmi raw 30 70 66 01 <zone> <duty_cycle> | |
# Note: We don't know what 66 means. | |
# Note: 66 00 is to read | |
# Note: 66 01 is to write | |
# | |
# The zones are 00 or 01. On jbod60, 00 seems to control all the fans. | |
# The duty cycle is 00 to FF (min to max). | |
# get_fan_speed | |
# prints the fan speed as 1-100% | |
function get_fan_speed { | |
local -a lines | |
() { | |
local IFS=$'\n' | |
lines=( $(ipmi raw 30 70 66 00) ) | |
} | |
if [[ ${lines[1]} != "00" ]]; then | |
echo "Unknown response from ipmi tool when getting fan speed: '${(j:\n:)lines[*]}'" >&2 | |
return 1 | |
fi | |
# Convert to an int to remove leading zeros | |
speed="$(( 16#${lines[2]} ))" | |
if (( $#flag_idle_percent_mode )); then | |
speed="$(speed_idle_to_busy_percent $speed)" | |
fi | |
echo $speed | |
} | |
# set_fan_speed <speed> | |
# speed - A value 0-100% busy | |
function set_fan_speed { | |
local speed=$1 | |
if (( speed > 100 || speed < 1 )); then | |
echo "Invalid fan speed $speed" >&2 | |
return 1 | |
fi | |
if (( $#flag_idle_percent_mode )); then | |
speed="$(speed_busy_to_idle_percent $speed)" | |
fi | |
local speed_hex | |
speed_hex="$(printf '%02x' $speed)" | |
local lines | |
lines="$(ipmi raw 30 70 66 01 00 $speed_hex)" | |
if [[ "$lines" != "00" ]]; then | |
echo "Unknown response from ipmi tool when setting fan speed: '$lines'" >&2 | |
return 1 | |
fi | |
} | |
function is_powered_on { | |
local lines | |
lines=$(ipmi power status) | |
if [[ $lines = "Power is currently on." ]]; then | |
return 0 | |
elif [[ $lines = "Power is currently off." ]]; then | |
return 1 | |
else | |
echo "Unknown response from ipmi tool when checking power status: '$lines'" >&2 | |
exit 1 | |
fi | |
} | |
if ! is_powered_on; then | |
exit 0 | |
fi | |
local mode | |
mode="$(get_fan_mode)" | |
if [[ $mode != full ]]; then | |
(( $#flag_verbose )) && echo "Fan mode is $mode. Setting to 'full'." | |
set_fan_mode full | |
fi | |
local speed | |
speed="$(get_fan_speed)" | |
if [[ $speed -ne $desired_speed ]]; then | |
(( $#flag_verbose )) && echo "Fan speed is $speed. Setting to $desired_speed." | |
set_fan_speed $desired_speed | |
fi | |
# vim:noet |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment