Controle de Temperatura - Acer Predator Neo 16 - PHN16-72 - Pode ser adaptado a outros sistemas

Compartilho uma solução para um problema que me afetava em um Laptop Acer Predator Helios Neo 16 - PHN16-72. Mas que pode com alguns poucos ajustes ajudar outras pessoas com o mesmo problema.

O Problema:

O acer não tem na sua BIOS o controle de temperatura que possa ser alterado fica tudo escondido e pode ser modificado (via Windows) com o app da própria Acer. No linux por algum motivo esse controle era inexistente ou ineficiente e o micro passou a desligar-se por problema de super aquecimento. Por tratar-se de um core i7 com uma 4070 isso ficou perigosamente frequente.

Depois de muito pesquisar, com um pouco de ajuda do ChatGPT (e me atrapalhou bastante também) cheguei a uma solução que pode ajudar mais pessoas e deixo aqui o registro. Foi feito baseado no CachyOS (Arch) meu sistema atual, mas com poucos ajustes pode ser usado em outras distribuições.

:ice: GUIA COMPLETO — CONTROLE DE FAN (ACER PREDATOR + LINUX)

:bullseye: Objetivo

Restaurar controle de fan em notebooks Acer Predator no Linux, especialmente após kernels novos onde NBFC quebra ou fica instável.


:warning: Pré-requisitos

  • Linux (Arch / CachyOS / derivados testados)

  • GPU NVIDIA (testado com RTX 30/40)

  • Acesso root (sudo)

  • NBFC instalado


:package: 1. Instalar dependências

:wrench: Pacotes necessários

Arch / CachyOS:

sudo pacman -S lm_sensors nvidia-utils stress-ng

NBFC (AUR):

yay -S nbfc-linux

:magnifying_glass_tilted_left: Detectar sensores

sudo sensors-detect

Aceita tudo com YES.

Depois testa:

sensors

:gear: 2. Preparar acesso ao EC

:fire: Habilitar escrita no EC

sudo modprobe ec_sys write_support=1

Persistir:

echo "options ec_sys write_support=1" | sudo tee /etc/modprobe.d/ec_sys.conf

:brain: 3. Configurar NBFC

Edita:

sudo nano /etc/nbfc/nbfc.json

Conteúdo:

{
   "SelectedConfigId": "Acer Predator PH315-54",
   "EmbeddedControllerType": "dev_port"
}

:rocket: Teste básico

sudo nbfc start
nbfc status

Se aparecer ativo → OK.


:scroll: 4. Criar script de controle

Cria o arquivo:

mkdir -p ~/bin
nano ~/bin/fan-control.sh

:rocket: Script

#!/bin/bash

LOGDIR="/tmp/fan-control"
mkdir -p "$LOGDIR"

temps=()

last_speed=-1
last_change_time=0
last_cpu_temp=0

start_nbfc() {
    if ! nbfc status >/dev/null 2>&1; then
        nbfc stop >/dev/null 2>&1
        sleep 1
        nbfc start
        sleep 2
    fi
}

get_cpu_temp() {
    sensors | grep "Package id 0" | awk '{print int($4)}' | tr -d '+°C'
}

get_nvidia_temp() {
    nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null
}

get_gpu_usage() {
    nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null
}

get_cpu_usage() {
    top -bn1 | grep "Cpu(s)" | awk '{print int(100 - $8)}'
}

is_on_ac() {
    grep -q 1 /sys/class/power_supply/AC*/online 2>/dev/null
}

get_avg_temp() {
    local sum=0
    [ "${#temps[@]}" -eq 0 ] && echo 0 && return

    for t in "${temps[@]}"; do
        sum=$((sum + t))
    done
    echo $((sum / ${#temps[@]}))
}

start_nbfc

while true; do
    LOGFILE="$LOGDIR/fan-control-$(date +%Y-%m-%d).log"

    cpu_temp=$(get_cpu_temp)
    nvidia_temp=$(get_nvidia_temp)
    cpu_usage=$(get_cpu_usage)
    gpu_usage=$(get_gpu_usage)

    [ -z "$cpu_temp" ] && cpu_temp=0
    [ -z "$nvidia_temp" ] && nvidia_temp=0
    [ -z "$cpu_usage" ] && cpu_usage=0
    [ -z "$gpu_usage" ] && gpu_usage=0

    # média móvel (mantida pra log/diagnóstico)
    temps+=($cpu_temp)
    if [ "${#temps[@]}" -gt 5 ]; then
        temps=("${temps[@]:1}")
    fi

    avg_temp=$(get_avg_temp)
    current_time=$(date +%s)

    ############################################
    # 🧠 DETECÇÃO DE CARGA GLOBAL
    ############################################
    if [ "$cpu_usage" -gt 70 ] || [ "$gpu_usage" -gt 70 ]; then
        load_mode="heavy"
    elif [ "$cpu_usage" -gt 30 ] || [ "$gpu_usage" -gt 30 ]; then
        load_mode="medium"
    else
        load_mode="light"
    fi

    ############################################
    # ⚡ PERFIL (AC vs bateria)
    ############################################
    if is_on_ac; then
        profile="performance"
    else
        profile="silent"
    fi

    ############################################
    # 🔥 TEMPERATURA DOMINANTE
    ############################################
    max_temp=$cpu_temp
    [ "$nvidia_temp" -gt "$max_temp" ] && max_temp=$nvidia_temp

    ############################################
    # 🎯 CURVA INTELIGENTE
    ############################################
    if [ "$profile" = "performance" ]; then

        case $load_mode in
            heavy)
                if [ "$max_temp" -ge 80 ]; then speed=100
                elif [ "$max_temp" -ge 70 ]; then speed=90
                elif [ "$max_temp" -ge 60 ]; then speed=80
                else speed=70
                fi
                ;;
            medium)
                if [ "$max_temp" -ge 75 ]; then speed=90
                elif [ "$max_temp" -ge 65 ]; then speed=75
                elif [ "$max_temp" -ge 55 ]; then speed=60
                else speed=50
                fi
                ;;
            light)
                if [ "$max_temp" -ge 70 ]; then speed=80
                elif [ "$max_temp" -ge 60 ]; then speed=60
                else speed=40
                fi
                ;;
        esac

    else
        if [ "$max_temp" -ge 80 ]; then speed=90
        elif [ "$max_temp" -ge 70 ]; then speed=70
        elif [ "$max_temp" -ge 60 ]; then speed=50
        else speed=30
        fi
    fi

    ############################################
    # 🔥 SEGURANÇA ABSOLUTA
    ############################################
    if [ "$cpu_temp" -ge 90 ] || [ "$nvidia_temp" -ge 88 ]; then
        speed=100
    fi

    ############################################
    # 🔄 HISTERese
    ############################################
    if [ "$speed" -gt "$last_speed" ]; then
        apply=1
        last_change_time=$current_time

    elif [ "$speed" -lt "$last_speed" ]; then
        if [ "$cpu_temp" -le $((last_cpu_temp - 3)) ] && \
           [ $((current_time - last_change_time)) -gt 8 ]; then
            apply=1
            last_change_time=$current_time
        else
            apply=0
        fi
    else
        apply=0
    fi

    ############################################
    # 🔧 NBFC CHECK
    ############################################
    if ! nbfc status >/dev/null 2>&1; then
        echo "$(date '+%F %T') | NBFC caiu, reiniciando..." >> "$LOGFILE"
        start_nbfc
    fi

    ############################################
    # 🚀 APPLY (SEM FLOOD)
    ############################################
    if [ "$apply" -eq 1 ]; then
        nbfc set -s "$speed"
        last_speed=$speed
    fi

    ############################################
    # 🔔 ALERTA
    ############################################
    if [ "$cpu_temp" -ge 85 ]; then
        notify-send "🔥 CPU CRÍTICA: ${cpu_temp}°C"
    fi

    ############################################
    # 📝 LOG
    ############################################
    echo "$(date '+%F %T') | CPU:${cpu_temp}°C GPU:${nvidia_temp}°C CPU_LOAD:${cpu_usage}% GPU_LOAD:${gpu_usage}% MODE:${profile}/${load_mode} FAN:${speed}%" >> "$LOGFILE"

    find "$LOGDIR" -name "fan-control-*.log" -mtime +7 -delete

    last_cpu_temp=$cpu_temp

    sleep 5
done

:locked_with_key: Permissão

chmod +x ~/bin/fan-control.sh

:gear: 5. Criar serviço systemd

Cria:

sudo nano /etc/systemd/system/fan-control.service

Conteúdo:

[Unit]
Description=Fan Control Script
After=multi-user.target

