From 4f734a1fd1d0e8d40e0e3a1eadd0956618aa8f18 Mon Sep 17 00:00:00 2001 From: Sanghyun Na <106161726+sanghyunna@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:32:44 +0900 Subject: [PATCH 1/5] Support for up to 2 GPUs --- library/sensors/sensors.py | 6 +- .../sensors/sensors_librehardwaremonitor.py | 288 +++++++----- library/sensors/sensors_python.py | 419 ++++++++++++------ library/sensors/sensors_stub_random.py | 26 +- library/sensors/sensors_stub_static.py | 60 ++- library/stats.py | 329 ++++++++++++-- 6 files changed, 792 insertions(+), 336 deletions(-) diff --git a/library/sensors/sensors.py b/library/sensors/sensors.py index 0a9ada94..5f43209d 100644 --- a/library/sensors/sensors.py +++ b/library/sensors/sensors.py @@ -20,7 +20,7 @@ # To be overriden by child sensors classes from abc import ABC, abstractmethod -from typing import Tuple +from typing import List, Tuple class Cpu(ABC): @@ -77,6 +77,10 @@ def frequency() -> float: def is_available() -> bool: pass + @staticmethod + @abstractmethod + def get_gpu_names() -> List[str]: + pass class Memory(ABC): @staticmethod diff --git a/library/sensors/sensors_librehardwaremonitor.py b/library/sensors/sensors_librehardwaremonitor.py index f619a5b3..754bf6a7 100644 --- a/library/sensors/sensors_librehardwaremonitor.py +++ b/library/sensors/sensors_librehardwaremonitor.py @@ -25,7 +25,7 @@ import os import sys from statistics import mean -from typing import Tuple +from typing import List, Tuple import clr # Clr is from pythonnet package. Do not install clr package import psutil @@ -96,6 +96,17 @@ def get_hw_and_update(hwtype: Hardware.HardwareType, name: str = None) -> Hardwa return hardware return None +def get_all_gpus_and_update() -> List[Hardware.Hardware]: + hw_gpus = [] + for hardware in handle.Hardware: + if hardware.HardwareType in ( + Hardware.HardwareType.GpuNvidia, + Hardware.HardwareType.GpuAmd, + Hardware.HardwareType.GpuIntel + ): + hardware.Update() + hw_gpus.append(hardware) + return hw_gpus def get_gpu_name() -> str: # Determine which GPU to use, in case there are multiple : try to avoid using discrete GPU for stats @@ -160,14 +171,16 @@ def get_gpu_name() -> str: return gpu_to_use - def get_net_interface_and_update(if_name: str) -> Hardware.Hardware: for hardware in handle.Hardware: if hardware.HardwareType == Hardware.HardwareType.Network and hardware.Name == if_name: hardware.Update() return hardware - - logger.warning("Network interface '%s' not found. Check names in config.yaml." % if_name) + # Log only once per missing interface + if not hasattr(Net, '_logged_missing_lhm') or if_name not in Net._logged_missing_lhm: + logger.warning(f"LHM: Network interface '{if_name}' not found. Check names in config.yaml.") + if not hasattr(Net, '_logged_missing_lhm'): Net._logged_missing_lhm = set() + Net._logged_missing_lhm.add(if_name) return None @@ -175,12 +188,12 @@ class Cpu(sensors.Cpu): @staticmethod def percentage(interval: float) -> float: cpu = get_hw_and_update(Hardware.HardwareType.Cpu) + if not cpu: return math.nan for sensor in cpu.Sensors: if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith( "CPU Total") and sensor.Value is not None: return float(sensor.Value) - - logger.error("CPU load cannot be read") + logger.error("LHM: CPU load cannot be read") return math.nan @staticmethod @@ -207,11 +220,13 @@ def frequency() -> float: @staticmethod def load() -> Tuple[float, float, float]: # 1 / 5 / 15min avg (%): # Get this data from psutil because it is not available from LibreHardwareMonitor - return psutil.getloadavg() + try: return psutil.getloadavg() + except: return math.nan, math.nan, math.nan @staticmethod def temperature() -> float: cpu = get_hw_and_update(Hardware.HardwareType.Cpu) + if not cpu: return math.nan try: # By default, the average temperature of all CPU cores will be used for sensor in cpu.Sensors: @@ -233,23 +248,37 @@ def temperature() -> float: if sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith( "Core") and sensor.Value is not None: return float(sensor.Value) - except: - pass + except Exception as e: + logger.debug(f"LHM: Error reading CPU temperature: {e}") return math.nan @staticmethod def fan_percent(fan_name: str = None) -> float: mb = get_hw_and_update(Hardware.HardwareType.Motherboard) + controllers = [hw for hw in handle.Hardware if hw.HardwareType == Hardware.HardwareType.Controller] + try: - for sh in mb.SubHardware: - sh.Update() - for sensor in sh.Sensors: - if sensor.SensorType == Hardware.SensorType.Control and "#2" in str( - sensor.Name) and sensor.Value is not None: # Is Motherboard #2 Fan always the CPU Fan ? - return float(sensor.Value) - except: - pass + search_hardware = [mb] + controllers if mb else controllers + + for hw_container in search_hardware: + if not hw_container: continue + hw_container.Update() + + for sh in hw_container.SubHardware: + sh.Update() + + for sensor in sh.Sensors: + # Look for exact match + if fan_name and sensor.Identifier and fan_name == str(sensor.Identifier).replace('/control/', '/fan/') and sensor.SensorType == Hardware.SensorType.Control: + if sensor.Value is not None: + return float(sensor.Value) + # Try to find CPU fan + elif fan_name is None and sensor.SensorType == Hardware.SensorType.Control and ("cpu" in str(sensor.Name).lower() or "#2" in str(sensor.Name)): + if sensor.Value is not None: + return float(sensor.Value) + except Exception as e: + logger.debug(f"LHM: Error reading CPU fan speed: {e}") # No Fan Speed sensor for this CPU model return math.nan @@ -262,118 +291,143 @@ class Gpu(sensors.Gpu): # Latest FPS value is backed up in case next reading returns no value prev_fps = 0 - # Get GPU to use for sensors, and update it @classmethod - def get_gpu_to_use(cls): - gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuAmd, cls.gpu_name) - if gpu_to_use is None: - gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuNvidia, cls.gpu_name) - if gpu_to_use is None: - gpu_to_use = get_hw_and_update(Hardware.HardwareType.GpuIntel, cls.gpu_name) - - return gpu_to_use + def stats(cls) -> List[Tuple[float, float, float, float, float]]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) + # Returns stats for all GPUs found in the system + all_stats = [] + gpus_to_use = get_all_gpus_and_update() + + if not gpus_to_use: + # No supported GPUs found + return [] + + for gpu_hw in gpus_to_use: + load = math.nan + used_mem = math.nan + total_mem = math.nan + temp = math.nan + + try: + for sensor in gpu_hw.Sensors: + if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith( + "GPU Core") and sensor.Value is not None: + load = float(sensor.Value) + elif sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("D3D 3D") and math.isnan( + load) and sensor.Value is not None: + # Only use D3D usage if global "GPU Core" sensor is not available + load = float(sensor.Value) + elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( + "GPU Memory Used") and sensor.Value is not None: + used_mem = float(sensor.Value) + elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( + "D3D") and str(sensor.Name).endswith("Memory Used") and math.isnan( + used_mem) and sensor.Value is not None: + # Only use D3D memory usage if global "GPU Memory Used" sensor is not available + used_mem = float(sensor.Value) + elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( + "GPU Memory Total") and sensor.Value is not None: + total_mem = float(sensor.Value) + elif sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith( + "GPU Core") and sensor.Value is not None: + temp = float(sensor.Value) + + # Calculate memory percentage if possible + memory_percent = (used_mem / total_mem * 100.0) if not math.isnan(used_mem) and not math.isnan(total_mem) and total_mem > 0 else math.nan + + all_stats.append((load, memory_percent, used_mem, total_mem, temp)) + except Exception as e: + logger.debug(f"LHM: Error processing sensors for GPU {gpu_hw.Name}: {e}") + all_stats.append((math.nan, math.nan, math.nan, math.nan, math.nan)) + + return all_stats @classmethod - def stats(cls) -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) - gpu_to_use = cls.get_gpu_to_use() - if gpu_to_use is None: - # GPU not supported - return math.nan, math.nan, math.nan, math.nan, math.nan - - load = math.nan - used_mem = math.nan - total_mem = math.nan - temp = math.nan - - for sensor in gpu_to_use.Sensors: - if sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith( - "GPU Core") and sensor.Value is not None: - load = float(sensor.Value) - elif sensor.SensorType == Hardware.SensorType.Load and str(sensor.Name).startswith("D3D 3D") and math.isnan( - load) and sensor.Value is not None: - # Only use D3D usage if global "GPU Core" sensor is not available, because it is less - # precise and does not cover the entire GPU: https://www.hwinfo.com/forum/threads/what-is-d3d-usage.759/ - load = float(sensor.Value) - elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( - "GPU Memory Used") and sensor.Value is not None: - used_mem = float(sensor.Value) - elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( - "D3D") and str(sensor.Name).endswith("Memory Used") and math.isnan( - used_mem) and sensor.Value is not None: - # Only use D3D memory usage if global "GPU Memory Used" sensor is not available, because it is less - # precise and does not cover the entire GPU: https://www.hwinfo.com/forum/threads/what-is-d3d-usage.759/ - used_mem = float(sensor.Value) - elif sensor.SensorType == Hardware.SensorType.SmallData and str(sensor.Name).startswith( - "GPU Memory Total") and sensor.Value is not None: - total_mem = float(sensor.Value) - elif sensor.SensorType == Hardware.SensorType.Temperature and str(sensor.Name).startswith( - "GPU Core") and sensor.Value is not None: - temp = float(sensor.Value) - - return load, (used_mem / total_mem * 100.0), used_mem, total_mem, temp + def get_gpu_names(cls) -> List[str]: + names = [] + gpus_to_use = get_all_gpus_and_update() + for gpu_hw in gpus_to_use: + names.append(gpu_hw.Name if gpu_hw.Name else "Unknown LHM GPU") + return names @classmethod - def fps(cls) -> int: - gpu_to_use = cls.get_gpu_to_use() - if gpu_to_use is None: - # GPU not supported - return -1 - - try: - for sensor in gpu_to_use.Sensors: - if sensor.SensorType == Hardware.SensorType.Factor and "FPS" in str( - sensor.Name) and sensor.Value is not None: - # If a reading returns a value <= 0, returns old value instead - if int(sensor.Value) > 0: - cls.prev_fps = int(sensor.Value) - return cls.prev_fps - except: - pass - - # No FPS sensor for this GPU model - return -1 + def fps(cls) -> List[int]: + # Returns FPS for all GPUs found in the system + all_fps = [] + gpus_to_use = get_all_gpus_and_update() + + if not gpus_to_use: + # No supported GPUs found + return [] + + for gpu_hw in gpus_to_use: + current_fps = -1 + try: + for sensor in gpu_hw.Sensors: + if sensor.SensorType == Hardware.SensorType.Factor and "FPS" in str(sensor.Name) and sensor.Value is not None: + # If a reading returns a valid value, use it + if int(sensor.Value) > 0: + current_fps = int(sensor.Value) + break + except Exception as e: + logger.debug(f"LHM: Error reading FPS for GPU {gpu_hw.Name}: {e}") + + all_fps.append(current_fps) + + return all_fps @classmethod - def fan_percent(cls) -> float: - gpu_to_use = cls.get_gpu_to_use() - if gpu_to_use is None: - # GPU not supported - return math.nan - - try: - for sensor in gpu_to_use.Sensors: - if sensor.SensorType == Hardware.SensorType.Control and sensor.Value is not None: - return float(sensor.Value) - except: - pass - - # No Fan Speed sensor for this GPU model - return math.nan + def fan_percent(cls) -> List[float]: + # Returns fan speed for all GPUs found in the system + all_fans = [] + gpus_to_use = get_all_gpus_and_update() + + if not gpus_to_use: + # No supported GPUs found + return [] + + for gpu_hw in gpus_to_use: + fan = math.nan + try: + for sensor in gpu_hw.Sensors: + if sensor.SensorType == Hardware.SensorType.Control and sensor.Value is not None: + fan = float(sensor.Value) + break + except Exception as e: + logger.debug(f"LHM: Error reading fan speed for GPU {gpu_hw.Name}: {e}") + + all_fans.append(fan) + + return all_fans @classmethod - def frequency(cls) -> float: - gpu_to_use = cls.get_gpu_to_use() - if gpu_to_use is None: - # GPU not supported - return math.nan - - try: - for sensor in gpu_to_use.Sensors: - if sensor.SensorType == Hardware.SensorType.Clock: - # Keep only real core clocks, ignore effective core clocks - if "Core" in str(sensor.Name) and "Effective" not in str(sensor.Name) and sensor.Value is not None: - return float(sensor.Value) - except: - pass - - # No Frequency sensor for this GPU model - return math.nan + def frequency(cls) -> List[float]: + # Returns core clock for all GPUs found in the system + all_freqs = [] + gpus_to_use = get_all_gpus_and_update() + + if not gpus_to_use: + # No supported GPUs found + return [] + + for gpu_hw in gpus_to_use: + freq = math.nan + try: + for sensor in gpu_hw.Sensors: + if sensor.SensorType == Hardware.SensorType.Clock: + # Keep only real core clocks, ignore effective core clocks + if "Core" in str(sensor.Name) and "Effective" not in str(sensor.Name) and sensor.Value is not None: + freq = float(sensor.Value) + break + except Exception as e: + logger.debug(f"LHM: Error reading frequency for GPU {gpu_hw.Name}: {e}") + + all_freqs.append(freq) + + return all_freqs @classmethod def is_available(cls) -> bool: - cls.gpu_name = get_gpu_name() - return bool(cls.gpu_name) + return bool(get_all_gpus_and_update()) class Memory(sensors.Memory): diff --git a/library/sensors/sensors_python.py b/library/sensors/sensors_python.py index 289e8800..726dd7df 100644 --- a/library/sensors/sensors_python.py +++ b/library/sensors/sensors_python.py @@ -24,7 +24,7 @@ import sys from collections import namedtuple from enum import IntEnum, auto -from typing import Tuple +from typing import Tuple, List # Added List # Nvidia GPU import GPUtil @@ -92,7 +92,15 @@ def sensors_fans(): min_rpm = int(bcat(base + '_min')) except: min_rpm = 0 # Approximated: min fan speed is 0 RPM - percent = int((current_rpm - min_rpm) / (max_rpm - min_rpm) * 100) + + # Avoid division by zero if max_rpm equals min_rpm + if max_rpm > min_rpm: + percent = int((current_rpm - min_rpm) / (max_rpm - min_rpm) * 100) + # Clamp percentage between 0 and 100 + percent = max(0, min(100, percent)) + else: + percent = 0 + except (IOError, OSError) as err: continue unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip() @@ -173,45 +181,105 @@ def fan_percent(fan_name: str = None) -> float: class Gpu(sensors.Gpu): @staticmethod - def stats() -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) + def stats() -> List[Tuple[float, float, float, float, float]]: + # Returns list of: load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) per GPU global DETECTED_GPU if DETECTED_GPU == GpuType.AMD: return GpuAmd.stats() elif DETECTED_GPU == GpuType.NVIDIA: return GpuNvidia.stats() else: - return math.nan, math.nan, math.nan, math.nan, math.nan + return [] # Return empty list if no supported GPU @staticmethod - def fps() -> int: + def fps() -> List[int]: global DETECTED_GPU if DETECTED_GPU == GpuType.AMD: return GpuAmd.fps() elif DETECTED_GPU == GpuType.NVIDIA: return GpuNvidia.fps() else: - return -1 + return [] @staticmethod - def fan_percent() -> float: + def fan_percent() -> List[float]: + global DETECTED_GPU + num_gpus = 0 + expected_fan_dev_names = [] + + # Determine number of GPUs and expected device names for fan lookup + if DETECTED_GPU == GpuType.NVIDIA: + try: + num_gpus = len(GPUtil.getGPUs()) + expected_fan_dev_names = ['nouveau', 'nvidia'] + except: return [] + elif DETECTED_GPU == GpuType.AMD: + try: + if pyamdgpuinfo: + num_gpus = pyamdgpuinfo.detect_gpus() + expected_fan_dev_names = ['amdgpu', 'radeon'] + elif pyadl: + num_gpus = len(pyadl.ADLManager.getInstance().getDevices()) + expected_fan_dev_names = ['amdgpu', 'radeon'] + else: return [] + except: return [] + else: + return [] + + fan_percentages = [math.nan] * num_gpus + + try: + if platform.system() == "Linux": # Linux : sensors_fans + fans = sensors_fans() + fans_found_for_type = [] + # Find fans related to the GPU + for dev_name, entries in fans.items(): + if any(expected_name in dev_name.lower() for expected_name in expected_fan_dev_names): + for entry in entries: + if "gpu" in entry.label.lower() or "fan" in entry.label.lower(): # Broader check + fans_found_for_type.append(entry.percent) + + # Sequential mapping + for i in range(min(num_gpus, len(fans_found_for_type))): + fan_percentages[i] = fans_found_for_type[i] + except Exception as e: + logger.debug(f"sensors_fans check failed or not applicable: {e}") + pass + + if DETECTED_GPU == GpuType.AMD and pyadl and platform.system() == "Windows": # AMD gpu on Windows : pyadl + try: + devices = pyadl.ADLManager.getInstance().getDevices() + for i, device in enumerate(devices): + if i < num_gpus and math.isnan(fan_percentages[i]): # Only overwrite if the previous method resulted in NaN + try: + fan_percentages[i] = device.getCurrentFanSpeed(pyadl.ADL_DEVICE_FAN_SPEED_TYPE_PERCENTAGE) + except: + fan_percentages[i] = math.nan # Keep nan if pyadl fails + except Exception as e: + logger.debug(f"pyadl fan check failed: {e}") + pass + + return fan_percentages + + @staticmethod + def get_gpu_names() -> List[str]: global DETECTED_GPU if DETECTED_GPU == GpuType.AMD: - return GpuAmd.fan_percent() + return GpuAmd.get_gpu_names() elif DETECTED_GPU == GpuType.NVIDIA: - return GpuNvidia.fan_percent() + return GpuNvidia.get_gpu_names() else: - return math.nan + return [] @staticmethod - def frequency() -> float: + def frequency() -> List[float]: global DETECTED_GPU if DETECTED_GPU == GpuType.AMD: return GpuAmd.frequency() elif DETECTED_GPU == GpuType.NVIDIA: return GpuNvidia.frequency() else: - return math.nan + return [] @staticmethod def is_available() -> bool: @@ -236,65 +304,62 @@ def is_available() -> bool: class GpuNvidia(sensors.Gpu): @staticmethod - def stats() -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) - # Unlike other sensors, Nvidia GPU with GPUtil pulls in all the stats at once - nvidia_gpus = GPUtil.getGPUs() - - try: - memory_used_all = [item.memoryUsed for item in nvidia_gpus] - memory_used_mb = sum(memory_used_all) / len(memory_used_all) - except: - memory_used_mb = math.nan - - try: - memory_total_all = [item.memoryTotal for item in nvidia_gpus] - memory_total_mb = sum(memory_total_all) / len(memory_total_all) - except: - memory_total_mb = math.nan - + def stats() -> List[Tuple[float, float, float, float, float]]: + # Returns list of: load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) per GPU + all_stats = [] try: - memory_percentage = (memory_used_mb / memory_total_mb) * 100 - except: - memory_percentage = math.nan + nvidia_gpus = GPUtil.getGPUs() + for gpu in nvidia_gpus: + load = gpu.load * 100 if gpu.load is not None else math.nan + memory_used_mb = gpu.memoryUsed if gpu.memoryUsed is not None else math.nan + memory_total_mb = gpu.memoryTotal if gpu.memoryTotal is not None else math.nan + + if not math.isnan(memory_used_mb) and not math.isnan(memory_total_mb) and memory_total_mb > 0: + memory_percentage = (memory_used_mb / memory_total_mb) * 100 + else: + memory_percentage = math.nan + + temperature = gpu.temperature if gpu.temperature is not None else math.nan + all_stats.append((load, memory_percentage, memory_used_mb, memory_total_mb, temperature)) + except Exception as e: + logger.error(f"Error getting Nvidia stats with GPUtil: {e}") + # Return list of nans if GPUtil fails entirely + try: num_gpus = len(GPUtil.getGPUs()) # Try to get count even on error + except: num_gpus = 1 # Assume 1 if count fails + return [(math.nan, math.nan, math.nan, math.nan, math.nan)] * num_gpus + return all_stats + @staticmethod + def get_gpu_names() -> List[str]: + names = [] try: - load_all = [item.load for item in nvidia_gpus] - load = (sum(load_all) / len(load_all)) * 100 - except: - load = math.nan - - try: - temperature_all = [item.temperature for item in nvidia_gpus] - temperature = sum(temperature_all) / len(temperature_all) - except: - temperature = math.nan - - return load, memory_percentage, memory_used_mb, memory_total_mb, temperature + nvidia_gpus = GPUtil.getGPUs() + for gpu in nvidia_gpus: + names.append(gpu.name if gpu.name else "NVIDIA GPU") + except Exception as e: + logger.error(f"Error getting Nvidia GPU names: {e}") + return names @staticmethod - def fps() -> int: - # Not supported by Python libraries - return -1 + def fps() -> List[int]: + # Not supported by the GPUtil library + try: num_gpus = len(GPUtil.getGPUs()) + except: num_gpus = 0 + return [-1] * num_gpus @staticmethod - def fan_percent() -> float: - try: - fans = sensors_fans() - if fans: - for name, entries in fans.items(): - for entry in entries: - if "gpu" in (entry.label.lower() or name.lower()): - return entry.percent - except: - pass - - return math.nan + def fan_percent() -> List[float]: + # Fan speed is handled by the main Gpu.fan_percent() method using OS interfaces, GPUtil doesn't provide fan speed directly. + try: num_gpus = len(GPUtil.getGPUs()) + except: num_gpus = 0 + return [math.nan] * num_gpus @staticmethod - def frequency() -> float: - # Not supported by Python libraries - return math.nan + def frequency() -> List[float]: + # Not supported by the GPUtil library + try: num_gpus = len(GPUtil.getGPUs()) + except: num_gpus = 0 + return [math.nan] * num_gpus @staticmethod def is_available() -> bool: @@ -306,96 +371,137 @@ def is_available() -> bool: class GpuAmd(sensors.Gpu): @staticmethod - def stats() -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) + def stats() -> List[Tuple[float, float, float, float, float]]: + # Returns list of: load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) per GPU + all_stats = [] if pyamdgpuinfo: - # Unlike other sensors, AMD GPU with pyamdgpuinfo pulls in all the stats at once - pyamdgpuinfo.detect_gpus() - amd_gpu = pyamdgpuinfo.get_gpu(0) - try: - memory_used_bytes = amd_gpu.query_vram_usage() - memory_used = memory_used_bytes / 1024 / 1024 - except: - memory_used_bytes = math.nan - memory_used = math.nan + num_gpus = pyamdgpuinfo.detect_gpus() + for i in range(num_gpus): + load, memory_percentage, memory_used, memory_total, temperature = math.nan, math.nan, math.nan, math.nan, math.nan + try: + amd_gpu = pyamdgpuinfo.get_gpu(i) + try: memory_used_bytes = amd_gpu.query_vram_usage() + except: memory_used_bytes = math.nan + try: memory_total_bytes = amd_gpu.memory_info["vram_size"] + except: memory_total_bytes = math.nan + + if not math.isnan(memory_used_bytes) and not math.isnan(memory_total_bytes) and memory_total_bytes > 0: + memory_percentage = (memory_used_bytes / memory_total_bytes) * 100 + memory_used = memory_used_bytes / 1024 / 1024 + memory_total = memory_total_bytes / 1024 / 1024 + else: + memory_percentage, memory_used, memory_total = math.nan, math.nan, math.nan + + try: load = amd_gpu.query_load() * 100 + except: load = math.nan + try: temperature = amd_gpu.query_temperature() + except: temperature = math.nan + except Exception as gpu_err: + logger.debug(f"Error getting stats for AMD GPU {i} (pyamdgpuinfo): {gpu_err}") + all_stats.append((load, memory_percentage, memory_used, memory_total, temperature)) + except Exception as e: + logger.error(f"Error detecting AMD GPUs with pyamdgpuinfo: {e}") - try: - memory_total_bytes = amd_gpu.memory_info["vram_size"] - memory_total = memory_total_bytes / 1024 / 1024 - except: - memory_total_bytes = math.nan - memory_total = math.nan - - try: - memory_percentage = (memory_used_bytes / memory_total_bytes) * 100 - except: - memory_percentage = math.nan + elif pyadl: try: - load = amd_gpu.query_load() * 100 - except: - load = math.nan + devices = pyadl.ADLManager.getInstance().getDevices() + for amd_gpu in devices: + load, temperature = math.nan, math.nan + try: + try: load = amd_gpu.getCurrentUsage() + except: load = math.nan + try: temperature = amd_gpu.getCurrentTemperature() + except: temperature = math.nan + except Exception as gpu_err: + logger.debug(f"Error getting stats for AMD GPU (pyadl): {gpu_err}") + # pyadl doesn't easily provide memory details + all_stats.append((load, math.nan, math.nan, math.nan, temperature)) + except Exception as e: + logger.error(f"Error detecting AMD GPUs with pyadl: {e}") + + return all_stats + @staticmethod + def get_gpu_names() -> List[str]: + names = [] + if pyamdgpuinfo: try: - temperature = amd_gpu.query_temperature() - except: - temperature = math.nan - - return load, memory_percentage, memory_used, memory_total, temperature + num_gpus = pyamdgpuinfo.detect_gpus() + for i in range(num_gpus): + try: + name = pyamdgpuinfo.get_gpu(i).marketing_name + names.append(name if name else f"AMD GPU {i}") + except: + names.append(f"AMD GPU {i}") + except Exception as e: + logger.error(f"Error getting AMD GPU names (pyamdgpuinfo): {e}") elif pyadl: - amd_gpu = pyadl.ADLManager.getInstance().getDevices()[0] - - try: - load = amd_gpu.getCurrentUsage() - except: - load = math.nan - try: - temperature = amd_gpu.getCurrentTemperature() - except: - temperature = math.nan - - # GPU memory data not supported by pyadl - return load, math.nan, math.nan, math.nan, temperature + devices = pyadl.ADLManager.getInstance().getDevices() + for i, device in enumerate(devices): + try: + name = device.adapterName.decode('utf-8') + names.append(name if name else f"AMD GPU {i}") + except: + names.append(f"AMD GPU {i}") + except Exception as e: + logger.error(f"Error getting AMD GPU names (pyadl): {e}") + return names @staticmethod - def fps() -> int: + def fps() -> List[int]: # Not supported by Python libraries - return -1 + num_gpus = 0 + try: + if pyamdgpuinfo: num_gpus = pyamdgpuinfo.detect_gpus() + elif pyadl: num_gpus = len(pyadl.ADLManager.getInstance().getDevices()) + except: pass + return [-1] * num_gpus @staticmethod - def fan_percent() -> float: + def fan_percent() -> List[float]: + # Fan speed is handled by the main Gpu.fan_percent method using OS interfaces or pyadl + num_gpus = 0 try: - # Try with psutil fans - fans = sensors_fans() - if fans: - for name, entries in fans.items(): - for entry in entries: - if "gpu" in (entry.label.lower() or name.lower()): - return entry.percent + if pyamdgpuinfo: num_gpus = pyamdgpuinfo.detect_gpus() + elif pyadl: num_gpus = len(pyadl.ADLManager.getInstance().getDevices()) + except: pass + return [math.nan] * num_gpus # Return list of nans, main method handles it - # Try with pyadl if psutil did not find GPU fan - if pyadl: - return pyadl.ADLManager.getInstance().getDevices()[0].getCurrentFanSpeed( - pyadl.ADL_DEVICE_FAN_SPEED_TYPE_PERCENTAGE) - except: - pass + @staticmethod + def frequency() -> List[float]: # Returns list of MHz + frequencies = [] + if pyamdgpuinfo: + try: + num_gpus = pyamdgpuinfo.detect_gpus() + frequencies = [math.nan] * num_gpus + for i in range(num_gpus): + try: frequencies[i] = pyamdgpuinfo.get_gpu(i).query_sclk() + except: pass # Keep nan on error + + except Exception as e: + logger.error(f"Error detecting AMD GPU frequency with pyamdgpuinfo: {e}") + try: num_gpus = pyamdgpuinfo.detect_gpus() # Try to determine num_gpus anyway + except: num_gpus = 1 # Assume 1 if count fails + return [math.nan] * num_gpus - return math.nan + elif pyadl: + try: + devices = pyadl.ADLManager.getInstance().getDevices() + frequencies = [math.nan] * len(devices) + for i, device in enumerate(devices): + try: frequencies[i] = device.getCurrentEngineClock() # Returns MHz + except: pass # Keep nan on error + + except Exception as e: + logger.error(f"Error detecting AMD GPU frequency with pyadl: {e}") + try: num_gpus = len(pyadl.ADLManager.getInstance().getDevices()) # Try to determine num_gpus anyway + except: num_gpus = 1 # Assume 1 if count fails + return [math.nan] * num_gpus + return frequencies - @staticmethod - def frequency() -> float: - try: - if pyamdgpuinfo: - pyamdgpuinfo.detect_gpus() - return pyamdgpuinfo.get_gpu(0).query_sclk() / 1000000 - elif pyadl: - return pyadl.ADLManager.getInstance().getDevices()[0].getCurrentEngineClock() - else: - return math.nan - except: - return math.nan @staticmethod def is_available() -> bool: @@ -484,18 +590,41 @@ def stats(if_name, interval) -> Tuple[ if if_name != "": if if_name in pnic_after: try: - upload_rate = (pnic_after[if_name].bytes_sent - PNIC_BEFORE[if_name].bytes_sent) / interval + # Ensure interval is not zero and we have previous data + if interval > 0 and if_name in PNIC_BEFORE: + upload_rate = (pnic_after[if_name].bytes_sent - PNIC_BEFORE[if_name].bytes_sent) / interval + download_rate = (pnic_after[if_name].bytes_recv - PNIC_BEFORE[if_name].bytes_recv) / interval + # Prevent negative rates if counters reset + upload_rate = max(0, upload_rate) + download_rate = max(0, download_rate) + else: + upload_rate = 0 + download_rate = 0 + uploaded = pnic_after[if_name].bytes_sent - download_rate = (pnic_after[if_name].bytes_recv - PNIC_BEFORE[if_name].bytes_recv) / interval downloaded = pnic_after[if_name].bytes_recv - except: - # Interface might not be in PNIC_BEFORE for now - pass + + except KeyError: # Handles the case where if_name is not in PNIC_BEFORE yet + upload_rate = 0 + download_rate = 0 + uploaded = pnic_after[if_name].bytes_sent + downloaded = pnic_after[if_name].bytes_recv + except Exception as e: + logger.debug(f"Error calculating net stats for {if_name}: {e}") + upload_rate, uploaded, download_rate, downloaded = 0, 0, 0, 0 PNIC_BEFORE.update({if_name: pnic_after[if_name]}) else: - logger.warning("Network interface '%s' not found. Check names in config.yaml." % if_name) - - return upload_rate, uploaded, download_rate, downloaded - except: - return -1, -1, -1, -1 + # Log only once per missing interface to avoid spamming + if not hasattr(Net, '_logged_missing') or if_name not in Net._logged_missing: + logger.warning(f"Network interface '{if_name}' not found in psutil.net_io_counters(). Check names in config.yaml.") + if not hasattr(Net, '_logged_missing'): Net._logged_missing = set() + Net._logged_missing.add(if_name) + upload_rate, uploaded, download_rate, downloaded = 0, 0, 0, 0 + + + return int(upload_rate), int(uploaded), int(download_rate), int(downloaded) + + except Exception as e: + logger.error(f"General error fetching network stats: {e}") + return -1, -1, -1, -1 \ No newline at end of file diff --git a/library/sensors/sensors_stub_random.py b/library/sensors/sensors_stub_random.py index 07e37748..64fee830 100644 --- a/library/sensors/sensors_stub_random.py +++ b/library/sensors/sensors_stub_random.py @@ -20,7 +20,7 @@ # For all platforms (Linux, Windows, macOS) import random -from typing import Tuple +from typing import Tuple, List import library.sensors.sensors as sensors @@ -49,22 +49,26 @@ def fan_percent(fan_name: str = None) -> float: class Gpu(sensors.Gpu): @staticmethod - def stats() -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) - return random.uniform(0, 100), random.uniform(0, 100), random.uniform(300, 16000), 16000.0, random.uniform(30, - 90) + def stats() -> List[Tuple[float, float, float, float, float]]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) + gpu0 = (random.uniform(0, 100), random.uniform(0, 100), random.uniform(300, 16000), 16000.0, random.uniform(30, 90)) + gpu1 = (random.uniform(0, 100), random.uniform(0, 100), random.uniform(300, 16000), 16000.0, random.uniform(30, 90)) + return [gpu0, gpu1] @staticmethod - def fps() -> int: - return random.randint(20, 120) + def get_gpu_names() -> List[str]: + return ["Dummy GPU 0 (Rand)", "Dummy GPU 1 (Rand)"] @staticmethod - def fan_percent() -> float: - return random.uniform(0, 100) + def fps() -> List[int]: + return [random.randint(20, 120), random.randint(20, 120)] @staticmethod - def frequency() -> float: - return random.uniform(800, 3400) + def fan_percent() -> List[float]: + return [random.uniform(0, 100), random.uniform(0, 100)] + + @staticmethod + def frequency() -> List[float]: + return [random.uniform(800, 3400), random.uniform(800, 3400)] @staticmethod def is_available() -> bool: diff --git a/library/sensors/sensors_stub_static.py b/library/sensors/sensors_stub_static.py index 5aa16168..b3651922 100644 --- a/library/sensors/sensors_stub_static.py +++ b/library/sensors/sensors_stub_static.py @@ -20,7 +20,7 @@ # Useful for theme editor # For all platforms (Linux, Windows, macOS) -from typing import Tuple +from typing import Tuple, List import library.sensors.sensors as sensors @@ -32,11 +32,28 @@ CPU_FREQ_MHZ = 2400.0 DISK_TOTAL_SIZE_GB = 1000 MEMORY_TOTAL_SIZE_GB = 64 -GPU_MEM_TOTAL_SIZE_GB = 32 NETWORK_SPEED_BYTES = 1061000000 +GPU_MEM_TOTAL_SIZE_GB = 32 GPU_FPS = 120 GPU_FREQ_MHZ = 1500.0 +# Define first GPU values +GPU1_PERCENTAGE = PERCENTAGE_SENSOR_VALUE +GPU1_MEM_PERCENTAGE = PERCENTAGE_SENSOR_VALUE +GPU1_MEM_TOTAL_SIZE_GB = GPU_MEM_TOTAL_SIZE_GB +GPU1_TEMPERATURE = TEMPERATURE_SENSOR_VALUE +GPU1_FPS = GPU_FPS +GPU1_FREQ_MHZ = GPU_FREQ_MHZ +GPU1_FAN_PERCENT = PERCENTAGE_SENSOR_VALUE + +# Define second GPU values +GPU2_PERCENTAGE = PERCENTAGE_SENSOR_VALUE +GPU2_MEM_PERCENTAGE = PERCENTAGE_SENSOR_VALUE +GPU2_MEM_TOTAL_SIZE_GB = GPU_MEM_TOTAL_SIZE_GB +GPU2_TEMPERATURE = TEMPERATURE_SENSOR_VALUE +GPU2_FPS = GPU_FPS +GPU2_FREQ_MHZ = GPU_FREQ_MHZ +GPU2_FAN_PERCENT = PERCENTAGE_SENSOR_VALUE class Cpu(sensors.Cpu): @staticmethod @@ -62,25 +79,36 @@ def fan_percent(fan_name: str = None) -> float: class Gpu(sensors.Gpu): @staticmethod - def stats() -> Tuple[ - float, float, float, float, float]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) - return (PERCENTAGE_SENSOR_VALUE, - PERCENTAGE_SENSOR_VALUE, - GPU_MEM_TOTAL_SIZE_GB / 100 * PERCENTAGE_SENSOR_VALUE * 1024, - GPU_MEM_TOTAL_SIZE_GB * 1024, - TEMPERATURE_SENSOR_VALUE) + def stats() -> List[Tuple[float, float, float, float, float]]: # load (%) / used mem (%) / used mem (Mb) / total mem (Mb) / temp (°C) + gpu1 = (GPU1_PERCENTAGE, + GPU1_MEM_PERCENTAGE, + GPU1_MEM_TOTAL_SIZE_GB / 100 * GPU1_MEM_PERCENTAGE * 1024, + GPU1_MEM_TOTAL_SIZE_GB * 1024, + GPU1_TEMPERATURE) + + gpu2 = (GPU2_PERCENTAGE, + GPU2_MEM_PERCENTAGE, + GPU2_MEM_TOTAL_SIZE_GB / 100 * GPU2_MEM_PERCENTAGE * 1024, + GPU2_MEM_TOTAL_SIZE_GB * 1024, + GPU2_TEMPERATURE) + + return [gpu1, gpu2] @staticmethod - def fps() -> int: - return GPU_FPS + def get_gpu_names() -> List[str]: + return ["Dummy GPU 0 (Static)", "Dummy GPU 1 (Static)"] @staticmethod - def fan_percent() -> float: - return PERCENTAGE_SENSOR_VALUE + def fps() -> List[int]: + return [GPU1_FPS, GPU2_FPS] @staticmethod - def frequency() -> float: - return GPU_FREQ_MHZ + def fan_percent() -> List[float]: + return [GPU1_FAN_PERCENT, GPU2_FAN_PERCENT] + + @staticmethod + def frequency() -> List[float]: + return [GPU1_FREQ_MHZ, GPU2_FREQ_MHZ] @staticmethod def is_available() -> bool: @@ -123,4 +151,4 @@ class Net(sensors.Net): @staticmethod def stats(if_name, interval) -> Tuple[ int, int, int, int]: # up rate (B/s), uploaded (B), dl rate (B/s), downloaded (B) - return NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES + return NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES, NETWORK_SPEED_BYTES \ No newline at end of file diff --git a/library/stats.py b/library/stats.py index fa1e20ee..8c824fbf 100644 --- a/library/stats.py +++ b/library/stats.py @@ -26,7 +26,7 @@ import os import platform import sys -from typing import List +from typing import List, Tuple, Callable # Added List, Tuple, Callable import babel.dates import requests @@ -119,18 +119,20 @@ def display_themed_value(theme_data, value, min_size=0, unit=''): def display_themed_percent_value(theme_data, value): + display_val = 0 if math.isnan(value) else int(value) display_themed_value( theme_data=theme_data, - value=int(value), + value=display_val, min_size=3, unit="%" ) def display_themed_temperature_value(theme_data, value): + display_val = 0 if math.isnan(value) else int(value) display_themed_value( theme_data=theme_data, - value=int(value), + value=display_val, min_size=3, unit="°C" ) @@ -140,12 +142,13 @@ def display_themed_progress_bar(theme_data, value): if not theme_data.get("SHOW", False): return + display_val = 0 if math.isnan(value) else int(value) display.lcd.DisplayProgressBar( x=theme_data.get("X", 0), y=theme_data.get("Y", 0), width=theme_data.get("WIDTH", 0), height=theme_data.get("HEIGHT", 0), - value=int(value), + value=display_val, # Use the checked value min_value=theme_data.get("MIN_VALUE", 0), max_value=theme_data.get("MAX_VALUE", 100), bar_color=theme_data.get("BAR_COLOR", (0, 0, 0)), @@ -159,11 +162,12 @@ def display_themed_radial_bar(theme_data, value, min_size=0, unit='', custom_tex if not theme_data.get("SHOW", False): return + display_val = 0 if math.isnan(value) else value if theme_data.get("SHOW_TEXT", False): if custom_text: text = custom_text else: - text = f"{{:>{min_size}}}".format(value) + text = f"{{:>{min_size}}}".format(int(display_val) if not isinstance(display_val, str) else display_val) # Ensure int for formatting if not already string if theme_data.get("SHOW_UNIT", True) and unit: text += str(unit) else: @@ -181,7 +185,7 @@ def display_themed_radial_bar(theme_data, value, min_size=0, unit='', custom_tex angle_steps=theme_data.get("ANGLE_STEPS", 1), angle_sep=theme_data.get("ANGLE_SEP", 0), clockwise=theme_data.get("CLOCKWISE", False), - value=value, + value=display_val, bar_color=theme_data.get("BAR_COLOR", (0, 0, 0)), text=text, font=config.FONTS_DIR + theme_data.get("FONT", "roboto-mono/RobotoMono-Regular.ttf"), @@ -198,9 +202,10 @@ def display_themed_radial_bar(theme_data, value, min_size=0, unit='', custom_tex def display_themed_percent_radial_bar(theme_data, value): + display_val = 0 if math.isnan(value) else int(value) display_themed_radial_bar( theme_data=theme_data, - value=int(value), + value=display_val, unit="%", min_size=3 ) @@ -372,6 +377,16 @@ def fan_speed(cls): class Gpu: + loads = [] + memory_percentages = [] + memory_used_mbs = [] + total_memory_mbs = [] + temperatures = [] + fps_values = [] + fan_percents = [] + frequencies_ghz = [] + gpu_names = [] # Added GPU names storage array + last_values_gpu_percentage = [] last_values_gpu_mem_percentage = [] last_values_gpu_temperature = [] @@ -381,37 +396,155 @@ class Gpu: @classmethod def stats(cls): - load, memory_percentage, memory_used_mb, total_memory_mb, temperature = sensors.Gpu.stats() - fps = sensors.Gpu.fps() - fan_percent = sensors.Gpu.fan_percent() - freq_ghz = sensors.Gpu.frequency() / 1000 + """Main entry point for GPU statistics collection and display""" + # 1. Fetch data and prepare storage + num_gpus = cls._fetch_gpu_data() + + # 2. Process each GPU's data and update histories + for i in range(num_gpus): + cls._update_history(i) + + # 3. Render multi-GPU data (new structure with GPU0, GPU1, etc.) + cls._render_multi_gpu(num_gpus) + + # 4. Handle legacy/backward compatibility (first GPU) + if num_gpus > 0: + cls._render_legacy_gpu() + + @classmethod + def _fetch_gpu_data(cls): + """Fetch GPU data and resize storage if needed""" + all_stats_tuples = sensors.Gpu.stats() + all_fps = sensors.Gpu.fps() + all_fan_percent = sensors.Gpu.fan_percent() + all_frequency_mhz = sensors.Gpu.frequency() + all_names = sensors.Gpu.get_gpu_names() + + num_gpus = len(all_stats_tuples) + + if len(all_names) < num_gpus: + all_names.extend([f"GPU {i}" for i in range(len(all_names), num_gpus)]) + elif len(all_names) > num_gpus: + all_names = all_names[:num_gpus] + + if len(cls.loads) != num_gpus: + cls._resize_storage(num_gpus) + + for i in range(num_gpus): + if i < len(all_stats_tuples): + cls.loads[i], cls.memory_percentages[i], cls.memory_used_mbs[i], cls.total_memory_mbs[i], cls.temperatures[i] = all_stats_tuples[i] + else: + cls.loads[i], cls.memory_percentages[i], cls.memory_used_mbs[i], cls.total_memory_mbs[i], cls.temperatures[i] = [math.nan]*5 + + cls.fps_values[i] = all_fps[i] if i < len(all_fps) else -1 + cls.fan_percents[i] = all_fan_percent[i] if i < len(all_fan_percent) else math.nan + freq_mhz = all_frequency_mhz[i] if i < len(all_frequency_mhz) else math.nan + cls.frequencies_ghz[i] = freq_mhz / 1000.0 if freq_mhz is not None and not math.isnan(freq_mhz) else math.nan + cls.gpu_names[i] = all_names[i] + + return num_gpus + + @classmethod + def _render_multi_gpu(cls, num_gpus): + """Render metrics for multiple GPUs using the GPU0, GPU1, etc. structure""" + theme_gpu_main_data = config.THEME_DATA['STATS']['GPU'] + for i in range(num_gpus): + gpu_key = f"GPU{i}" + if gpu_key in theme_gpu_main_data: + gpu_theme_section = theme_gpu_main_data[gpu_key] + + # Render GPU name if configured in theme + if 'NAME' in gpu_theme_section and 'TEXT' in gpu_theme_section['NAME']: + display_themed_value(gpu_theme_section['NAME']['TEXT'], cls.gpu_names[i]) + + # Render stats using helper methods + cls._render_gpu_stat(gpu_theme_section, 'PERCENTAGE', cls.loads[i], + cls.last_values_gpu_percentage[i], + display_themed_percent_value, display_themed_percent_radial_bar) + + cls._render_gpu_stat(gpu_theme_section, 'MEMORY_PERCENT', cls.memory_percentages[i], + cls.last_values_gpu_mem_percentage[i], + display_themed_percent_value, display_themed_percent_radial_bar) + + cls._render_gpu_stat(gpu_theme_section, 'TEMPERATURE', cls.temperatures[i], + cls.last_values_gpu_temperature[i], + display_themed_temperature_value, display_themed_temperature_radial_bar) + + cls._render_gpu_stat(gpu_theme_section, 'FAN_SPEED', cls.fan_percents[i], + cls.last_values_gpu_fan_speed[i], + display_themed_percent_value, display_themed_percent_radial_bar) + + cls._render_gpu_stat_custom_format(gpu_theme_section, 'MEMORY_USED', + cls.memory_used_mbs[i], unit=" M", min_size=5) + + cls._render_gpu_stat_custom_format(gpu_theme_section, 'MEMORY_TOTAL', + cls.total_memory_mbs[i], unit=" M", min_size=5) + + cls._render_gpu_stat_custom_format(gpu_theme_section, 'FPS', cls.fps_values[i], + unit=" FPS", min_size=4, + history_list=cls.last_values_gpu_fps[i]) + + cls._render_gpu_stat_custom_format(gpu_theme_section, 'FREQUENCY', cls.frequencies_ghz[i], + unit=" GHz", min_size=4, format_str='{:.2f}', + history_list=cls.last_values_gpu_frequency[i]) + + @classmethod + def _render_legacy_gpu(cls): + """Render metrics for the first GPU in the legacy format for backward compatibility""" theme_gpu_data = config.THEME_DATA['STATS']['GPU'] + + # Extract data for the first GPU + load = cls.loads[0] + memory_percentage = cls.memory_percentages[0] + memory_used_mb = cls.memory_used_mbs[0] + total_memory_mb = cls.total_memory_mbs[0] + temperature = cls.temperatures[0] + fps = cls.fps_values[0] + fan_percent = cls.fan_percents[0] + freq_ghz = cls.frequencies_ghz[0] + + # Legacy memory section + cls._render_legacy_memory(theme_gpu_data, memory_percentage, memory_used_mb) + + # GPU load percentage + cls._render_legacy_percentage(theme_gpu_data, load) + + # GPU memory percentage + cls._render_legacy_memory_percent(theme_gpu_data, memory_percentage) + + # GPU memory used + cls._render_legacy_memory_used(theme_gpu_data, memory_used_mb) + + # GPU total memory + cls._render_legacy_memory_total(theme_gpu_data, total_memory_mb) + + # GPU temperature + cls._render_legacy_temperature(theme_gpu_data, temperature) + + # GPU FPS + cls._render_legacy_fps(theme_gpu_data, fps) + + # GPU fan speed + cls._render_legacy_fan_speed(theme_gpu_data, fan_percent) + + # GPU frequency + cls._render_legacy_frequency(theme_gpu_data, freq_ghz) - save_last_value(load, cls.last_values_gpu_percentage, - theme_gpu_data['PERCENTAGE']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - save_last_value(memory_percentage, cls.last_values_gpu_mem_percentage, - theme_gpu_data['MEMORY_PERCENT']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - save_last_value(temperature, cls.last_values_gpu_temperature, - theme_gpu_data['TEMPERATURE']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - save_last_value(fps, cls.last_values_gpu_fps, - theme_gpu_data['FPS']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - save_last_value(fan_percent, cls.last_values_gpu_fan_speed, - theme_gpu_data['FAN_SPEED']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - save_last_value(freq_ghz, cls.last_values_gpu_frequency, - theme_gpu_data['FREQUENCY']['LINE_GRAPH'].get("HISTORY_SIZE", DEFAULT_HISTORY_SIZE)) - - ################################ for backward compatibility only + @classmethod + def _render_legacy_memory(cls, theme_gpu_data, memory_percentage, memory_used_mb): + """Render legacy memory section""" gpu_mem_graph_data = theme_gpu_data['MEMORY']['GRAPH'] gpu_mem_radial_data = theme_gpu_data['MEMORY']['RADIAL'] + gpu_mem_text_data = theme_gpu_data['MEMORY']['TEXT'] + if math.isnan(memory_percentage): memory_percentage = 0 if gpu_mem_graph_data['SHOW'] or gpu_mem_radial_data['SHOW']: logger.warning("Your GPU memory relative usage (%) is not supported yet") gpu_mem_graph_data['SHOW'] = False gpu_mem_radial_data['SHOW'] = False - - gpu_mem_text_data = theme_gpu_data['MEMORY']['TEXT'] + if math.isnan(memory_used_mb): memory_used_mb = 0 if gpu_mem_text_data['SHOW']: @@ -426,9 +559,10 @@ def stats(cls): min_size=5, unit=" M" ) - ################################ end of backward compatibility only - # GPU usage (%) + @classmethod + def _render_legacy_percentage(cls, theme_gpu_data, load): + """Render legacy GPU load percentage""" gpu_percent_graph_data = theme_gpu_data['PERCENTAGE']['GRAPH'] gpu_percent_radial_data = theme_gpu_data['PERCENTAGE']['RADIAL'] gpu_percent_text_data = theme_gpu_data['PERCENTAGE']['TEXT'] @@ -447,9 +581,11 @@ def stats(cls): display_themed_progress_bar(gpu_percent_graph_data, load) display_themed_percent_radial_bar(gpu_percent_radial_data, load) display_themed_percent_value(gpu_percent_text_data, load) - display_themed_line_graph(gpu_percent_line_graph_data, cls.last_values_gpu_percentage) + display_themed_line_graph(gpu_percent_line_graph_data, cls.last_values_gpu_percentage[0]) - # GPU mem. usage (%) + @classmethod + def _render_legacy_memory_percent(cls, theme_gpu_data, memory_percentage): + """Render legacy GPU memory percentage""" gpu_mem_percent_graph_data = theme_gpu_data['MEMORY_PERCENT']['GRAPH'] gpu_mem_percent_radial_data = theme_gpu_data['MEMORY_PERCENT']['RADIAL'] gpu_mem_percent_text_data = theme_gpu_data['MEMORY_PERCENT']['TEXT'] @@ -467,9 +603,11 @@ def stats(cls): display_themed_progress_bar(gpu_mem_percent_graph_data, memory_percentage) display_themed_percent_radial_bar(gpu_mem_percent_radial_data, memory_percentage) display_themed_percent_value(gpu_mem_percent_text_data, memory_percentage) - display_themed_line_graph(gpu_mem_percent_line_graph_data, cls.last_values_gpu_mem_percentage) + display_themed_line_graph(gpu_mem_percent_line_graph_data, cls.last_values_gpu_mem_percentage[0]) - # GPU mem. absolute usage (M) + @classmethod + def _render_legacy_memory_used(cls, theme_gpu_data, memory_used_mb): + """Render legacy GPU memory used""" gpu_mem_used_text_data = theme_gpu_data['MEMORY_USED']['TEXT'] if math.isnan(memory_used_mb): memory_used_mb = 0 @@ -484,7 +622,9 @@ def stats(cls): unit=" M" ) - # GPU mem. total memory (M) + @classmethod + def _render_legacy_memory_total(cls, theme_gpu_data, total_memory_mb): + """Render legacy GPU total memory""" gpu_mem_total_text_data = theme_gpu_data['MEMORY_TOTAL']['TEXT'] if math.isnan(total_memory_mb): total_memory_mb = 0 @@ -495,11 +635,13 @@ def stats(cls): display_themed_value( theme_data=gpu_mem_total_text_data, value=int(total_memory_mb), - min_size=5, # Adjust min_size as necessary for your display - unit=" M" # Assuming the unit is in Megabytes + min_size=5, + unit=" M" ) - # GPU temperature (°C) + @classmethod + def _render_legacy_temperature(cls, theme_gpu_data, temperature): + """Render legacy GPU temperature""" gpu_temp_text_data = theme_gpu_data['TEMPERATURE']['TEXT'] gpu_temp_radial_data = theme_gpu_data['TEMPERATURE']['RADIAL'] gpu_temp_graph_data = theme_gpu_data['TEMPERATURE']['GRAPH'] @@ -518,9 +660,11 @@ def stats(cls): display_themed_temperature_value(gpu_temp_text_data, temperature) display_themed_progress_bar(gpu_temp_graph_data, temperature) display_themed_temperature_radial_bar(gpu_temp_radial_data, temperature) - display_themed_line_graph(gpu_temp_line_graph_data, cls.last_values_gpu_temperature) + display_themed_line_graph(gpu_temp_line_graph_data, cls.last_values_gpu_temperature[0]) - # GPU FPS + @classmethod + def _render_legacy_fps(cls, theme_gpu_data, fps): + """Render legacy GPU FPS""" gpu_fps_text_data = theme_gpu_data['FPS']['TEXT'] gpu_fps_radial_data = theme_gpu_data['FPS']['RADIAL'] gpu_fps_graph_data = theme_gpu_data['FPS']['GRAPH'] @@ -549,9 +693,11 @@ def stats(cls): min_size=4, unit=" FPS" ) - display_themed_line_graph(gpu_fps_line_graph_data, cls.last_values_gpu_fps) + display_themed_line_graph(gpu_fps_line_graph_data, cls.last_values_gpu_fps[0]) - # GPU Fan Speed (%) + @classmethod + def _render_legacy_fan_speed(cls, theme_gpu_data, fan_percent): + """Render legacy GPU fan speed""" gpu_fan_text_data = theme_gpu_data['FAN_SPEED']['TEXT'] gpu_fan_radial_data = theme_gpu_data['FAN_SPEED']['RADIAL'] gpu_fan_graph_data = theme_gpu_data['FAN_SPEED']['GRAPH'] @@ -570,13 +716,16 @@ def stats(cls): display_themed_percent_value(gpu_fan_text_data, fan_percent) display_themed_progress_bar(gpu_fan_graph_data, fan_percent) display_themed_percent_radial_bar(gpu_fan_radial_data, fan_percent) - display_themed_line_graph(gpu_fan_line_graph_data, cls.last_values_gpu_fan_speed) + display_themed_line_graph(gpu_fan_line_graph_data, cls.last_values_gpu_fan_speed[0]) - # GPU Frequency (Ghz) + @classmethod + def _render_legacy_frequency(cls, theme_gpu_data, freq_ghz): + """Render legacy GPU frequency""" gpu_freq_text_data = theme_gpu_data['FREQUENCY']['TEXT'] gpu_freq_radial_data = theme_gpu_data['FREQUENCY']['RADIAL'] gpu_freq_graph_data = theme_gpu_data['FREQUENCY']['GRAPH'] gpu_freq_line_graph_data = theme_gpu_data['FREQUENCY']['LINE_GRAPH'] + display_themed_value( theme_data=gpu_freq_text_data, value=f'{freq_ghz:.2f}', @@ -590,13 +739,101 @@ def stats(cls): unit=" GHz", min_size=4 ) - display_themed_line_graph(gpu_freq_line_graph_data, cls.last_values_gpu_frequency) + display_themed_line_graph(gpu_freq_line_graph_data, cls.last_values_gpu_frequency[0]) + + @classmethod + def _resize_storage(cls, num_gpus): + """ Helper to initialize/resize GPU storage lists """ + logger.info(f"Resizing GPU storage for {num_gpus} GPU(s).") + default_hist_size = DEFAULT_HISTORY_SIZE + try: + default_hist_size = config.THEME_DATA['STATS']['GPU'].get('GPU0',{}).get('PERCENTAGE',{}).get('LINE_GRAPH',{}).get('HISTORY_SIZE', DEFAULT_HISTORY_SIZE) + except: + pass + + # Initialize value lists + cls.loads = [math.nan] * num_gpus + cls.memory_percentages = [math.nan] * num_gpus + cls.memory_used_mbs = [math.nan] * num_gpus + cls.total_memory_mbs = [math.nan] * num_gpus + cls.temperatures = [math.nan] * num_gpus + cls.fps_values = [-1] * num_gpus + cls.fan_percents = [math.nan] * num_gpus + cls.frequencies_ghz = [math.nan] * num_gpus + cls.gpu_names = [""] * num_gpus # Initialize GPU names array + + # Initialize history lists + cls.last_values_gpu_percentage = [last_values_list(default_hist_size) for _ in range(num_gpus)] + cls.last_values_gpu_mem_percentage = [last_values_list(default_hist_size) for _ in range(num_gpus)] + cls.last_values_gpu_temperature = [last_values_list(default_hist_size) for _ in range(num_gpus)] + cls.last_values_gpu_fps = [last_values_list(default_hist_size) for _ in range(num_gpus)] + cls.last_values_gpu_fan_speed = [last_values_list(default_hist_size) for _ in range(num_gpus)] + cls.last_values_gpu_frequency = [last_values_list(default_hist_size) for _ in range(num_gpus)] + + + @classmethod + def _update_history(cls, gpu_index): + """ Helper to update history for a specific GPU """ + if gpu_index >= len(cls.last_values_gpu_percentage): + return # Safety check + + hist_size = len(cls.last_values_gpu_percentage[gpu_index]) # Get size from list itself + + save_last_value(cls.loads[gpu_index], cls.last_values_gpu_percentage[gpu_index], hist_size) + save_last_value(cls.memory_percentages[gpu_index], cls.last_values_gpu_mem_percentage[gpu_index], hist_size) + save_last_value(cls.temperatures[gpu_index], cls.last_values_gpu_temperature[gpu_index], hist_size) + save_last_value(float(cls.fps_values[gpu_index]), cls.last_values_gpu_fps[gpu_index], hist_size) + save_last_value(cls.fan_percents[gpu_index], cls.last_values_gpu_fan_speed[gpu_index], hist_size) + save_last_value(cls.frequencies_ghz[gpu_index], cls.last_values_gpu_frequency[gpu_index], hist_size) + + @classmethod + def _render_gpu_stat(cls, gpu_theme_section, stat_key, value, history_list, text_func, radial_func): + """ Helper to render common stat types """ + # Method remains unchanged + if stat_key in gpu_theme_section: + theme_def = gpu_theme_section[stat_key] + if theme_def: # Check if theme definition exists + if 'TEXT' in theme_def: + text_func(theme_def.get('TEXT',{}), value) + if 'GRAPH' in theme_def: + display_themed_progress_bar(theme_def.get('GRAPH',{}), value) + if 'RADIAL' in theme_def: + radial_func(theme_def.get('RADIAL',{}), value) + if 'LINE_GRAPH' in theme_def: + display_themed_line_graph(theme_def.get('LINE_GRAPH',{}), history_list) + + @classmethod + def _render_gpu_stat_custom_format(cls, gpu_theme_section, stat_key, value, unit, min_size, format_str='{}', history_list=None): + """ Helper to render stats requiring specific formatting """ + if stat_key in gpu_theme_section: + theme_def = gpu_theme_section[stat_key] # Check if theme definition exists + + if theme_def: + is_nan_value = value is None or math.isnan(value) + display_value = 0 if is_nan_value else value + + try: # Attempt to format the value + formatted_val_str = "N/A" if is_nan_value else format_str.format(value) + except: + formatted_val_str = "N/A" + + if 'TEXT' in theme_def: + display_themed_value(theme_def.get('TEXT',{}), formatted_val_str, unit=unit, min_size=min_size) + if 'GRAPH' in theme_def: + display_themed_progress_bar(theme_def.get('GRAPH',{}), display_value) + if 'RADIAL' in theme_def: + radial_text = "N/A" + if not is_nan_value: # Only format if value is valid + radial_text = f"{formatted_val_str}{unit}" if theme_def.get('RADIAL',{}).get("SHOW_UNIT", True) else formatted_val_str + display_themed_radial_bar(theme_def.get('RADIAL',{}), display_value, custom_text=radial_text) # Pass the potentially 0 value + if 'LINE_GRAPH' in theme_def and history_list is not None: + # Note: save_last_value already handles appending NaN to history + display_themed_line_graph(theme_def.get('LINE_GRAPH',{}), history_list) @staticmethod def is_available(): return sensors.Gpu.is_available() - class Memory: last_values_memory_swap = [] last_values_memory_virtual = [] @@ -940,4 +1177,4 @@ def stats(cls): unit="ms", min_size=6 ) - display_themed_line_graph(theme_data['LINE_GRAPH'], cls.last_values_ping) + display_themed_line_graph(theme_data['LINE_GRAPH'], cls.last_values_ping) \ No newline at end of file From 2cab83ddbadea6b768d40ee422177772b95d2de8 Mon Sep 17 00:00:00 2001 From: Sanghyun Na <106161726+sanghyunna@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:33:12 +0900 Subject: [PATCH 2/5] Update default.yaml to support dual GPUs --- res/themes/default.yaml | 79 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/res/themes/default.yaml b/res/themes/default.yaml index a915bcb6..3a0a4924 100644 --- a/res/themes/default.yaml +++ b/res/themes/default.yaml @@ -54,7 +54,72 @@ STATS: LINE_GRAPH: SHOW: False GPU: - INTERVAL: 0 + INTERVAL: 0 # Interval applies to fetching data for ALL GPUs + + # --- Multi-GPU Structure Defaults --- + GPU0: # Default structure for the first detected GPU (index 0) + PERCENTAGE: + GRAPH: + SHOW: False + RADIAL: + SHOW: False + TEXT: + SHOW: False + LINE_GRAPH: + SHOW: False + MEMORY_PERCENT: + GRAPH: + SHOW: False + RADIAL: + SHOW: False + TEXT: + SHOW: False + LINE_GRAPH: + SHOW: False + MEMORY_USED: + TEXT: + SHOW: False + MEMORY_TOTAL: + TEXT: + SHOW: False + TEMPERATURE: + TEXT: + SHOW: False + GRAPH: + SHOW: False + RADIAL: + SHOW: False + LINE_GRAPH: + SHOW: False + FPS: + TEXT: + SHOW: False + GRAPH: + SHOW: False + RADIAL: + SHOW: False + LINE_GRAPH: + SHOW: False + FAN_SPEED: + TEXT: + SHOW: False + GRAPH: + SHOW: False + RADIAL: + SHOW: False + LINE_GRAPH: + SHOW: False + FREQUENCY: + TEXT: + SHOW: False + GRAPH: + SHOW: False + RADIAL: + SHOW: False + LINE_GRAPH: + SHOW: False + + # --- Single-GPU Structure Defaults (kept for backwards compatibility) --- PERCENTAGE: GRAPH: SHOW: False @@ -122,6 +187,7 @@ STATS: SHOW: False LINE_GRAPH: SHOW: False + MEMORY: INTERVAL: 0 SWAP: @@ -139,11 +205,14 @@ STATS: LINE_GRAPH: SHOW: False USED: - SHOW: False + TEXT: + SHOW: False FREE: - SHOW: False + TEXT: + SHOW: False TOTAL: - SHOW: False + TEXT: + SHOW: False PERCENT_TEXT: SHOW: False DISK: @@ -245,4 +314,4 @@ STATS: TEXT: SHOW: False CUSTOM: - INTERVAL: 0 + INTERVAL: 0 \ No newline at end of file From 11a55e18ec78f8ef3882204be8ba65f4c1aea86e Mon Sep 17 00:00:00 2001 From: Sanghyun Na <106161726+sanghyunna@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:33:34 +0900 Subject: [PATCH 3/5] Theme for debugging dual GPU envs --- config.yaml | 13 +- res/themes/MultiGPUTest_3.5/background.jpg | Bin 0 -> 15652 bytes res/themes/MultiGPUTest_3.5/theme.yaml | 377 +++++++++++++++++++++ 3 files changed, 383 insertions(+), 7 deletions(-) create mode 100644 res/themes/MultiGPUTest_3.5/background.jpg create mode 100644 res/themes/MultiGPUTest_3.5/theme.yaml diff --git a/config.yaml b/config.yaml index cc2958b2..e20bce36 100644 --- a/config.yaml +++ b/config.yaml @@ -1,16 +1,15 @@ ---- config: # Configuration values to set up basic communication # Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux... # Use AUTO for COM port auto-discovery (may not work on every setup) # COM_PORT: "/dev/ttyACM0" # COM_PORT: "COM3" - COM_PORT: "AUTO" + COM_PORT: AUTO # Theme to use (located in res/themes) # Use the name of the folder as value # Choose a theme made for your screen size (see DISPLAY_SIZE inside theme.yaml) - THEME: 3.5inchTheme2 + THEME: MultiGPUTest_3.5 # Hardware sensors reading # Choose the appropriate method for reading your hardware sensors: @@ -24,8 +23,8 @@ config: # Linux/MacOS interfaces are named "eth0", "wlan0", "wlp1s0", "enp2s0"... # For Windows use the interfaces pretty name: "Ethernet 2", "Wi-Fi", ... # Leave the fields empty if the card does not exist on your setup - ETH: "" # Ethernet Card - WLO: "" # Wi-Fi Card + ETH: '' # Ethernet Card + WLO: '' # Wi-Fi Card # CPU fan # For Linux/MacOS platforms, the CPU fan is amongst all fan sensors gathered from the motherboard chipset @@ -41,7 +40,7 @@ config: # OpenWeatherMap API KEY. Can be obtained by creating a free account on https://home.openweathermap.org/users/sign_up. # You need to subscribe to the 3.0 OneCallAPI that has 1000 free daily calls - WEATHER_API_KEY: "" + WEATHER_API_KEY: '' # Location from which to display the weather. Use for example https://www.latlong.net/ to get latitude/longitude WEATHER_LATITUDE: 45.75 WEATHER_LONGITUDE: 4.85 @@ -63,7 +62,7 @@ display: # Display Brightness # Set this as the desired %, 0 being completely dark and 100 being max brightness # Warning: revision A display can get hot at high brightness! - BRIGHTNESS: 20 + BRIGHTNESS: 50 # Display reverse: true/false # Set to true to reverse display orientation (landscape <-> reverse landscape, portrait <-> reverse portrait) diff --git a/res/themes/MultiGPUTest_3.5/background.jpg b/res/themes/MultiGPUTest_3.5/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..18e5a18927ce58242a9f1b59c73d4ad389c50dbf GIT binary patch literal 15652 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 literal 0 HcmV?d00001 diff --git a/res/themes/MultiGPUTest_3.5/theme.yaml b/res/themes/MultiGPUTest_3.5/theme.yaml new file mode 100644 index 00000000..7e4c383a --- /dev/null +++ b/res/themes/MultiGPUTest_3.5/theme.yaml @@ -0,0 +1,377 @@ +# res/themes/MultiGPUTest/theme.yaml +# Multi-GPU 기능 테스트를 위한 개선된 가독성의 테마. +# GPU 0 및 GPU 1의 텍스트 Value과 Name을 표시합니다. +--- +author: "sanghyunna" + +display: + DISPLAY_SIZE: 3.5" + DISPLAY_ORIENTATION: portrait + DISPLAY_RGB_LED: 0, 0, 0 + +static_images: + BACKGROUND: + PATH: background.jpg + X: 0 + Y: 0 + WIDTH: 320 + HEIGHT: 480 + +static_text: + # --- GPU 0 Label --- + GPU0_TITLE: + TEXT: "GPU 0 Stats:" + X: 10 + Y: 10 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 14 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_NAME_LBL: # *** Name Label *** + TEXT: " Name :" + X: 10 + Y: 35 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_LOAD_LBL: + TEXT: " Load :" + X: 10 + Y: 55 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_MEM_LBL: + TEXT: " Memory (%) :" + X: 10 + Y: 75 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_TEMP_LBL: + TEXT: " Temp (°C) :" + X: 10 + Y: 95 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FAN_LBL: + TEXT: " Fan (%) :" + X: 10 + Y: 115 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FREQ_LBL: + TEXT: " Freq (GHz) :" + X: 10 + Y: 135 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FPS_LBL: + TEXT: " FPS :" + X: 10 + Y: 155 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + + # --- GPU 1 Label --- + GPU1_TITLE: + TEXT: "GPU 1 Stats:" + X: 10 + Y: 200 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 14 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_NAME_LBL: # *** Name Label *** + TEXT: " Name :" + X: 10 + Y: 225 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_LOAD_LBL: + TEXT: " Load :" + X: 10 + Y: 245 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_MEM_LBL: + TEXT: " Memory (%) :" + X: 10 + Y: 265 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_TEMP_LBL: + TEXT: " Temp (°C) :" + X: 10 + Y: 285 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FAN_LBL: + TEXT: " Fan (%) :" + X: 10 + Y: 305 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FREQ_LBL: + TEXT: " Freq (GHz) :" + X: 10 + Y: 325 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FPS_LBL: + TEXT: " FPS :" + X: 10 + Y: 345 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + +STATS: + GPU: + INTERVAL: 1 + + # --- GPU 0 Value --- + GPU0: + NAME: # *** Name Value *** + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 35 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + WIDTH: 180 + HEIGHT: 15 + ALIGN: left + ANCHOR: lt + PERCENTAGE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 55 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + MEMORY_PERCENT: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 75 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + TEMPERATURE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 95 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FAN_SPEED: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 115 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FREQUENCY: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 135 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FPS: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 155 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 4 + ALIGN: left + ANCHOR: lt + + # --- GPU 1 Value --- + GPU1: + NAME: # *** Name Value *** + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 225 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + WIDTH: 180 + HEIGHT: 15 + ALIGN: left + ANCHOR: lt + PERCENTAGE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 245 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + MEMORY_PERCENT: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 265 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + TEMPERATURE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 285 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FAN_SPEED: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 305 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FREQUENCY: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 325 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FPS: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 345 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 4 + ALIGN: left + ANCHOR: lt \ No newline at end of file From 333c3374dc6882e962396ee512a5c0d9ac4caecb Mon Sep 17 00:00:00 2001 From: Sanghyun Na <106161726+sanghyunna@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:38:11 +0900 Subject: [PATCH 4/5] Revert "Theme for debugging dual GPU envs" This reverts commit 11a55e18ec78f8ef3882204be8ba65f4c1aea86e. --- config.yaml | 13 +- res/themes/MultiGPUTest_3.5/background.jpg | Bin 15652 -> 0 bytes res/themes/MultiGPUTest_3.5/theme.yaml | 377 --------------------- 3 files changed, 7 insertions(+), 383 deletions(-) delete mode 100644 res/themes/MultiGPUTest_3.5/background.jpg delete mode 100644 res/themes/MultiGPUTest_3.5/theme.yaml diff --git a/config.yaml b/config.yaml index e20bce36..cc2958b2 100644 --- a/config.yaml +++ b/config.yaml @@ -1,15 +1,16 @@ +--- config: # Configuration values to set up basic communication # Set your COM port e.g. COM3 for Windows, /dev/ttyACM0 for Linux... # Use AUTO for COM port auto-discovery (may not work on every setup) # COM_PORT: "/dev/ttyACM0" # COM_PORT: "COM3" - COM_PORT: AUTO + COM_PORT: "AUTO" # Theme to use (located in res/themes) # Use the name of the folder as value # Choose a theme made for your screen size (see DISPLAY_SIZE inside theme.yaml) - THEME: MultiGPUTest_3.5 + THEME: 3.5inchTheme2 # Hardware sensors reading # Choose the appropriate method for reading your hardware sensors: @@ -23,8 +24,8 @@ config: # Linux/MacOS interfaces are named "eth0", "wlan0", "wlp1s0", "enp2s0"... # For Windows use the interfaces pretty name: "Ethernet 2", "Wi-Fi", ... # Leave the fields empty if the card does not exist on your setup - ETH: '' # Ethernet Card - WLO: '' # Wi-Fi Card + ETH: "" # Ethernet Card + WLO: "" # Wi-Fi Card # CPU fan # For Linux/MacOS platforms, the CPU fan is amongst all fan sensors gathered from the motherboard chipset @@ -40,7 +41,7 @@ config: # OpenWeatherMap API KEY. Can be obtained by creating a free account on https://home.openweathermap.org/users/sign_up. # You need to subscribe to the 3.0 OneCallAPI that has 1000 free daily calls - WEATHER_API_KEY: '' + WEATHER_API_KEY: "" # Location from which to display the weather. Use for example https://www.latlong.net/ to get latitude/longitude WEATHER_LATITUDE: 45.75 WEATHER_LONGITUDE: 4.85 @@ -62,7 +63,7 @@ display: # Display Brightness # Set this as the desired %, 0 being completely dark and 100 being max brightness # Warning: revision A display can get hot at high brightness! - BRIGHTNESS: 50 + BRIGHTNESS: 20 # Display reverse: true/false # Set to true to reverse display orientation (landscape <-> reverse landscape, portrait <-> reverse portrait) diff --git a/res/themes/MultiGPUTest_3.5/background.jpg b/res/themes/MultiGPUTest_3.5/background.jpg deleted file mode 100644 index 18e5a18927ce58242a9f1b59c73d4ad389c50dbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15652 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 diff --git a/res/themes/MultiGPUTest_3.5/theme.yaml b/res/themes/MultiGPUTest_3.5/theme.yaml deleted file mode 100644 index 7e4c383a..00000000 --- a/res/themes/MultiGPUTest_3.5/theme.yaml +++ /dev/null @@ -1,377 +0,0 @@ -# res/themes/MultiGPUTest/theme.yaml -# Multi-GPU 기능 테스트를 위한 개선된 가독성의 테마. -# GPU 0 및 GPU 1의 텍스트 Value과 Name을 표시합니다. ---- -author: "sanghyunna" - -display: - DISPLAY_SIZE: 3.5" - DISPLAY_ORIENTATION: portrait - DISPLAY_RGB_LED: 0, 0, 0 - -static_images: - BACKGROUND: - PATH: background.jpg - X: 0 - Y: 0 - WIDTH: 320 - HEIGHT: 480 - -static_text: - # --- GPU 0 Label --- - GPU0_TITLE: - TEXT: "GPU 0 Stats:" - X: 10 - Y: 10 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 14 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_NAME_LBL: # *** Name Label *** - TEXT: " Name :" - X: 10 - Y: 35 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_LOAD_LBL: - TEXT: " Load :" - X: 10 - Y: 55 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_MEM_LBL: - TEXT: " Memory (%) :" - X: 10 - Y: 75 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_TEMP_LBL: - TEXT: " Temp (°C) :" - X: 10 - Y: 95 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_FAN_LBL: - TEXT: " Fan (%) :" - X: 10 - Y: 115 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_FREQ_LBL: - TEXT: " Freq (GHz) :" - X: 10 - Y: 135 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU0_FPS_LBL: - TEXT: " FPS :" - X: 10 - Y: 155 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - - # --- GPU 1 Label --- - GPU1_TITLE: - TEXT: "GPU 1 Stats:" - X: 10 - Y: 200 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 14 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_NAME_LBL: # *** Name Label *** - TEXT: " Name :" - X: 10 - Y: 225 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_LOAD_LBL: - TEXT: " Load :" - X: 10 - Y: 245 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_MEM_LBL: - TEXT: " Memory (%) :" - X: 10 - Y: 265 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_TEMP_LBL: - TEXT: " Temp (°C) :" - X: 10 - Y: 285 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_FAN_LBL: - TEXT: " Fan (%) :" - X: 10 - Y: 305 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_FREQ_LBL: - TEXT: " Freq (GHz) :" - X: 10 - Y: 325 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - GPU1_FPS_LBL: - TEXT: " FPS :" - X: 10 - Y: 345 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 200, 200, 200 - BACKGROUND_IMAGE: background.jpg - ALIGN: left - ANCHOR: lt - -STATS: - GPU: - INTERVAL: 1 - - # --- GPU 0 Value --- - GPU0: - NAME: # *** Name Value *** - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 35 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - WIDTH: 180 - HEIGHT: 15 - ALIGN: left - ANCHOR: lt - PERCENTAGE: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 55 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - MEMORY_PERCENT: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 75 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - TEMPERATURE: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 95 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FAN_SPEED: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 115 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FREQUENCY: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 135 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FPS: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 155 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 4 - ALIGN: left - ANCHOR: lt - - # --- GPU 1 Value --- - GPU1: - NAME: # *** Name Value *** - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 225 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - WIDTH: 180 - HEIGHT: 15 - ALIGN: left - ANCHOR: lt - PERCENTAGE: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 245 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - MEMORY_PERCENT: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 265 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - TEMPERATURE: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 285 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FAN_SPEED: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 305 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FREQUENCY: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 325 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 5 - ALIGN: left - ANCHOR: lt - FPS: - TEXT: - SHOW: True - SHOW_UNIT: False - X: 130 - Y: 345 - FONT: roboto-mono/RobotoMono-Regular.ttf - FONT_SIZE: 12 - FONT_COLOR: 255, 255, 255 - BACKGROUND_IMAGE: background.jpg - MIN_SIZE: 4 - ALIGN: left - ANCHOR: lt \ No newline at end of file From f1b829e1f0710613b6e01c71902ab99c37be40a5 Mon Sep 17 00:00:00 2001 From: Sanghyun Na <106161726+sanghyunna@users.noreply.github.com> Date: Thu, 3 Apr 2025 23:43:34 +0900 Subject: [PATCH 5/5] Restore MultiGPUTest theme files accidentally removed by revert --- res/themes/MultiGPUTest_3.5/background.jpg | Bin 0 -> 15652 bytes res/themes/MultiGPUTest_3.5/theme.yaml | 377 +++++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 res/themes/MultiGPUTest_3.5/background.jpg create mode 100644 res/themes/MultiGPUTest_3.5/theme.yaml diff --git a/res/themes/MultiGPUTest_3.5/background.jpg b/res/themes/MultiGPUTest_3.5/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..18e5a18927ce58242a9f1b59c73d4ad389c50dbf GIT binary patch literal 15652 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 literal 0 HcmV?d00001 diff --git a/res/themes/MultiGPUTest_3.5/theme.yaml b/res/themes/MultiGPUTest_3.5/theme.yaml new file mode 100644 index 00000000..7e4c383a --- /dev/null +++ b/res/themes/MultiGPUTest_3.5/theme.yaml @@ -0,0 +1,377 @@ +# res/themes/MultiGPUTest/theme.yaml +# Multi-GPU 기능 테스트를 위한 개선된 가독성의 테마. +# GPU 0 및 GPU 1의 텍스트 Value과 Name을 표시합니다. +--- +author: "sanghyunna" + +display: + DISPLAY_SIZE: 3.5" + DISPLAY_ORIENTATION: portrait + DISPLAY_RGB_LED: 0, 0, 0 + +static_images: + BACKGROUND: + PATH: background.jpg + X: 0 + Y: 0 + WIDTH: 320 + HEIGHT: 480 + +static_text: + # --- GPU 0 Label --- + GPU0_TITLE: + TEXT: "GPU 0 Stats:" + X: 10 + Y: 10 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 14 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_NAME_LBL: # *** Name Label *** + TEXT: " Name :" + X: 10 + Y: 35 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_LOAD_LBL: + TEXT: " Load :" + X: 10 + Y: 55 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_MEM_LBL: + TEXT: " Memory (%) :" + X: 10 + Y: 75 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_TEMP_LBL: + TEXT: " Temp (°C) :" + X: 10 + Y: 95 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FAN_LBL: + TEXT: " Fan (%) :" + X: 10 + Y: 115 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FREQ_LBL: + TEXT: " Freq (GHz) :" + X: 10 + Y: 135 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU0_FPS_LBL: + TEXT: " FPS :" + X: 10 + Y: 155 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + + # --- GPU 1 Label --- + GPU1_TITLE: + TEXT: "GPU 1 Stats:" + X: 10 + Y: 200 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 14 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_NAME_LBL: # *** Name Label *** + TEXT: " Name :" + X: 10 + Y: 225 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_LOAD_LBL: + TEXT: " Load :" + X: 10 + Y: 245 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_MEM_LBL: + TEXT: " Memory (%) :" + X: 10 + Y: 265 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_TEMP_LBL: + TEXT: " Temp (°C) :" + X: 10 + Y: 285 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FAN_LBL: + TEXT: " Fan (%) :" + X: 10 + Y: 305 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FREQ_LBL: + TEXT: " Freq (GHz) :" + X: 10 + Y: 325 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + GPU1_FPS_LBL: + TEXT: " FPS :" + X: 10 + Y: 345 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 200, 200, 200 + BACKGROUND_IMAGE: background.jpg + ALIGN: left + ANCHOR: lt + +STATS: + GPU: + INTERVAL: 1 + + # --- GPU 0 Value --- + GPU0: + NAME: # *** Name Value *** + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 35 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + WIDTH: 180 + HEIGHT: 15 + ALIGN: left + ANCHOR: lt + PERCENTAGE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 55 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + MEMORY_PERCENT: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 75 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + TEMPERATURE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 95 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FAN_SPEED: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 115 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FREQUENCY: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 135 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FPS: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 155 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 4 + ALIGN: left + ANCHOR: lt + + # --- GPU 1 Value --- + GPU1: + NAME: # *** Name Value *** + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 225 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + WIDTH: 180 + HEIGHT: 15 + ALIGN: left + ANCHOR: lt + PERCENTAGE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 245 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + MEMORY_PERCENT: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 265 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + TEMPERATURE: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 285 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FAN_SPEED: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 305 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FREQUENCY: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 325 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 5 + ALIGN: left + ANCHOR: lt + FPS: + TEXT: + SHOW: True + SHOW_UNIT: False + X: 130 + Y: 345 + FONT: roboto-mono/RobotoMono-Regular.ttf + FONT_SIZE: 12 + FONT_COLOR: 255, 255, 255 + BACKGROUND_IMAGE: background.jpg + MIN_SIZE: 4 + ALIGN: left + ANCHOR: lt \ No newline at end of file