#!/usr/bin/env python
# coding=UTF-8
#
# Copyright 2009 Harhalakis Stefanos <v13@v13.gr>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License or (at your option) version 3 or any later version
# accepted by the membership of KDE e.V. (or its successor approved
# by the membership of KDE e.V.), which shall act as a proxy 
# defined in Section 14 of version 3 of the license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# $Id: ati.py 951561 2009-04-09 16:40:14Z sharhalakis $
#

import os
import sys
import subprocess
import math

dirs=['/bin', '/usr/bin', '/usr/local/bin', '/sbin', '/usr/sbin',
  '/usr/local/sbin']

class AtiInfo:
  def __init__(self):
    self.temperature=0
    self.fanspeed=0

    self.gpuload=0
    self.speed_core=0
    self.speed_core_max=0
    self.speed_core_range_min=0
    self.speed_core_range_max=0
    self.speed_mem=0
    self.speed_mem_max=0
    self.speed_mem_range_min=0
    self.speed_mem_range_max=0

    self.detect_fan=True    # Is a fan detected? (auto-detection)

  def setFanDetect(self, value):
    print "No FAN!"
    self.detect_fan=value
  def hasFanDetect(self):
    return self.detect_fan
    
  def setGPULoad(self, value):
    self.gpuload=value
  def getGPULoad(self):
    return(self.gpuload)

  def setFanSpeed(self, value):
    self.fanspeed=value
  def getFanSpeed(self):
    return(self.fanspeed)

  def getTemperature(self):
    return(self.temperature)
  def setTemperature(self, value):
    self.temperature=value

  def getTemperature(self):
    return(self.temperature)
  # -----

  def setSpeedCore(self, value):
    self.speed_core=value

  def setSpeedCoreMax(self, value):
    self.speed_core_max=value

  def setSpeedCoreRange(self, min, max):
    self.speed_core_range_min=min
    self.speed_core_range_max=max

  def getSpeedCore(self):
    return self.speed_core

  def getSpeedCoreMax(self):
    return self.speed_core_max

  def getSpeedCoreRange(self):
    return((self.speed_core_range_min, self.speed_core_range_max))

  # -----

  def setSpeedMem(self, value):
    self.speed_mem=value

  def setSpeedMemMax(self, value):
    self.speed_core_max=value

  def setSpeedMemRange(self, min, max):
    self.speed_mem_range_min=min
    self.speed_mem_range_max=max

  def getSpeedMem(self):
    return self.speed_mem

  def getSpeedMemMax(self):
    return self.speed_mem_max

  def getSpeedMemRange(self):
    return((self.speed_mem_range_min, self.speed_mem_range_max))

# End of AtiInfo


