#/bin/sh

######################################################################################################################
# Bash script to control the three fans for an iMac 10,1 (late 2009) Intel Core Duo.
# Save somewhere like /usr/local/bin/fancontrol.sh
#   sudo chmod +x /usr/local/bin/fancontrol.sh
######################################################################################################################

######################################################################################################################
# To read internal drive temperature need to load the drivetemp module and make sure the applesmc and coretemp
# modules are active before running the service.
#   echo -e "drivetemp\napplesmc\ncoretemp" | sudo tee /etc/modules-load.d/imac-temps.conf
# For initial load:
#   sudo modprobe drivetemp
######################################################################################################################

######################################################################################################################
# Create systemd service by writing /etc/systemd/system/fancontrol.service. Paste following into terminal and press
# return.
: '
cat <<EOF | sudo tee /etc/systemd/system/fancontrol.service
[Unit]
Description=Control iMac fans
Requires=systemd-modules-load.service
After=systemd-modules-load.service

[Service]
Type=simple
ExecStart=/bin/bash /usr/local/bin/fancontrol.sh

[Install]
WantedBy=multi-user.target
EOF
'
# Then
#   sudo systemctl enable fancontrol.service
#   sudo systemctl start fancontrol.service
# Check the service is running with:
#   sudo systemctl status fancontrol.service
######################################################################################################################

######################################################################################################################
# The optical drive fan will be controlled by the GPU die temperature and the DVD drive temp, whichever is higher
# within its limits.
# The hard disk fan will be controlled by the SSD, memory and LCD temperatures, whichever is higher within its limits.
# The CPU fan will be controlled by the power supply and CPU core temperatures, whichever is higher within its limits.
######################################################################################################################

######################################################################################################################
# *_HIGH_TEMP variables contain the temperature in Celsius when the fan RPM will start to rise above the minimum.
# *_MAX_TEMP  variables contain the temperature in Celsius when the fan will be running at maximum RPM.
######################################################################################################################

######################################################################################################################
# The sensors available on other iMac models will probably be different. Find the sensor names with:
#   for sensor in /sys/devices/platform/applesmc.768/*_label; do echo -n $sensor " "; cat "$sensor"; done
# then an Internet search to identify what they mean. This is a good resource:
#   https://github.com/acidanthera/VirtualSMC/blob/master/Docs/SMCSensorKeys.txt
######################################################################################################################

# GPU die temp
GPU_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp6_input
GPU_HIGH_TEMP="70"
GPU_MAX_TEMP="95"

# Optical drive
OPT_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp22_input
OPT_HIGH_TEMP="47"
OPT_MAX_TEMP="60"

# Memory temp
MEM_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp26_input
MEM_HIGH_TEMP="65"
MEM_MAX_TEMP="85"

# SSD temp
# If you use /sys/class/hwmon you might find removable drives plugged in at boot time instead of the internal drive
# so look for ATA devices instead. Find the sensor path using:
#   find /sys/devices -name hwmon -type d | grep ata1
SSD_TEMP_SENSOR_PATH="$(grep -l "drivetemp" /sys/devices/pci0000:00/0000:00:0b.0/ata1/host0/target0:0:0/0:0:0:0/hwmon/hwmon*/name)"
SSD_TEMP_SENSOR="${SSD_TEMP_SENSOR_PATH%/*}/temp1_input"
SSD_HIGH_TEMP="44"
SSD_MAX_TEMP="65"

# LCD temp
LCD_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp11_input
LCD_HIGH_TEMP="48"
LCD_MAX_TEMP="70"

# PSU temp
PSU_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp29_input
PSU_HIGH_TEMP="82"
PSU_MAX_TEMP="100"

# CPU temps
CPU_TEMP_SENSOR_PATH="$(grep -l "coretemp" /sys/class/hwmon/hwmon*/name)"
CPU1_TEMP_SENSOR="${CPU_TEMP_SENSOR_PATH%/*}/temp2_input"
CPU2_TEMP_SENSOR="${CPU_TEMP_SENSOR_PATH%/*}/temp3_input"
CPU_HIGH_TEMP="55"
CPU_MAX_TEMP="100"

