#/bin/bash

######################################################################################################################
# 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 755 /usr/local/bin/fancontrol.sh
######################################################################################################################

######################################################################################################################
# To read internal drive temperature need to load the drivetemp module:
# Create file /etc/modules-load.d/drivetemp.conf with the text
#   drivetemp
# For initial load:
#   sudo modprobe drivetemp
######################################################################################################################

######################################################################################################################
# Create systemd service by writing /etc/systemd/system/fancontrol.service:
#
#   [Unit]
#   Description=Control iMac fans
# 
#   [Service]
#   Type=simple
#   ExecStart=/bin/bash /usr/local/bin/fancontrol.sh
# 
#   [Install]
#   WantedBy=multi-user.target
#
# Then
#   sudo systemctl enable fancontrol.service
#   sudo systemctl start fancontrol.service
######################################################################################################################

######################################################################################################################
# The optical drive fan will be controlled by the GPU die temperature.
# The hard disk fan will be controlled by the SSD and memory 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.
######################################################################################################################

# Initial sleep so devices are present. I'm sure there's a better way of doing this.
sleep 10

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

# 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"

# PSU temp
PSU_TEMP_SENSOR=/sys/devices/platform/applesmc.768/temp29_input
PSU_HIGH_TEMP="84"
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)))
MEM_FAN_STEP=$((($FAN_HDD_MAX - $FAN_HDD_MIN) / ($MEM_MAX_TEMP - $MEM_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))
  MEM_TEMP=$(<"${MEM_TEMP_SENSOR}"); MEM_TEMP=$(($MEM_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

  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

  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
  HDD_FAN=$MEM_FAN
  if [ "$SSD_FAN" -gt "$HDD_FAN" ] ; then HDD_FAN=$SSD_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

