Mantener la temperatura adecuada en un portátil Dell con Linux

Públicado el mar 12 junio 2012

Los que tengáis o hayáis tenido un portátil Dell, sabréis que es posible controlar la velocidad de funcionamiento de sus ventiladores de forma manual. Existen aplicaciones para Windows, pero también es posible hacerlo desde Linux con el paquete i8kutils creado por Massimo Dal Zotto. Este paquete incluye un modulo del kernel i8k que necesita ser cargado al inicio y una serie de utilidades para controlar el ventilador e informar de la temperatura y otros valores de la BIOS.

Este paquete fue creado originalmente para el portátil Dell Inspiron 8000, de ahí su nombre. Esta utilidad aprovecha el modo SMM de la BIOS que estaba presente en los modelos Inspiron para controlar la velocidad de los ventiladores (algo que también permiten otras marcas como Toshiba y Lenovo, para las que también hay algunas utilidades). Dell también ofrece el soporte de SMM BIOS en otras gamas y portátiles, como los de la serie Latitude o XPS. Aunque está utilidad no funciona en algunos modelos.

Esta utilidad funciona bajo la línea de comandos, pero al amparo del modulo del kernel surgieron varias aplicaciones, principalmente applets para el panel de Gnome 2, que permitían un control gráfico de los ventiladores y su temperatura. Este control podía ser automático o manual. Yo personalmente me decanté en su momento por emplear Conky para mostrar las velocidades y la temperatura y emplear un script, i8kapplet por Wheelspin, para controlar automáticamente la temperatura dentro de unos rangos.

i8kfans.py

Este era un script bash que lleva dándome servicio mucho años (el portátil ya tiene sus siete años) pero que por la forma que tiene de controlar cuando se debe subir/bajar la velocidad de los ventiladores, provocaba que se sucediesen de vez en cuando continuos acelerones y frenazos en los mismos. Esto al principio no me disgustaba, pero con los años los ventiladores hacen cada vez más ruido y si bien el sonido constante a alta velocidad es ligeramente molesto, esos cambios bruscos de velocidad se me han vuelto insoportables. Y dado que el portátil se acerca al final de su vida útil, sustituir los ventiladores, aunque es la solución adecuada, no lo veo económicamente rentable.

Yo sabía que el problema por el que esto sucedía es porque la BIOS por defecto regula las temperaturas de los ventiladores en unos rangos predefinidos y esto no se desactiva, de hecho trabaja conjuntamente con el script. Como los rangos de temperatura que yo predefino son inferiores a los de la BIOS, en algunas ocasiones los dos pelean por el control de los ventiladores y es lo que ocasiona el problema. Pero el calor es el peor enemigo de la electrónica, y en los portátiles esto es un factor critico. De hecho estoy seguro de que este equipo (muy bien amortizado) me ha durado tantos años gracias a que me he preocupado de este punto. Estoy cansado de ver morir a portátiles y discos duros en verano porque la gente no se preocupa de este tema. Por favor, limpiad el polvo de los ventiladores al llegar el verano, os ahorrareis muchos disgustos.

Así que descartado el reemplazar los ventiladores, me planteé el crear un script que intentara hacer lo mismo pero de forma más suave, intentado reducir el número de cambios bruscos de velocidad y fruto de ello es el siguiente script, que está disponible en GitHub:

#!/usr/bin/env python
# -*- coding: utf8 -*-

"""
    i8kfans.py: Adjust the fans speed in various Dell laptops (with a nvidia
    graphics card) to maintain the right temperatures. This affect both fans,
    the cpu and the gpu fan. Originally i8k was created to run in a Dell
    Inspiron 8000 laptop, but this Dell fan control via SMM BIOS is available
    in others laptops of various series (Inspiron, XPS, Latitude, etcetera),
    but not all of them are supported. Mine is an Inspiron 9400 but I tested
    this successfully in a XPS m1330 too.

    Based on a 2006 bash script by Wheelspin, `i8kapplet`. This old script
    served faithfully me for many years, but my ears couldn't stand much longer
    its random and common slow downs/speed ups. Over the years, fans have
    become more and more loud. This new script runs in a more smooth way, with
    less sudden changes.  It's cheaper than replace booth fans, don't you
    think?

    This script needs the `i8kutils` linux package installed and the `i8k`
    kernel module loaded to work.
"""

#==============================================================================
#    Copyright 2012 joe di castro <joe@joedicastro.com>
#
#    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 3 of the License, or
#    (at your option) any later version.
#
#    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/>.
#==============================================================================

__author__ = "joe di castro <joe@joedicastro.com>"
__license__ = "GNU General Public License version 3"
__date__ = "12/06/2012"
__version__ = "0.3"

try:
    from os import linesep
    from subprocess import check_output, Popen, PIPE
    from sys import exit, exc_info
    from time import sleep
except ImportError:
    # Checks the installation of the necessary python modules
    print((linesep * 2).join(["An error found importing one module:",
    str(exc_info()[1]), "You need to install it", "Stopping..."]))
    exit(-2)

def check_execs(*progs):
    """Check if the programs are installed, if not exit and report."""
    for prog in progs:
        try:
            Popen([prog, '--help'], stdout=PIPE, stderr=PIPE)
        except OSError:
            msg = 'The {0} program is necessary to run this script'.format(prog)
            exit(msg)
    return

def get_right_fan_speed(current_temperature, current_fan_speed, temp_triggers):
    """Get the right fan speed to use with i8kfan command.

    :current_temperature: current temperature value for the fan implied
    :current_fan_speed: current fan speed
    :temp_triggers: the threshold temperatures to trigger the fan speed change
    :returns: right fan speed or "-" (means change nothing to i8kfan)

    """
    right_fan_speed = None  # the right fan speed for the current temp
    if current_temperature >= temp_triggers[0]:
        if current_temperature >= temp_triggers[1]:
            right_fan_speed = 2
        else:
            right_fan_speed = 1
    else:
        right_fan_speed = 0
    return right_fan_speed if right_fan_speed != current_fan_speed else "-"

def main():
    """Main section"""
    # time between temperature checks
    interval = 1
    # the temp thresholds to jump to a faster fan speed. Values greater than
    # [g|c]pu[0] set the fan speed to 1 and the ones greater than [g|c]pu[1]
    # set the speed to 2. Obviously, values minor than [g|c]pu[0] stop the fan
    gpu_temps = [45, 53]
    cpu_temps = [40, 50]

    # check if the i8k kernel module is already loaded
    if  "i8k" not in check_output("ls /proc/".split()):
        exit("The i8k kernel module is not loaded")

    while True:
        try:
            # get current values
            cpu_temp = int(check_output("i8kctl temp".split()))
            gpu_out = check_output("nvidia-smi -q -d TEMPERATURE".split())
            gpu_temp = int([s for s in gpu_out.split() if s.isdigit()][-1])
            cpu_fan, gpu_fan = [int(f) for f in check_output("i8kfan").split()]

            # get the right speed values for each fan
            cpu_rfs = get_right_fan_speed(cpu_temp, cpu_fan, cpu_temps)
            gpu_rfs = get_right_fan_speed(gpu_temp, gpu_fan, gpu_temps)

            # if any of the fans needs to change their speed, change it!
            if cpu_rfs != "-" or gpu_rfs != "-":
                Popen("i8kfan {0} {1}".format(cpu_rfs, gpu_rfs).split(),
                      stdout=PIPE)

            # wait a moment. We want a cooler laptop, aren't we?
            sleep(interval)
        except KeyboardInterrupt:
            exit()

if __name__ == "__main__":
    check_execs("i8kctl", "i8kfan", "nvidia-smi")
    main()

###############################################################################
#                                  Changelog                                  #
###############################################################################
#
# 0.3:
#
# * Better documentation
#
# 0.2:
#
# * Fix an error in a function docstring due to refactorization
# * Give appropriate credit to original idea' script
#
# 0.1:
#
# * First attempt
#

De momento, aunque aún sufre del mismo problema inevitable por el conflicto por el control entre el script y la BIOS, de momento he observado que se produce con menos frecuencia y durante menos tiempo. Aunque quizás realice algunos cambios para intentar reducir aún más esos conflictos.

Etiquetado como: linux, dell, python.