# Fan devices
FAN_ODD_DEVICE=/sys/devices/platform/applesmc.768/fan1_
FAN_ODD_MIN=$(<"${FAN_ODD_DEVICE}min")
FAN_ODD_MAX=$(<"${FAN_ODD_DEVICE}max")
FAN_HDD_DEVICE=/sys/devices/platform/applesmc.768/fan2_
FAN_HDD_MIN=$(<"${FAN_HDD_DEVICE}min")
FAN_HDD_MAX=$(<"${FAN_HDD_DEVICE}max")
FAN_CPU_DEVICE=/sys/devices/platform/applesmc.768/fan3_
FAN_CPU_MIN=$(<"${FAN_CPU_DEVICE}min")
FAN_CPU_MAX=$(<"${FAN_CPU_DEVICE}max")

# Steps for fan when over the high temperature
GPU_FAN_STEP=$(( (FAN_ODD_MAX - FAN_ODD_MIN) / (GPU_MAX_TEMP - GPU_HIGH_TEMP) ))
OPT_FAN_STEP=$(( (FAN_ODD_MAX - FAN_ODD_MIN) / (OPT_MAX_TEMP - OPT_HIGH_TEMP) ))
MEM_FAN_STEP=$(( (FAN_HDD_MAX - FAN_HDD_MIN) / (MEM_MAX_TEMP - MEM_HIGH_TEMP) ))
LCD_FAN_STEP=$(( (FAN_HDD_MAX - FAN_HDD_MIN) / (LCD_MAX_TEMP - LCD_HIGH_TEMP) ))
SSD_FAN_STEP=$(( (FAN_HDD_MAX - FAN_HDD_MIN) / (SSD_MAX_TEMP - SSD_HIGH_TEMP) ))
PSU_FAN_STEP=$(( (FAN_CPU_MAX - FAN_CPU_MIN) / (PSU_MAX_TEMP - PSU_HIGH_TEMP) ))
CPU_FAN_STEP=$(( (FAN_CPU_MAX - FAN_CPU_MIN) / (CPU_MAX_TEMP - CPU_HIGH_TEMP) ))

# Set manual fan mode
echo 1 > "${FAN_ODD_DEVICE}manual"
echo 1 > "${FAN_HDD_DEVICE}manual"
echo 1 > "${FAN_CPU_DEVICE}manual"