class AtiCard:
  def __init__(self, index):
    self.aticonfig=None
    self.index=0

    self.min_temp=63
    self.max_temp=75 #80
    self.min_speed=20
    self.max_speed=100

    self.down_step=10
    self.up_step=20

    #self.temperature=0
    #self.fanspeed=0

    self.automatic=True
    self.custom_speed=60

    self.ai=AtiInfo()

    self.overclocking=False
    self.continuousoverclocking=False

    # A small deviation for avoiding changing tha speed all the time
    # It is a good thing to be half the up_step
    self.speed_deviation=10

    # Alarm temperature
    self.alarm_on=True
    self.alarm_temp=85
    self.alarm_alarmed=False	# If this is true then alarm was already shoot
				# Don't re-alarm

    self.ignore_fan=False	# User setting for fan control/support

  def check_aticonfig(self):
    for dir in dirs:
      t=dir+'/aticonfig'
      if os.path.exists(t):
	self.aticonfig=t
	break

    if self.aticonfig==None:
      print "aticonfig not found!"
      sys.exit(1)

    # print "Found aticonfig: ", self.aticonfig

  def reread(self):
    self._get_temperature()
    self._get_fanspeed()
    self._get_clocks()

  def _get_temperature(self):
    cmd=[self.aticonfig, "--adapter="+str(self.index), "--od-gettemperature"]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    t1=t[0].split("\n")

    temperature=-1

    # Find the Temperature line
    for i in t1:
      if i.find("Temperature")==-1:
	continue 
      t=i.split(" ")

      # Determine the temperature
      # Temperature is after the "-"
      last_e=''
      for e in t:
	if e=='':
	  continue

	if last_e=='-':
	  temperature=float(e)
	  break

	last_e=e

    self.ai.setTemperature(temperature)
    #self.temperature=temperature

    return(temperature)
  # End of get_temperature

  def _get_fanspeed(self):
    if not self.monitorFan():
      return(-1)

    cmd=[self.aticonfig, "--pplib-cmd", "get fanspeed "+str(self.index)]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    t1=t[0].split("\n")

    fanspeed=-1

    # Find the Fan Speed line
    for i in t1:
      if i.find("Result: Fan Speed")==-1:
	continue 
      t=i.split(" ")

      # The last character is % so remove it
      fanspeed=int(t[3][:-1])

    if fanspeed==-1:
      self.ai.setFanDetect(False)
      fanspeed=0

    self.ai.setFanSpeed(fanspeed)

    return(fanspeed)
  # End of get_fanspeed

  def _get_clocks(self):
    cmd=[self.aticonfig, "--adapter="+str(self.index), \
      "--od-getclocks"]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    #print t
    t1=t[0].split("\n")

    gpuload=-1
    speed_core=-1
    speed_core_max=-1
    speed_core_range_min=-1
    speed_core_range_max=-1
    speed_mem=-1
    speed_mem_max=-1
    speed_mem_range_min=-1
    speed_mem_range_max=-1

    # Parse Lines
    for i in t1:
      # Get words
      t=filter((lambda x: x!=''), i.split(' '))

      if i.find("GPU load")!=-1:
	gpuload=t[3][:-1]
      elif i.find("Current Clocks")!=-1:
	speed_core=t[3]
	speed_mem=t[4]
      elif i.find("Current Peak")!=-1:
	speed_core_max=t[3]
	speed_mem_max=t[4]
      elif i.find("Configurable Peak Range")!=-1:
	t1=t[4].split('-')
	t2=t[5].split('-')
	#print "t1: ", t1
	#print "t2: ", t2
	speed_core_range_min=t1[0][1:]
	speed_core_range_max=t1[1][:-1]
	speed_mem_range_min=t2[0][1:]
	speed_mem_range_max=t2[1][:-1]

    self.ai.gpuload=int(gpuload)
    self.ai.speed_core=int(speed_core)
    self.ai.speed_core_max=int(speed_core_max)
    self.ai.speed_core_range_min=int(speed_core_range_min)
    self.ai.speed_core_range_max=int(speed_core_range_max)
    self.ai.speed_mem=int(speed_mem)
    self.ai.speed_mem_max=int(speed_mem_max)
    self.ai.speed_mem_range_min=int(speed_mem_range_min)
    self.ai.speed_mem_range_max=int(speed_mem_range_max)

    #print "Gpuload: %d, Core: %d [%d-%d], Mem: %d [%d-%d]" % \
    #  (self.ai.gpuload,
    #  self.ai.speed_core, self.ai.speed_core_range_min,