[Service]
ExecStart=/home/SEU_USUARIO/bin/fan-control.sh
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

:backhand_index_pointing_right: Substitui SEU_USUARIO


:counterclockwise_arrows_button: Ativar serviço

sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable fan-control
sudo systemctl start fan-control

:bar_chart: Ver status

sudo systemctl status fan-control

:test_tube: 6. Testar funcionamento

:fire: Monitorar log

tail -f /tmp/fan-control/fan-control-$(date +%Y-%m-%d).log

:bomb: Teste de carga

stress-ng --cpu 8

:white_check_mark: Comportamento esperado

  • Fan sobe antes do spike térmico

  • CPU/GPU não passam de ~80–85°C facilmente

  • Sem “respiração” (oscilação constante)

:brain: Como funciona (resumo técnico)

O script:

  • lê:

    • temperatura CPU

    • temperatura GPU

    • uso CPU (%)

    • uso GPU (%)

  • decide baseado em:

    • carga (leve / média / pesada)

    • fonte de energia (AC vs bateria)

    • temperatura dominante

  • aplica:

    • curva dinâmica

    • histerese (evita oscilar)

    • proteção térmica

5 curtidas

Especificamente para para quem tem o Acer Predator Neo - PHN16-72, vou deixar um link que resolve esse problema e tbm a questão dos LEDs (inclusive da tampa).

Só, como ele é uma módulo do Kernel, toda vez que fizer um update de kernel ele precisa ser recompilado/instalado de novo.

Fiz um script para automatizar essa tarefa:

A ordem (depois do nekro instalado) é update do linux, reboot, roda o script (ele pede a pasta).

Detalhe, uso CachyOS (podem haver diferenças no processo de compilação).

#!/usr/bin/env bash
set -euo pipefail

echo "=== Rebuild Nekro-Sense para o kernel atual ==="
echo

KERNEL="$(uname -r)"
MODULE_NAME="nekro_sense"
DEFAULT_PATH="$HOME/bin/nekro-sense"

read -rp "Path do nekro-sense [$DEFAULT_PATH]: " NEKRO_PATH
NEKRO_PATH="${NEKRO_PATH:-$DEFAULT_PATH}"

if [[ ! -d "$NEKRO_PATH" ]]; then
  echo "ERRO: diretório não encontrado: $NEKRO_PATH"
  exit 1
fi

if [[ ! -f "$NEKRO_PATH/Makefile" ]]; then
  echo "ERRO: Makefile não encontrado em: $NEKRO_PATH"
  exit 1
fi

if [[ ! -d "/lib/modules/$KERNEL/build" ]]; then
  echo "ERRO: headers/build directory não encontrado para o kernel $KERNEL"
  echo "Instale os headers correspondentes, por exemplo:"
  echo "  sudo pacman -S linux-cachyos-headers"
  exit 1
fi

echo
echo "Kernel atual: $KERNEL"
echo "Path Nekro-Sense: $NEKRO_PATH"
echo

cd "$NEKRO_PATH"

echo "==> Limpando build anterior..."
make clean || true

echo
echo "==> Compilando com clang/LLVM..."
make CC=clang LLVM=1

KO_FILE="$(find "$NEKRO_PATH" -name "${MODULE_NAME}.ko" | head -n 1)"

if [[ -z "$KO_FILE" ]]; then
  echo "ERRO: módulo ${MODULE_NAME}.ko não foi encontrado após o build."
  exit 1
fi

echo
echo "==> Instalando módulo:"
echo "    $KO_FILE"
sudo install -Dm644 "$KO_FILE" "/lib/modules/$KERNEL/extra/${MODULE_NAME}.ko"

echo
echo "==> Atualizando dependências de módulos..."
sudo depmod -a

echo
echo "==> Removendo módulo antigo, se estiver carregado..."
if lsmod | grep -q "^${MODULE_NAME}"; then
  sudo modprobe -r "$MODULE_NAME" || {
    echo "Aviso: não consegui remover o módulo antigo. Talvez esteja em uso."
  }
fi

echo
echo "==> Carregando módulo..."
sudo modprobe "$MODULE_NAME"

echo
echo "==> Verificando..."
if lsmod | grep -q "^${MODULE_NAME}"; then
  echo "OK: módulo ${MODULE_NAME} carregado com sucesso."
