#nullable enable using System; using System.Globalization; using LibreHardwareMonitor.Hardware; public class Telemetry : IDisposable { private const int UpdateRateDefaultMs = 1000; public int UpdateRateMs => UpdateRateDefaultMs; private readonly UdpSender udp = new UdpSender(); private Computer computer = new Computer(); private IHardware? cpuHw; private IHardware? gpuHw; private IHardware? memHw; private ISensor[] cpuLoadSensors = Array.Empty(); private ISensor? cpuTempSensor; private ISensor? gpu3DLoadSensor; private ISensor? gpuTempSensor; private ISensor? gpuVramUsedSensor; private ISensor? gpuVramTotalSensor; private ISensor? memUsedSensor; private ISensor? memAvailSensor; private static readonly CultureInfo CI = CultureInfo.InvariantCulture; // Restart LHM every 30 minutes private readonly TimeSpan restartInterval = TimeSpan.FromMinutes(30); private DateTime lastRestart = DateTime.UtcNow; public void Initialize() { ConfigureComputer(); computer.Open(); CacheHardwareAndSensors(); } private void ConfigureComputer() { computer.IsCpuEnabled = true; computer.IsGpuEnabled = true; computer.IsMemoryEnabled = true; // True minimal mode computer.IsMotherboardEnabled = false; computer.IsControllerEnabled = false; computer.IsNetworkEnabled = false; computer.IsStorageEnabled = false; computer.IsBatteryEnabled = false; } private void RestartComputerIfNeeded() { if (DateTime.UtcNow - lastRestart < restartInterval) return; lastRestart = DateTime.UtcNow; try { computer.Close(); } catch { // ignore } computer = new Computer(); ConfigureComputer(); computer.Open(); CacheHardwareAndSensors(); } private void CacheHardwareAndSensors() { cpuHw = null; gpuHw = null; memHw = null; cpuLoadSensors = Array.Empty(); cpuTempSensor = null; gpu3DLoadSensor = null; gpuTempSensor = null; gpuVramUsedSensor = null; gpuVramTotalSensor = null; memUsedSensor = null; memAvailSensor = null; foreach (var hw in computer.Hardware) { hw.Update(); switch (hw.HardwareType) { case HardwareType.Cpu: cpuHw = hw; CacheCpuSensors(hw); break; case HardwareType.GpuNvidia: case HardwareType.GpuAmd: case HardwareType.GpuIntel: gpuHw = hw; CacheGpuSensors(hw); break; case HardwareType.Memory: memHw = hw; CacheMemorySensors(hw); break; } } } private void CacheCpuSensors(IHardware hw) { var loads = new System.Collections.Generic.List(); foreach (var s in hw.Sensors) { if (s.SensorType == SensorType.Load && s.Name.Contains("CPU Core")) loads.Add(s); if (s.SensorType == SensorType.Temperature) { if (s.Name == "Core (Tctl/Tdie)") cpuTempSensor = s; else if (cpuTempSensor == null) cpuTempSensor = s; } } cpuLoadSensors = loads.ToArray(); } private void CacheGpuSensors(IHardware hw) { foreach (var s in hw.Sensors) { if (s.SensorType == SensorType.Load) { if (s.Name == "D3D 3D") gpu3DLoadSensor = s; else if (gpu3DLoadSensor == null && s.Name == "GPU Core") gpu3DLoadSensor = s; } if (s.SensorType == SensorType.Temperature) { if (s.Name == "GPU Core") gpuTempSensor = s; else if (gpuTempSensor == null) gpuTempSensor = s; } if (s.SensorType == SensorType.SmallData) { if (s.Name == "GPU Memory Used") gpuVramUsedSensor = s; if (s.Name == "GPU Memory Total") gpuVramTotalSensor = s; } } } private void CacheMemorySensors(IHardware hw) { foreach (var s in hw.Sensors) { if (s.SensorType == SensorType.Data && s.Name == "Memory Used") memUsedSensor = s; if (s.SensorType == SensorType.Data && s.Name == "Memory Available") memAvailSensor = s; } } public void UpdateAndSend() { RestartComputerIfNeeded(); //cpuHw?.Update(); //gpuHw?.Update(); //memHw?.Update(); // Single-pass update: update ALL enabled hardware in one loop foreach (var hw in computer.Hardware) hw.Update(); float cpu = GetCpuLoadPercent(); float cpuTemp = GetCpuTemperaturePercent(); float mem = GetMemoryUsagePercent(); float gpu3d = GetGpu3DLoad(); float gpuTemp = GetGpuTemperaturePercent(); float vram = GetGpuVramPercent(); float[] packet = { cpu, cpuTemp, mem, gpu3d, gpuTemp, vram, 0f, 0f }; udp.SendFloats(packet); } private float GetCpuLoadPercent() { if (cpuLoadSensors.Length == 0) return 0; float total = 0; int count = 0; foreach (var s in cpuLoadSensors) { total += s.Value ?? 0; count++; } return count == 0 ? 0 : total / count; } private float GetCpuTemperaturePercent() { float t = cpuTempSensor?.Value ?? 0; return Math.Clamp(t, 0, 100); } private float GetGpu3DLoad() { return gpu3DLoadSensor?.Value ?? 0; } private float GetGpuTemperaturePercent() { float t = gpuTempSensor?.Value ?? 0; return Math.Clamp(t, 0, 100); } private float GetGpuVramPercent() { float used = gpuVramUsedSensor?.Value ?? 0; float total = gpuVramTotalSensor?.Value ?? 0; if (total <= 0) return 0; return (used / total) * 100f; } private float GetMemoryUsagePercent() { float used = memUsedSensor?.Value ?? 0; float avail = memAvailSensor?.Value ?? 0; float total = used + avail; if (total <= 0) return 0; return (used / total) * 100f; } public void Dispose() { udp.Dispose(); computer.Close(); } }