using System; using System.Net.Sockets; using System.Text; using System.Collections.Generic; using LibreHardwareMonitor.Hardware; public class Telemetry { private Config config; private string oscIp = "127.0.0.1"; private int oscPort = 9000; private ISensor[] cpuLoadSensors = Array.Empty(); private ISensor? cpuTempSensor; private ISensor? gpuVramUsedSensor; private ISensor? gpuVramTotalSensor; private ISensor? gpu3DLoadSensor; private ISensor? gpuTempSensor; private Computer computer = new Computer(); private UdpClient udp = new UdpClient(); // ---------------- INITIALIZATION ---------------- public void Initialize() { config = Config.Load(); // Load defaults from config oscIp = config.OscIp; oscPort = config.OscPort; // Override with command-line args if provided ParseArgs(Environment.GetCommandLineArgs()); // Save updated config (optional) config.OscIp = oscIp; config.OscPort = oscPort; config.Save(); computer.IsMemoryEnabled = true; computer.IsCpuEnabled = true; computer.IsGpuEnabled = true; computer.Open(); DetectSensors(); } public int UpdateRateMs => config.UpdateRateMs; private void ParseArgs(string[] args) { foreach (var arg in args) { if (arg.StartsWith("--ip=")) oscIp = arg.Substring("--ip=".Length); if (arg.StartsWith("--port=") && int.TryParse(arg.Substring("--port=".Length), out int p)) oscPort = p; if (arg.StartsWith("--rate=") && int.TryParse(arg.Substring("--rate=".Length), out int r)) config.UpdateRateMs = r; } } // ---------------- MAIN UPDATE LOOP ---------------- public void UpdateAndSend() { int memPercent = GetMemoryUsagePercent(); int cpuPercent = GetCpuLoadPercent(); int vramPercent = GetGpuVramPercent(); int gpu3DPercent = GetGpu3DLoad(); int cpuTempPercent = GetCpuTemperaturePercent(); int gpuTempPercent = GetGpuTemperaturePercent(); SendOscBundle( cpuPercent / 100f, cpuTempPercent / 100f, memPercent / 100f, gpu3DPercent / 100f, gpuTempPercent / 100f, vramPercent / 100f ); } // ---------------- SENSOR DETECTION ---------------- private void DetectSensors() { var cpuLoadList = new List(); ISensor? bestCpuTemp = null; ISensor? bestGpuTemp = null; ISensor? bestGpu3D = null; ISensor? bestVramUsed = null; ISensor? bestVramTotal = null; foreach (var hw in computer.Hardware) { hw.Update(); if (hw.HardwareType == HardwareType.Cpu) { foreach (var sensor in hw.Sensors) { if (sensor.SensorType == SensorType.Load && sensor.Name.Contains("CPU Core")) cpuLoadList.Add(sensor); if (sensor.SensorType == SensorType.Temperature) { if (sensor.Name == "Core (Tctl/Tdie)") bestCpuTemp = sensor; else if (bestCpuTemp == null) bestCpuTemp = sensor; } } } if (hw.HardwareType == HardwareType.GpuNvidia || hw.HardwareType == HardwareType.GpuAmd || hw.HardwareType == HardwareType.GpuIntel) { foreach (var sensor in hw.Sensors) { if (sensor.SensorType == SensorType.Temperature) { if (sensor.Name == "GPU Core") bestGpuTemp = sensor; else if (bestGpuTemp == null) bestGpuTemp = sensor; } if (sensor.SensorType == SensorType.Load) { if (sensor.Name == "D3D 3D") bestGpu3D = sensor; else if (bestGpu3D == null && sensor.Name == "GPU Core") bestGpu3D = sensor; } if (sensor.SensorType == SensorType.SmallData) { if (sensor.Name == "GPU Memory Used") bestVramUsed = sensor; if (sensor.Name == "GPU Memory Total") bestVramTotal = sensor; } } } } cpuLoadSensors = cpuLoadList.ToArray(); cpuTempSensor = bestCpuTemp; gpuTempSensor = bestGpuTemp; gpu3DLoadSensor = bestGpu3D; gpuVramUsedSensor = bestVramUsed; gpuVramTotalSensor = bestVramTotal; } // ---------------- METRICS ---------------- private int GetMemoryUsagePercent() { float used = 0; float available = 0; foreach (var hw in computer.Hardware) { if (hw.HardwareType == HardwareType.Memory) { hw.Update(); foreach (var sensor in hw.Sensors) { if (sensor.SensorType == SensorType.Data && sensor.Name == "Memory Used") used = sensor.Value ?? 0; if (sensor.SensorType == SensorType.Data && sensor.Name == "Memory Available") available = sensor.Value ?? 0; } } } float total = used + available; if (total <= 0) return 0; return (int)Math.Round((used / total) * 100); } private int GetCpuLoadPercent() { if (cpuLoadSensors.Length == 0) return 0; float total = 0; int count = 0; foreach (var sensor in cpuLoadSensors) { sensor.Hardware.Update(); total += sensor.Value ?? 0; count++; } return count == 0 ? 0 : (int)Math.Round(total / count); } private int GetGpuVramPercent() { if (gpuVramUsedSensor == null || gpuVramTotalSensor == null) return 0; gpuVramUsedSensor.Hardware.Update(); float usedMB = gpuVramUsedSensor.Value ?? 0; float totalMB = gpuVramTotalSensor.Value ?? 0; if (totalMB <= 0) return 0; return (int)Math.Round((usedMB / totalMB) * 100); } private int GetGpu3DLoad() { if (gpu3DLoadSensor == null) return 0; gpu3DLoadSensor.Hardware.Update(); return (int)Math.Round(gpu3DLoadSensor.Value ?? 0); } private int GetCpuTemperaturePercent() { if (cpuTempSensor == null) return 0; cpuTempSensor.Hardware.Update(); float temp = cpuTempSensor.Value ?? 0; return (int)Math.Round(Math.Clamp(temp, 0, 100)); } private int GetGpuTemperaturePercent() { if (gpuTempSensor == null) return 0; gpuTempSensor.Hardware.Update(); float temp = gpuTempSensor.Value ?? 0; return (int)Math.Round(Math.Clamp(temp, 0, 100)); } // ---------------- OSC ---------------- private void SendOscBundle( float cpu, float cpuTemp, float mem, float gpu3d, float gpuTemp, float vram) { var messages = new List(); messages.Add(BuildOscFloatMessage("/cpu", cpu)); messages.Add(BuildOscFloatMessage("/cputemp", cpuTemp)); messages.Add(BuildOscFloatMessage("/memory", mem)); messages.Add(BuildOscFloatMessage("/gpu3d", gpu3d)); messages.Add(BuildOscFloatMessage("/gputemp", gpuTemp)); messages.Add(BuildOscFloatMessage("/vram", vram)); byte[] bundle = BuildOscBundle(messages); udp.Send(bundle, bundle.Length, oscIp, oscPort); } private byte[] BuildOscBundle(List messages) { List parts = new List(); parts.Add(PadOscString("#bundle")); byte[] timetag = new byte[8]; timetag[7] = 1; parts.Add(timetag); foreach (var msg in messages) { byte[] len = BitConverter.GetBytes(msg.Length); if (BitConverter.IsLittleEndian) Array.Reverse(len); parts.Add(len); parts.Add(msg); } int total = 0; foreach (var p in parts) total += p.Length; byte[] bundle = new byte[total]; int offset = 0; foreach (var p in parts) { Buffer.BlockCopy(p, 0, bundle, offset, p.Length); offset += p.Length; } return bundle; } private byte[] BuildOscFloatMessage(string address, float value) { byte[] addr = PadOscString(address); byte[] types = PadOscString(",f"); byte[] floatBytes = BitConverter.GetBytes(value); if (BitConverter.IsLittleEndian) Array.Reverse(floatBytes); byte[] packet = new byte[addr.Length + types.Length + floatBytes.Length]; Buffer.BlockCopy(addr, 0, packet, 0, addr.Length); Buffer.BlockCopy(types, 0, packet, addr.Length, types.Length); Buffer.BlockCopy(floatBytes, 0, packet, addr.Length + types.Length, floatBytes.Length); return packet; } private byte[] PadOscString(string s) { byte[] raw = Encoding.ASCII.GetBytes(s); int paddedLength = ((raw.Length + 1 + 3) / 4) * 4; byte[] padded = new byte[paddedLength]; Buffer.BlockCopy(raw, 0, padded, 0, raw.Length); return padded; } }