#!/usr/bin/env python3
"""
NVIDIA Aggressive Fan Control Daemon
RTX Pro 6000 Blackwell - Gaming-style fan curve
Runs headless, no X11 required.
"""

import pynvml
import time
import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s',
    handlers=[logging.StreamHandler(sys.stdout)]
)
log = logging.getLogger(__name__)

# --- Fan curve: (temp_C, fan_pct) ---
# More aggressive than stock workstation defaults.
# By 70C fans are already at 80%, by 80C they're maxed.
FAN_CURVE = [
    (0,  30),
    (45, 40),
    (55, 55),
    (63, 70),
    (70, 82),
    (75, 90),
    (80, 100),
]

POLL_INTERVAL = 3   # seconds between checks
GPU_INDEX     = 0   # change if you have multiple GPUs


def interpolate_fan(temp):
    """Return target fan % for a given GPU temp using the curve above."""
    if temp <= FAN_CURVE[0][0]:
        return FAN_CURVE[0][1]
    if temp >= FAN_CURVE[-1][0]:
        return FAN_CURVE[-1][1]
    for i in range(1, len(FAN_CURVE)):
        t1, f1 = FAN_CURVE[i - 1]
        t2, f2 = FAN_CURVE[i]
        if t1 <= temp <= t2:
            ratio = (temp - t1) / (t2 - t1)
            return int(f1 + ratio * (f2 - f1))
    return 100


def set_fans(handle, num_fans, pct):
    pct = max(0, min(100, pct))
    for fan_idx in range(num_fans):
        pynvml.nvmlDeviceSetFanSpeed_v2(handle, fan_idx, pct)


def main():
    pynvml.nvmlInit()
    driver = pynvml.nvmlSystemGetDriverVersion()
    log.info(f"NVIDIA driver: {driver}")

    handle   = pynvml.nvmlDeviceGetHandleByIndex(GPU_INDEX)
    name     = pynvml.nvmlDeviceGetName(handle)
    num_fans = pynvml.nvmlDeviceGetNumFans(handle)
    log.info(f"Controlling: {name}  ({num_fans} fans)")
    log.info("Fan curve (temp->fan%): " +
             "  ".join(f"{t}C->{f}%" for t, f in FAN_CURVE))

    # Take manual control of the fan
    for fan_idx in range(num_fans):
        pynvml.nvmlDeviceSetFanControlPolicy(
            handle, fan_idx,
            pynvml.NVML_FAN_POLICY_MANUAL
        )
    log.info("Fan control set to MANUAL")

    last_pct = -1
    try:
        while True:
            temp    = pynvml.nvmlDeviceGetTemperature(
                handle, pynvml.NVML_TEMPERATURE_GPU)
            target  = interpolate_fan(temp)

            if target != last_pct:
                set_fans(handle, num_fans, target)
                log.info(f"GPU {temp}C -> fans {target}%")
                last_pct = target

            time.sleep(POLL_INTERVAL)

    except KeyboardInterrupt:
        log.info("Interrupted - restoring automatic fan control")
    finally:
        # Hand control back to the driver on exit
        for fan_idx in range(num_fans):
            pynvml.nvmlDeviceSetFanControlPolicy(
                handle, fan_idx,
                pynvml.NVML_FAN_POLICY_TEMPERATURE_CONTINOUS_SW
            )
        pynvml.nvmlShutdown()
        log.info("Fan control restored to automatic")


if __name__ == "__main__":
    main()