else
  echo "ERRO: módulo não apareceu no lsmod."
  exit 1
fi

if [[ -d "/sys/module/$MODULE_NAME" ]]; then
  echo "OK: /sys/module/$MODULE_NAME existe."
else
  echo "Aviso: /sys/module/$MODULE_NAME não encontrado."
fi

echo
echo "Concluído."

No caso de mudar para o Nekro-sense, muda também o script inicial desse post para:

#!/bin/bash

NEKROCTL="PATH/nekro-sense/tools/nekroctl.py" ### PRECISA SER MUDADO ###

LOGDIR="/tmp/fan-control"
mkdir -p "$LOGDIR"
LOGFILE="$LOGDIR/fan-control-$(date +%Y-%m-%d).log"

last_cpu=-1
last_gpu=-1

############################################
# TEMPERATURAS
############################################

get_cpu_temp() {
    sensors | awk '/Package id 0:/ {
        gsub(/\+|°C/, "", $4)
        print int($4)
        exit
    }'
}

get_gpu_temp() {
    nvidia-smi \
        --query-gpu=temperature.gpu \
        --format=csv,noheader,nounits 2>/dev/null
}

############################################
# CURVAS
############################################

cpu_curve() {
    local t=$1

    if   [ "$t" -ge 90 ]; then echo 100
    elif [ "$t" -ge 85 ]; then echo 95
    elif [ "$t" -ge 75 ]; then echo 80
    elif [ "$t" -ge 68 ]; then echo 65
    elif [ "$t" -ge 60 ]; then echo 50
    elif [ "$t" -ge 55 ]; then echo 35
    else echo 25
    fi
}

gpu_curve() {
    local t=$1

    if   [ "$t" -ge 88 ]; then echo 100
    elif [ "$t" -ge 82 ]; then echo 90
    elif [ "$t" -ge 75 ]; then echo 75
    elif [ "$t" -ge 68 ]; then echo 60
    elif [ "$t" -ge 60 ]; then echo 45
    else echo 25
    fi
}

############################################
# APPLY FAN
############################################

set_fans() {
    local cpu=$1
    local gpu=$2

    python3 "$NEKROCTL" fan set \
        --cpu "$cpu" \
        --gpu "$gpu" \
        >/dev/null 2>&1
}

fan_auto() {
    python3 "$NEKROCTL" fan auto >/dev/null 2>&1
}

############################################
# CLEAN EXIT
############################################

cleanup() {
    fan_auto

    echo "$(date '+%F %T') | FAN AUTO RESTAURADO" >> "$LOGFILE"

    exit 0
}

trap cleanup INT TERM EXIT

############################################
# LOOP
############################################

while true; do

    cpu_temp=$(get_cpu_temp)
    gpu_temp=$(get_gpu_temp)

    [ -z "$cpu_temp" ] && cpu_temp=0
    [ -z "$gpu_temp" ] && gpu_temp=0

    cpu_speed=$(cpu_curve "$cpu_temp")
    gpu_speed=$(gpu_curve "$gpu_temp")

    ########################################
    # HISTERESE CPU
    ########################################

    if [ "$last_cpu" -ge 0 ] &&
       [ "$cpu_speed" -lt "$last_cpu" ]; then

        if [ $((last_cpu - cpu_speed)) -gt 10 ]; then
            cpu_speed=$((last_cpu - 5))
        fi
    fi

    ########################################
    # HISTERESE GPU
    ########################################

    if [ "$last_gpu" -ge 0 ] &&
       [ "$gpu_speed" -lt "$last_gpu" ]; then

        if [ $((last_gpu - gpu_speed)) -gt 10 ]; then
            gpu_speed=$((last_gpu - 5))
        fi
    fi

    ########################################
    # APPLY
    ########################################

    if [ "$cpu_speed" -ne "$last_cpu" ] ||
       [ "$gpu_speed" -ne "$last_gpu" ]; then

        set_fans "$cpu_speed" "$gpu_speed"

        last_cpu=$cpu_speed
        last_gpu=$gpu_speed
    fi

    ########################################
    # LOG
    ########################################

    echo "$(date '+%F %T') | CPU:${cpu_temp}°C GPU:${gpu_temp}°C CPU_FAN:${cpu_speed}% GPU_FAN:${gpu_speed}%" \
        >> "$LOGFILE"

    sleep 5
done

O processo de rodar como serviço segue o mesmo.