# Main loop
while true; do

  GPU_TEMP=$(<"${GPU_TEMP_SENSOR}"); GPU_TEMP=$(( GPU_TEMP / 1000 ))
  OPT_TEMP=$(<"${OPT_TEMP_SENSOR}"); OPT_TEMP=$(( OPT_TEMP / 1000 ))
  MEM_TEMP=$(<"${MEM_TEMP_SENSOR}"); MEM_TEMP=$(( MEM_TEMP / 1000 ))
  LCD_TEMP=$(<"${LCD_TEMP_SENSOR}"); LCD_TEMP=$(( LCD_TEMP / 1000 ))
  SSD_TEMP=$(<"${SSD_TEMP_SENSOR}"); SSD_TEMP=$(( SSD_TEMP / 1000 ))
  PSU_TEMP=$(<"${PSU_TEMP_SENSOR}"); PSU_TEMP=$(( PSU_TEMP / 1000 ))
  CPU1_TEMP=$(<"${CPU1_TEMP_SENSOR}"); CPU1_TEMP=$(( CPU1_TEMP / 1000 ))
  CPU2_TEMP=$(<"${CPU2_TEMP_SENSOR}"); CPU2_TEMP=$(( CPU2_TEMP / 1000 ))

  # Work out fan speeds and test for sanity
  GPU_FAN=$(( (GPU_TEMP - GPU_HIGH_TEMP) * GPU_FAN_STEP + FAN_ODD_MIN ))
  if [ "$GPU_FAN" -lt "$FAN_ODD_MIN" ] ; then GPU_FAN=$FAN_ODD_MIN ; fi
  if [ "$GPU_FAN" -gt "$FAN_ODD_MAX" ] ; then GPU_FAN=$FAN_ODD_MAX ; fi

  OPT_FAN=$(( (OPT_TEMP - OPT_HIGH_TEMP) * OPT_FAN_STEP + FAN_ODD_MIN ))
  if [ "$OPT_FAN" -lt "$FAN_ODD_MIN" ] ; then OPT_FAN=$FAN_ODD_MIN ; fi
  if [ "$OPT_FAN" -gt "$FAN_ODD_MAX" ] ; then OPT_FAN=$FAN_ODD_MAX ; fi

  MEM_FAN=$(( (MEM_TEMP - MEM_HIGH_TEMP) * MEM_FAN_STEP + FAN_HDD_MIN ))
  if [ "$MEM_FAN" -lt "$FAN_HDD_MIN" ] ; then MEM_FAN=$FAN_HDD_MIN ; fi
  if [ "$MEM_FAN" -gt "$FAN_HDD_MAX" ] ; then MEM_FAN=$FAN_HDD_MAX ; fi

  LCD_FAN=$(( (LCD_TEMP - LCD_HIGH_TEMP) * LCD_FAN_STEP + FAN_HDD_MIN ))
  if [ "$LCD_FAN" -lt "$FAN_HDD_MIN" ] ; then LCD_FAN=$FAN_HDD_MIN ; fi
  if [ "$LCD_FAN" -gt "$FAN_HDD_MAX" ] ; then LCD_FAN=$FAN_HDD_MAX ; fi

  SSD_FAN=$(( (SSD_TEMP - SSD_HIGH_TEMP) * SSD_FAN_STEP + FAN_HDD_MIN ))
  if [ "$SSD_FAN" -lt "$FAN_HDD_MIN" ] ; then SSD_FAN=$FAN_HDD_MIN ; fi
  if [ "$SSD_FAN" -gt "$FAN_HDD_MAX" ] ; then SSD_FAN=$FAN_HDD_MAX ; fi

  PSU_FAN=$(( (PSU_TEMP - PSU_HIGH_TEMP) * PSU_FAN_STEP + FAN_CPU_MIN ))
  if [ "$PSU_FAN" -lt "$FAN_CPU_MIN" ] ; then PSU_FAN=$FAN_CPU_MIN ; fi
  if [ "$PSU_FAN" -gt "$FAN_CPU_MAX" ] ; then PSU_FAN=$FAN_CPU_MAX ; fi

  CPU1_FAN=$(( (CPU1_TEMP - CPU_HIGH_TEMP) * CPU_FAN_STEP + FAN_CPU_MIN ))
  if [ "$CPU1_FAN" -lt "$FAN_CPU_MIN" ] ; then CPU1_FAN=$FAN_CPU_MIN ; fi
  if [ "$CPU1_FAN" -gt "$FAN_CPU_MAX" ] ; then CPU1_FAN=$FAN_CPU_MAX ; fi

  CPU2_FAN=$(( (CPU2_TEMP - CPU_HIGH_TEMP) * CPU_FAN_STEP + FAN_CPU_MIN ))
  if [ "$CPU2_FAN" -lt "$FAN_CPU_MIN" ] ; then CPU2_FAN=$FAN_CPU_MIN ; fi
  if [ "$CPU2_FAN" -gt "$FAN_CPU_MAX" ] ; then CPU2_FAN=$FAN_CPU_MAX ; fi

  # Set the fan speeds
  ODD_FAN=$GPU_FAN
  if [ "$OPT_FAN" -gt "$GPU_FAN" ] ; then ODD_FAN=$OPT_FAN ; fi
  HDD_FAN=$MEM_FAN
  if [ "$SSD_FAN" -gt "$HDD_FAN" ] ; then HDD_FAN=$SSD_FAN ; fi
  if [ "$LCD_FAN" -gt "$HDD_FAN" ] ; then HDD_FAN=$LCD_FAN ; fi
  CPU_FAN=$PSU_FAN
  if [ "$CPU1_FAN" -gt "$CPU_FAN" ] ; then CPU_FAN=$CPU1_FAN ; fi
  if [ "$CPU2_FAN" -gt "$CPU_FAN" ] ; then CPU_FAN=$CPU2_FAN ; fi

  echo $ODD_FAN > "${FAN_ODD_DEVICE}output"
  echo $HDD_FAN > "${FAN_HDD_DEVICE}output"
  echo $CPU_FAN > "${FAN_CPU_DEVICE}output"

  sleep 5
done