#	self.ai.speed_core_range_max,
#      self.ai.speed_mem, self.ai.speed_mem_range_min,
#	self.ai.speed_mem_range_max)

  # End of get_fanspeed

  def get_temperature(self):
    return(self.ai.getTemperature())

  def get_fanspeed(self):
    return(self.ai.getFanSpeed())

  def get_gpuload(self):
    return(self.ai.getGPULoad())

  def get_atiinfo(self):
    return(self.ai)

  # Set the fanspeed
  # Return true on success, false on error
  def set_fanspeed(self, percentage):
    if not self.monitorFan():
      return(True)

    if percentage<0 or percentage>100:
      print "Bad fanspeed:", percentage
      sys.exit(1)

    cmd=[self.aticonfig, "--pplib-cmd", "set fanspeed %d %d" % 
      (self.index, percentage)]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    self._get_fanspeed()

    if t[0].find("command execution is Successful")==-1:
      return(False)

    return(True)
  # End of set_fanspeed

  def set_overclocking(self, v):
    self.overclocking=v

  def set_continuousoverclocking(self, v):
    self.continuousoverclocking=v

  # Set overclocking values
  # Return true on success, false on error
  def set_overclock(self, core, mem):
    if core < self.ai.speed_core_range_min or \
	core > self.ai.speed_core_range_max or \
	mem < self.ai.speed_mem_range_min or \
	mem > self.ai.speed_mem_range_max:
      print "Bad core (%d) or mem (%d) speed" % (core, mem)
      return(False)

    if core==self.ai.speed_core_max and \
	mem==self.ai.speed_mem_max:
      # print "No need to change speeds"
      return(True)

    cmd=[self.aticonfig, "--od-enable"]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    if t[0].find("enabled")==-1:
      print "Could not enable overdrive"
      return(False)

    cmd=[self.aticonfig, "--adapter="+str(self.index),
      "--od-setclocks=%d,%d"% (core, mem)]
    proc=subprocess.Popen(cmd, stdout=subprocess.PIPE)
    t=proc.communicate()

    if t[0].find("failed")!=-1:
      print "Failed to set speed"
      print t[0]
      return(False)

    return(True)
  # End of set_overclock()

  def init(self):
    self.check_aticonfig()
    self._get_fanspeed()
    self.initial_speed=self.get_fanspeed()

  def finish(self):
    curspeed=self._get_fanspeed()
    if curspeed < self.initial_speed:
      self.set_fanspeed(self.initial_speed)
      print "Fan speed restored to %d%%" % self.initial_speed
    elif curspeed > self.initial_speed:
      print "Current fan speed is greater that the initial."
      print "Keeping the current fan speed for safety reasons."

  def set_parameters(self, min_temp, max_temp, min_speed, max_speed):
    self.min_temp=min_temp
    self.max_temp=max_temp
    self.min_speed=min_speed
    self.max_speed=max_speed

  def run_once(self):
    self.reread()
    #temp=self.get_temperature()
    temp=self.ai.getTemperature()
    #speed=self.get_fanspeed()
    speed=self.ai.getFanSpeed()
 
    speed_range=self.max_speed - self.min_speed
    temp_range=self.max_temp - self.min_temp

    # Step #1 - Determine required speed
    t=(temp-self.min_temp) * speed_range / temp_range
    if t<0:
      t=0

    t=t+self.min_speed

    if t>100:
      t=100

    # Step #2 - Adjust speed if needed
    # Use a deviation for avoiding changing the speed all the time
    #print "Temperature: %.1f\nSpeed: %d\nTarget Speed: %d\n" % \
    #  (temp, speed, t)
    self.target_speed=t

    # Don't do anything on manual speed
    if not self.getAutomatic():
      return

    if t < (speed-self.speed_deviation):
      # Need speed increase
      self.set_fanspeed(speed-self.down_step)
      #print "Decrease"
    elif t > (speed+self.speed_deviation):
      # Need speed decrease
      self.set_fanspeed(speed+self.up_step)
      #print "Increase"
  # End of run_once()
 
  # Automatic speed control
  def setAutomatic(self, value):
    self.automatic=value

  # Set the custom speed
  def setCustomSpeed(self, value):
    if (value<0):
      print "value < 0 !!!!"
      value=10

    if (value>100):
      print "value >100 !!!!"
      value=100

    self.custom_speed=value
    #print self.custom_speed, "kokoko"

  def applyCustomSpeed(self):
    self.set_fanspeed(self.custom_speed)

  def getAutomatic(self):
    return self.automatic

  def getCustomSpeed(self):
    return self.custom_speed

  def getInitialSpeed(self):
    return self.initial_speed

  def doInitialOverclocking(self, core, mem):
    if not self.overclocking or not self.continuousoverclocking:
      #print "Avoiding initial overclocking because overclocking is disabled"
      return

    print "Initial overclocking: %d, %d" % (core, mem)
    self.reread()
    self.set_overclock(core, mem)

  # ------

  def getAlarmOn(self):
    return self.alarm_on

  def getAlarmTemp(self):
    return self.alarm_temp

  def setAlarmOn(self, on):
    self.alarm_on=on

  def setAlarmTemp(self, temp):
    self.alarm_temp=temp

  def isAlarm(self):
    # Get current alarm state
    cur_temp=self.get_temperature()
    if cur_temp >= self.alarm_temp:
      is_alarm=True
    else:
      is_alarm=False

    # print self.get_temperature(), self.alarm_temp

    # If there was already an alarm
    if self.alarm_alarmed:
      # Forget/Cancel it if not needed any more
      if not is_alarm:
	# Use a threshold of 2 degrees to avoid continuous alarms
	if cur_temp < self.alarm_temp-2:
	  self.alarm_alarmed=False
      # Don't re-alarm
      ret=False
    else:
      if is_alarm:
	self.alarm_alarmed=True
	ret=True
      else:
	ret=False

    return ret
  # End of isAlarm()

  def setFanIgnore(self, value):
    self.ignore_fan=value
  def getFanIgnore(self):
    return self.ignore_fan

  # Return true/false indicating that fan control should be disabled
  def monitorFan(self):
    if not self.ai.hasFanDetect():
      return False
    return not self.getFanIgnore()

# vim: set ts=8 sts=2 sw=2 noet formatoptions=r ai nocindent:

